Skip to main content

Bulk import recipes (BOMs)

If you're setting up manufacturing for the first time, building every recipe by hand adds up fast. Import Recipes takes a spreadsheet and creates them in bulk — one row per component, so a recipe with four components is four rows.

You can import recipes for products that already exist. Create the products first, then bring their recipes in.

Before you begin​

  • The finished product and every component already exist as products. The import matches on SKU and won't create products for you.
  • Products you're building recipes for should have their Type set to Manufactured. A recipe on another type still imports, but you'll see a warning, and manufacturing orders expect a manufactured product.
  • To use Import on Manufacturing → Recipes / BOMs, you need the manufacturing.import permission.

Steps​

  1. Go to PIM → Products.
  2. Click Import.
  3. Under Import Type, choose Import Recipes.
  4. Click Download Template to get a spreadsheet with the right column headers, a field guide, and a Reference Data sheet listing your existing recipes and product SKUs. Fill in your rows, or paste the data straight into the box on this screen.
  5. Click Next: Map Columns. If your headers match the template, every column maps itself — check the Maps To side and correct anything that didn't.
  6. Click Next: Validate. Your rows are checked in the background and come back marked valid, warning, or error, with the reason on the row.
  7. Review the counts, then click Import. Recipes are created and the finished count appears when it's done.

The Import Recipes step of the import wizard

One row per component​

This is the part worth getting right. A recipe is not one row — each of its components is a row, and rows are grouped into a recipe by two columns together: product_sku and recipe_name.

So this file is two recipes from three rows:

product_sku,recipe_name,component_sku,quantity
DOC-TUMBLER-4PK,Four-pack assembly,DOC-TUMBLER-500,4
DOC-COLDBREW-1L,Cold brew batch,DOC-COFFEE-1KG,1
DOC-COLDBREW-1L,Cold brew batch,DOC-BOTTLE-1L,1

Repeat product_sku and recipe_name on every row of the same recipe. Because a row isn't a recipe, the button counts rows — "Import 3 rows" — while the finished screen reports the recipes actually created.

Leave recipe_name blank and the recipe is named after its product, as <SKU> Recipe.

Validation showing three valid rows

Columns​

Only the first three are required. Everything else is optional, and a blank cell means "leave it alone". The same columns are used whether you import from PIM → Products (Import Recipes) or from Manufacturing → Recipes / BOMs (Bills of Materials).

Component columns describe one line of the recipe:

ColumnRequiredFormatAccepted valuesNotes
product_skuYesTextAn existing product SKUThe product the recipe makes. Repeat it on every row of the recipe.
component_skuYesTextAn existing product SKUOne component the recipe consumes. Add a row per component. A recipe can't use its own product, and can't loop back on itself through another recipe.
quantityYesNumberGreater than zeroHow much of this component one build uses.
recipe_nameNoText, up to 255Any textNames the recipe. Defaults to <product_sku> Recipe. A product can have several recipes — see below.
uom_codeNoTextA unit code from Manufacturing → Units of Measure, e.g. KGThe unit quantity is in. Defaults to the component's stock unit. The component needs a conversion to this unit on its Units of Measure tab.
expected_scrap_percentNoNumber0–100How much of this component you expect to lose in a build.
consumption_methodNoTextforward flush, backflushWhether an operator issues this component during production, or it's consumed automatically from the output recorded.
is_optionalNoTextyes, noMarks a component a build can go ahead without.
is_contractor_suppliedNoTextyes, noThe contract manufacturer supplies this component from their own stock, so it's never allocated or costed on your side.
component_notesNoTextAny textA note against this component line.
sort_orderNoWhole numbere.g. 1The line's position in the recipe.

Recipe columns describe the whole recipe. SKU.io reads them from the recipe's first row. A different value on a later row of the same recipe is ignored, with a warning naming the row it used.

