MerchantFlowMerchantFlow Docs
Profit & Loss

Importing COGS - CSV, BeProfit, and Store Cost Sync

Bulk import product costs into MerchantFlow with CSV files, BeProfit exports, and one-click Shopify or WooCommerce cost sync.

Importing COGS

COGS imports get product costs into MerchantFlow at scale instead of typing them one variant at a time. There are three import paths available to every plan -- a generic CSV, a BeProfit export for merchants migrating from that tool, and a one-click cost sync from your connected Shopify or WooCommerce store -- plus a fourth, supplier invoice import, which is in limited early access. This guide covers exactly what each path accepts, how existing entries are protected, and which path to use when.

Quick router: store costs already maintained in Shopify or WooCommerce - use the store sync; a supplier price list in a spreadsheet - use CSV; migrating from BeProfit - use the BeProfit tab; a supplier invoice with freight and duty - use the invoice import, if it is enabled for your workspace.

How to Access

Navigate to Profit > COGS and click the "Import" button. The Bulk Import COGS modal opens with four tabs: CSV, BeProfit, Shopify, and WooCommerce. The platform tabs are only enabled when that platform is connected, and the modal opens on your connected platform's tab automatically.

CSV Import

Upload any CSV that contains a SKU column and a cost column. Header names are case-insensitive.

Supported Columns

FieldAccepted headersNotes
SKUskuRequired unless the row carries variant_id instead. No aliases - the header must be exactly sku
Variant IDvariant_id, variantid, variant idOptional. Resolves to that variant's SKU on our side - use this to target a specific variant when several share a SKU
Unit costcost, cogs, unit_cost, cost_per_itemThe product cost per unit (or the total for min_quantity units - see cost_basis below)
Cost currencycurrencyOptional per-row override. Falls back to the currency selector on the import modal, which itself falls back to your store currency. Must be a valid 3-letter ISO code
Quantity tier floor (cost)min_quantity, min_qty, quantity_tier, tier_qtyOptional. The PRODUCT COST tier floor - add several rows for the same SKU/variant with different min_quantity values to build a price-break ladder
Cost basiscost_basis, costbasis, cost basis, price_basisOptional, defaults to per_unit. Set to per_group when cost is the TOTAL for min_quantity units, exactly as your supplier quoted it. per_group requires min_quantity of at least 1
Per-unit fulfillment ratefulfillment_cost, fulfilment_cost, fulfillment, fulfilmentOnly used with the Per product fulfillment method
Fulfillment basisfulfillment_basis, fulfilment_basis, fulfillment basis, fulfilment basisOptional, defaults to per_unit. Set to per_order when fulfillment_cost is a single parcel price rather than a per-item rate
Destination countrycountry_code, country, destination_country, ship_countryWrites a country fulfillment rate override
Quantity tier floor (fulfilment)fulfillment_min_quantity, fulfilment_min_quantity, fulfillment_min_qty, fulfilment_min_qtyOptional. The country-rate tier floor on a row that ALSO carries cost - see the back-compat note below

A file with sku, country_code, and fulfillment_cost but no cost column is valid - it imports country rates only. A single row can also update both COGS and a country rate at once.

Back-compat on min_quantity: on a country-rate-only row (a country_code with no cost column), min_quantity keeps its original meaning and sets the country-rate tier - so CSVs written before price breaks existed still import unchanged. On a row that carries both cost and country_code, min_quantity belongs to the product-cost side and the country side reads fulfillment_min_quantity instead (defaulting to the base tier if omitted).

Not supported in the CSV:

  • No handling cost column - handling can only be imported through the BeProfit tab (below). The import modal's hint text mentions handling_cost, but the CSV parser does not read it.
  • No effective date column - imported entries always take effect at the moment of import. To backdate or schedule costs, use the Bulk Update flow instead - see COGS Accuracy. Because the timestamp is exact rather than midnight, re-uploading the same file adds a fresh history entry rather than replacing the previous one.

Downloadable CSV Template

The CSV tab includes a "Download CSV template" link that generates a file with one row per active variant in your store. The header row is exactly:

sku,variant_id,product,variant,price,cost,min_quantity,currency,cost_basis

sku, variant_id, product, variant, and price are filled in for reference, currency is pre-filled with your store currency, and cost, min_quantity, and cost_basis are left blank for you to complete.

The template does not include fulfillment_cost, country_code, fulfillment_min_quantity, or fulfillment_basis - those columns are optional and only needed if you are also bulk-loading fulfilment rates, so add them yourself using the header names in the table above. Stores with more than 25,000 active variants cannot generate a template and should narrow the catalogue (for example, by exporting one collection at a time from your store platform) before importing.

Replace Existing COGS History

