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.importpermission.
Steps
- Go to PIM → Products.
- Click Import.
- Under Import Type, choose Import Recipes.
- 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.
- 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.
- Click Next: Validate. Your rows are checked in the background and come back marked valid, warning, or error, with the reason on the row.
- Review the counts, then click Import. Recipes are created and the finished count appears when it's done.

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.

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:
| Column | Required | Format | Accepted values | Notes |
|---|---|---|---|---|
product_sku | Yes | Text | An existing product SKU | The product the recipe makes. Repeat it on every row of the recipe. |
component_sku | Yes | Text | An existing product SKU | One 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. |
quantity | Yes | Number | Greater than zero | How much of this component one build uses. |
recipe_name | No | Text, up to 255 | Any text | Names the recipe. Defaults to <product_sku> Recipe. A product can have several recipes — see below. |
uom_code | No | Text | A unit code from Manufacturing → Units of Measure, e.g. KG | The 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_percent | No | Number | 0–100 | How much of this component you expect to lose in a build. |
consumption_method | No | Text | forward flush, backflush | Whether an operator issues this component during production, or it's consumed automatically from the output recorded. |
is_optional | No | Text | yes, no | Marks a component a build can go ahead without. |
is_contractor_supplied | No | Text | yes, no | The contract manufacturer supplies this component from their own stock, so it's never allocated or costed on your side. |
component_notes | No | Text | Any text | A note against this component line. |
sort_order | No | Whole number | e.g. 1 | The 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.
| Column | Required | Format | Accepted values | Notes |
|---|---|---|---|---|
effective_from | No | Date | e.g. 2026-10-01 | The date the recipe takes effect. Defaults to today. |
effective_to | No | Date | On or after effective_from | The last date the recipe can be used. Blank means no end date. |
yield_percent | No | Number | 0–100 | Expected yield for the whole recipe. |
leadtime_days | No | Whole number | e.g. 3 | How many days a build takes. |
version_notes | No | Text | Any text | A note describing this version of the recipe. |
status | No | Text | draft, active | Defaults to active. Import never archives a recipe; archive it from the list instead. |
is_default | No | Text | yes, no | Makes 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_phantom | No | Text | yes, no | A phantom recipe is never built on its own. A parent recipe that uses it consumes its components directly. |
cost_allocation_method | No | Text | by_weight, by_volume, by_default_cost, by_avg_cost, by_manual_percent | How 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_method | No | Text | forward flush, backflush | The 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 import | What happens |
|---|---|
| The same recipe, unchanged | Nothing. Re-running a file is safe. |
| The same recipe name, with different components | A new version of that recipe. The previous version is kept. |
| A different recipe name for the same product | A 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.

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 type | One row per | What it does |
|---|---|---|
| Bills of Materials | Component line | The same import as Import Recipes on the products list, with the columns above. |
| Output Lines | Output of a recipe | Sets what a build yields: the primary product plus any co-products, by-products and scrap. |
| Operations | Production step | Sets a recipe's routing: its operations, their work centers, times and costs. |
- Go to Manufacturing → Recipes / BOMs and click Import.
- Under Import Type, choose Bills of Materials, Output Lines or Operations.
- 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.
- 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.
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
| Column | Required | Format | Accepted values | Notes |
|---|---|---|---|---|
product_sku | Yes | Text | An existing product SKU | The product the recipe makes. |
bom_name | No | Text | An existing recipe name | Blank means the product's default recipe. |
output_sku | Yes | Text | An existing product SKU | The product this output line yields. |
role | Yes | Text | primary, coproduct, byproduct, scrap | Each recipe needs exactly one primary row, usually the recipe's own product. |
quantity | Yes | Number | Greater than zero | How much one build yields. |
uom_code | No | Text | A unit code, e.g. KG | Defaults to the output product's stock unit. The product needs a conversion to this unit. |
manual_cost_percent | No | Number | 0–100 | The 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_method | No | Text | zero, market_value, percent_of_input, fixed_cost | How a by-product is valued. Only on byproduct rows. |
byproduct_valuation_amount | No | Number | 0 or more | With 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_order | No | Whole number | e.g. 1 | The 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
| Column | Required | Format | Accepted values | Notes |
|---|---|---|---|---|
product_sku | Yes | Text | An existing product SKU | The product the recipe makes. |
bom_name | No | Text | An existing recipe name | Blank means the product's default recipe. |
sequence | Yes | Whole number | 0 or more, unique within the recipe | The order the steps run in, for example 10, 20, 30. |
operation_name | Yes | Text | Any text | The step's name, for example Bind and trim. |
work_center | No | Text | A work center name, not its code | Must match a work center in Manufacturing → Work Centers. See Set up work centers. |
operation_type | No | Text | process, setup, per_unit, fixed_cost | Defaults to process. |
time_minutes | No | Number | 0 or more | The step's time. A process step with no time adds no cost, and validation warns you. |
cost_per_hour | No | Number | 0 or more | Blank takes the work center's Default Hourly Rate. It's only charged against time. |
fixed_cost | No | Number | 0 or more | A flat cost for the step. |
cost_classification | No | Text | labor, overhead | Labor operations pick up the work center's overhead rate. |
instructions | No | Text | Any text | Notes 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:
| Column | Format | Notes |
|---|---|---|
recipe_component_skus | SKU->QTY, SKU->QTY | e.g. DOC-TUMBLER-500->4. Components can be defined on other rows of the same file. |
recipe_name | Text | Defaults to <SKU> Recipe. |
recipe_effective_from | Date | Defaults to today. |
recipe_yield_percent | Number, 0–100 | |
recipe_leadtime_days | Whole 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
- Create a recipe (bill of materials) — building one by hand, and what versions and activation mean
- Build with a manufacturing order — using a recipe to actually make something
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.