ColumnRequiredFormatAccepted valuesNotes
effective_fromNoDatee.g. 2026-10-01The date the recipe takes effect. Defaults to today.
effective_toNoDateOn or after effective_fromThe last date the recipe can be used. Blank means no end date.
yield_percentNoNumber0–100Expected yield for the whole recipe.
leadtime_daysNoWhole numbere.g. 3How many days a build takes.
version_notesNoTextAny textA note describing this version of the recipe.
statusNoTextdraft, activeDefaults to active. Import never archives a recipe; archive it from the list instead.
is_defaultNoTextyes, noMakes this the product's default recipe, the one picked automatically on a new manufacturing order. The current default is demoted, with a warning. A draft recipe can't be the default. Only one recipe per product can claim it in a file.
is_phantomNoTextyes, noA phantom recipe is never built on its own. A parent recipe that uses it consumes its components directly.
cost_allocation_methodNoTextby_weight, by_volume, by_default_cost, by_avg_cost, by_manual_percentHow a build's cost is split between its outputs when it yields co-products. Switching an existing recipe is refused if its outputs can't support the method, such as by_weight with an output that has no weight.
default_consumption_methodNoTextforward flush, backflushThe consumption method for component lines that don't set their own.

Re-importing, and products with more than one recipe​

A product can hold several recipes at once — a standard build and a rush build, say. The recipe name is what tells them apart, and it decides what a repeat import does:

You importWhat happens
The same recipe, unchangedNothing. Re-running a file is safe.
The same recipe name, with different componentsA new version of that recipe. The previous version is kept.
A different recipe name for the same productA separate recipe, sitting alongside the first.

Only a product's first recipe becomes its default. Importing a second one won't quietly take the default away from the recipe you're already using, unless its is_default column says yes.

When rows don't pass​

Validation runs before anything is created, and every row is judged on its own:

  • Errors stop that row only — the rest of the file still imports. You'll see the reason on the row: a SKU that doesn't exist, a quantity that isn't a positive number, a component listed twice in one recipe, or a recipe using its own product as a component.
  • Warnings don't stop anything. The common one is a recipe on a product whose Type isn't Manufactured.
  • If a recipe's first row has an error, the rest of that recipe still imports — the error doesn't take the whole recipe down with it.

Fix the flagged rows in your spreadsheet and import again. Because re-importing an unchanged recipe does nothing, you can safely re-upload the whole file rather than picking out the rows that failed.

Recipes on the products list​

Once imported, the Recipes column on PIM → Products shows each product's recipe by name, or "N recipes" when it has more than one. Click through to see the recipe itself.

The Recipes column on the products list

Import from Recipes / BOMs​

Manufacturing → Recipes / BOMs has its own Import button with three import types. The first builds recipes. The other two add to recipes that already exist, and nothing else in a spreadsheet can set them:

Import typeOne row perWhat it does
Bills of MaterialsComponent lineThe same import as Import Recipes on the products list, with the columns above.
Output LinesOutput of a recipeSets what a build yields: the primary product plus any co-products, by-products and scrap.
OperationsProduction stepSets a recipe's routing: its operations, their work centers, times and costs.
  1. Go to Manufacturing → Recipes / BOMs and click Import.
  2. Under Import Type, choose Bills of Materials, Output Lines or Operations.
  3. Click Download Template for that type. Its Reference Data sheet lists your existing recipes, product SKUs, unit codes and, for operations, your work center names.
  4. Fill in your rows or paste them, then click Next: Map Columns, Next: Validate and Import, as in the steps above.

Output lines and operations always attach to a recipe that already exists. If one file also creates the recipes, import its Bills of Materials rows first, then the outputs, then the operations.

Output lines and operations replace the whole set

Importing output lines or operations for a recipe replaces every output line or operation it already has. Any line you leave out of the file is removed. Validation warns on each recipe it will replace, for example "Replaces the 2 operations already on this BOM — any operation not in this file is removed."

Which recipe a row attaches to​

Both child imports find their recipe with two columns together:

  • product_sku: the product the recipe makes.
  • bom_name: the recipe's name. Leave it blank to use the product's default recipe. When several versions share a name, the newest one is used. Archived recipes are never matched.

Rows for the same recipe are written together, in one go, so their order in the file only matters for sort_order and sequence.

Output Lines columns​