The CSV tab includes a "Replace existing COGS history" checkbox: it deletes all existing COGS entries before importing, for when you have updated every cost upstream and want a clean slate.

Note: Replacing history is destructive and cannot be undone. Historical P&L is recalculated against the new cost basis. MerchantFlow asks for a second confirmation - the button changes to "Confirm: delete {n} & import" - before anything is deleted. Leave the checkbox off for normal imports.

BeProfit Import

Migrating from BeProfit? Upload the CSV exported from BeProfit's Product Costs page on the BeProfit tab.

  • Products are matched by Shopify product ID first, falling back to SKU for rows without one - keep BeProfit's product_ID column in the export for the most reliable matching.
  • Columns are auto-detected, including cost aliases (cost, cost_per_item, cogs, unit_cost, cost_per_unit, product_cost) and handling cost aliases (handling_cost, handling, shipping_cost).
  • If no cost column is detected, you can point the importer at the right column manually.
  • Duplicate rows sharing a product ID are collapsed (the first occurrence wins). Rows whose cost cell is blank, non-numeric, or negative are dropped before the import is sent - a cost of exactly 0 is imported, not skipped.
  • Imported entries are stored with the CSV source badge, with a note recording the BeProfit product title.
  • The tab has the same "Replace existing COGS history" checkbox and two-click confirmation as the CSV tab.

Note: The BeProfit import is currently the only way to load per-unit handling costs into MerchantFlow. Handling is folded into the COGS line of your P&L - see COGS Accuracy for the details and for a warning about manual edits resetting handling to zero.

Shopify and WooCommerce Cost Sync

The platform tabs pull costs straight from your store:

  • Shopify reads each variant's "Cost per item" field. Variants with a blank or zero cost are skipped - set those in Shopify first, or type them in on the COGS page.
  • WooCommerce reads each product's cost meta the same way.

Synced costs are recorded as-is - Shopify costs in your Shopify shop currency, WooCommerce costs in your MerchantFlow store currency. The number is never converted at import.

Two modes:

  • "Fill in missing costs" (default) - only adds costs that are missing or already came from your store. It never overwrites costs you set manually in MerchantFlow.
  • "Replace all costs with my store values" - overwrites everything, including manual entries, with the store's current costs. This is the destructive option and requires the same two-click confirmation as the CSV replace.

Scope: by default only live products sync. Tick "Include drafts and archived" to also load costs for draft and archived products.

Automatic sync: besides the manual button, MerchantFlow re-syncs store costs after every product sync. The automatic sync always runs in fill-in mode, so your manual entries are never clobbered automatically - they are only replaced when you explicitly choose "Replace all costs with my store values".

Supplier Invoice Import

Availability: this path is in limited early access. It requires a Pro or Plus plan and has to be switched on for your workspace by MerchantFlow -- it is off by default, so most workspaces will not see it yet. If the card described below is not on your import modal, it has not been enabled for you. The other three import paths are unaffected.

Where it is enabled, MerchantFlow can read a supplier invoice and turn it into per-unit landed costs. Open the "Import from a supplier invoice" card at the top of the import modal.

The wizard has four steps:

  1. Upload - PDF, PNG, JPG, or WEBP up to 8 MB each, up to 10 files and 24 MB in one go. File type is verified from the file's own bytes, not its extension, so a renamed file is rejected. Files are read once and deleted straight away - they are never stored, logged, or used for training.
  2. Review - check the extracted supplier, invoice number, date, currency, and line items. Mark each line as "Product cost", "Spread across products", or "Not a product cost", and review invoice-level charges such as freight, duty, tax, inspection, or brokerage.
  3. Match - confirm which product each line belongs to, choose how freight and duty are shared out (by line value, units, or weight), and set the exchange rate if the invoice is not in your store currency.
  4. Confirm - see current cost vs new landed cost per product, pick the "Costs apply from" date, and choose a write mode:
    • "From this date onwards" - past profit figures untouched (the right choice in almost every case)
    • "Replace all cost history" - applies the cost to every order, past and present
    • "Blend with stock on hand" - averages the new cost with your existing stock, weighted by the units you still hold

Every import is listed on the Invoice imports history page, where you can continue a draft, view what was written, or Undo an applied import - undoing restores the previous costs and recalculates profit. Entries written this way carry an invoice-import source badge on the COGS page. Invoice imports have a monthly cap by plan; entering costs by hand stays unlimited.

Understanding Import Results

After an import or sync, MerchantFlow reports exactly what happened, for example:

  • "Imported {n} costs." - new entries written
  • "{n} costs were kept as your manual entries - choose "Replace all costs" to overwrite them." - those SKUs have manual entries, which always take precedence over imported ones
  • "{n} variants were skipped - no cost set in your store yet." - fix the costs in Shopify or WooCommerce, or set them on the COGS page

The "kept as your manual entries" rule is part of MerchantFlow's source precedence: a manual entry always beats an auto-synced one, even a newer one. See COGS Accuracy for the full precedence model.

Best Practices

1. Store Sync for the Baseline, CSV for Supplier Price Lists

Keep costs in your store platform where possible and let the automatic sync maintain them. Use CSV when your source of truth is a supplier spreadsheet.

2. Treat "Replace" Options as Migration-Only

Both replace modes delete or overwrite history. Use them when starting fresh or migrating tools - never for routine updates.

3. Run the BeProfit Import Once, at Migration

Bring costs and handling over in one pass, then maintain them in MerchantFlow. Re-running it later can overwrite refinements you made since.

4. Fix Duplicate SKUs Before Importing, or Use variant_id

Rows without a variant_id match by SKU. If several variants share one SKU, they will all receive the same cost - either give variants unique SKUs first, or include a variant_id column (the value the downloadable template already fills in) to target one specific variant per row. See COGS Accuracy.

Troubleshooting

Rows were skipped during CSV import

Cause: Missing or blank sku values (and no variant_id either), a missing required column, an invalid min_quantity (must be a non-negative whole number), an invalid cost_basis or fulfillment_basis value (must be per_unit/per_group or per_unit/per_order), a per_group row with no min_quantity, or invalid country/quantity values on country-rate rows. Solution: Check the error message for the failing row numbers, fix the file, and re-upload. The header must include sku (or variant_id) plus either a cost column or the country_code and fulfillment_cost pair - otherwise the file is refused outright with "CSV must have a "SKU" column plus either a "Cost" column or "country_code" and "fulfillment_cost" columns."

In a normal import, valid rows still import and the bad ones are listed back to you ("Some rows could not be imported: ..."). With "Replace existing COGS history" ticked, a single bad row aborts the entire import before anything is deleted, so your existing history is never left half-replaced. Row numbers count data rows, excluding the header.

The import finished but some products did not change

Cause: Those SKUs have manual entries, which imports never overwrite in fill-in mode. Solution: This is by design. If you genuinely want store values to win, choose "Replace all costs with my store values" - understanding that it overwrites your manual work.

Shopify sync imported 0 costs

Cause: No variants have "Cost per item" filled in Shopify. Solution: In Shopify admin, open each product's variants and enter the cost, then sync again - or set costs directly on the COGS page.

Handling costs disappeared after editing a product's cost

Cause: Manual cost edits write a fresh entry with handling reset to zero. Solution: Re-import handling via the BeProfit tab after bulk edits, or account for handling inside the unit cost instead. See COGS Accuracy.

Frequently Asked Questions

Can I set an effective date in the CSV?

No. CSV imports always take effect today. To backdate a change or schedule a future one, use the Bulk Update or Schedule Price Change flows on the COGS page - see COGS Accuracy.

Can I import costs in a different currency?

Yes, per row. Add a currency column with a valid 3-letter ISO code (for example, a USD supplier quote for a store that sells in SEK); MerchantFlow converts it at each order's daily exchange rate when calculating profit. Leave the column out, or leave a cell blank, and that row uses the currency selected on the import modal, which defaults to your store currency. The downloadable CSV template pre-fills currency with your store currency for convenience - overwrite it per row if some of your costs are quoted differently.

Will the automatic store sync overwrite my manual costs?

Never. The automatic sync runs in fill-in mode only. Manual entries are only replaced when you explicitly choose "Replace all costs with my store values" in the import modal.

Is my uploaded invoice stored anywhere?

No. Invoice files are read once to extract the line items and deleted straight away - they are not stored, logged, or used for training.

How many invoice imports do I get?

Where invoice import is enabled for your workspace, it is capped per calendar month: 20 on Pro, 100 on Plus. Starter plans do not include it. The wizard shows your usage ("{used} of {limit} invoice imports used this month"). Failed and discarded imports still count against the cap. Manual cost entry is always unlimited, and the cap never applies to reviewing, matching, applying, or undoing an import you already uploaded.

Can one CSV contain only country fulfillment rates?

Yes. A file with sku, country_code, and fulfillment_cost columns (optionally min_quantity and fulfillment_basis) imports country rates without touching COGS. See Country Fulfillment Rates.

Can I bulk-load quantity price breaks via CSV?

Yes. Add several rows sharing the same sku (or variant_id), each with a different min_quantity and its cost, and set cost_basis to per_group if the amount is a total rather than a per-item price. See Price Breaks for the full mechanics and worked examples.


Last updated: September 9, 2026

Last updated on

On this page