ColumnRequiredFormatAccepted valuesNotes
product_skuYesTextAn existing product SKUThe product the recipe makes.
bom_nameNoTextAn existing recipe nameBlank means the product's default recipe.
output_skuYesTextAn existing product SKUThe product this output line yields.
roleYesTextprimary, coproduct, byproduct, scrapEach recipe needs exactly one primary row, usually the recipe's own product.
quantityYesNumberGreater than zeroHow much one build yields.
uom_codeNoTextA unit code, e.g. KGDefaults to the output product's stock unit. The product needs a conversion to this unit.
manual_cost_percentNoNumber0–100The output's share of the build's cost when the recipe's Cost Allocation Method is By Manual Percent. Primary and co-product rows must then total 100.
byproduct_valuation_methodNoTextzero, market_value, percent_of_input, fixed_costHow a by-product is valued. Only on byproduct rows.
byproduct_valuation_amountNoNumber0 or moreWith market_value, dollars per unit produced. With percent_of_input, a percentage (0–100) of the component cost. With fixed_cost, a flat amount per manufacturing order. Needs a valuation method.
sort_orderNoWhole numbere.g. 1The line's position. Defaults to file order.

A recipe whose outputs a manufacturing order already uses can't have its outputs replaced. Edit the recipe to create a new version instead. A recipe that splits cost By Weight needs a weight on every primary and co-product output.

Operations columns​

ColumnRequiredFormatAccepted valuesNotes
product_skuYesTextAn existing product SKUThe product the recipe makes.
bom_nameNoTextAn existing recipe nameBlank means the product's default recipe.
sequenceYesWhole number0 or more, unique within the recipeThe order the steps run in, for example 10, 20, 30.
operation_nameYesTextAny textThe step's name, for example Bind and trim.
work_centerNoTextA work center name, not its codeMust match a work center in Manufacturing → Work Centers. See Set up work centers.
operation_typeNoTextprocess, setup, per_unit, fixed_costDefaults to process.
time_minutesNoNumber0 or moreThe step's time. A process step with no time adds no cost, and validation warns you.
cost_per_hourNoNumber0 or moreBlank takes the work center's Default Hourly Rate. It's only charged against time.
fixed_costNoNumber0 or moreA flat cost for the step.
cost_classificationNoTextlabor, overheadLabor operations pick up the work center's overhead rate.
instructionsNoTextAny textNotes for the shop floor.

Round-trip a whole recipe​

On Manufacturing → Recipes / BOMs, click Export and choose Full Workbook (XLSX). You get four sheets, BOMs, Components, Outputs and Operations, with the same headers the imports expect. Edit the workbook, then import it once per type, in the order Bills of Materials, Output Lines, Operations. Each time, upload the same workbook, and the import picks the sheet that matches the type you chose.

Importing a recipe alongside the product itself​

If you're setting products up from scratch, you can bring a product's main recipe in on the same sheet as the product, using Import Products instead. It uses the same SKU->QTY shorthand as kits and bundles:

ColumnFormatNotes
recipe_component_skusSKU->QTY, SKU->QTYe.g. DOC-TUMBLER-500->4. Components can be defined on other rows of the same file.
recipe_nameTextDefaults to <SKU> Recipe.
recipe_effective_fromDateDefaults to today.
recipe_yield_percentNumber, 0–100
recipe_leadtime_daysWhole number

One row is one product, so this route carries one recipe per product per file. For several recipes on the same product, or for per-component detail like scrap and consumption method, use Import Recipes.

A new product that arrives with a recipe and no Type set is created as Manufactured.

Exporting recipes​

The same columns come back out. On PIM → Products, click Export and include the recipe columns — you'll get each product's default recipe in the same SKU->QTY shorthand the product import accepts, so you can export, edit in a spreadsheet, and import the changes back.

For every recipe, not only the default, and for output lines and operations too, export from Manufacturing → Recipes / BOMs instead. See Round-trip a whole recipe.

Next steps​

Video transcript

Building every recipe by hand is slow work. A spreadsheet import does the whole set in one pass. Start on the Products page. Click Import at the top of the products list, then choose Import Recipes from the three types on offer. The template carries the right headers and a field guide. You can also paste rows straight into the box. One detail matters more than the rest. Each component of a recipe gets its own row. The product and the name together decide which lines belong to which build. So the file on screen is three rows, and exactly two recipes. Move on to Map Columns. Headers that match the template map themselves. Check the right-hand side for any gaps. Validation runs before anything is created. Each row is judged on its own merits. An error stops that row alone. The rest of the file still imports. Notice the button counts rows, not recipes. Import, and the work runs in the background. For a file this size it takes barely a moment. Three rows became two recipes, and the summary reports the count. Back on the products list, the Recipes column shows the name, or a count when a product has more than one build. Re-importing an unchanged file changes nothing, so you can safely upload the whole sheet again after fixing a row.

Last verified: