Bulk recipe import and export

CSV import creates or updates many recipes at once from a spreadsheet. This article is the full reference: every column, how to format each one, and what to do when a row fails.

CSV import is part of every paid plan, and development stores can use it too. CSV export is free on every plan. Compare plans on the Recipe Kit app listing.

Download the template

The quickest way to start is to download the template, fill it in with your recipes, and import it.

  1. Open Recipe Kit and go to Tools.
  2. Select CSV import.
  3. Click Download CSV Template.

The CSV import tool with the Add CSV file drop zone and the Download CSV Template button below it

The template has a header row with every column name and one example row showing the format for each field. Open it in Google Sheets, Excel or any spreadsheet app, use the example as a reference, then delete it and add your own recipes.

Delete the example row rather than importing it. Its blog_id and article_id are placeholders that point at nothing, so the import would report that Shopify rejected the blog.

CSV column reference

Each row is one recipe. Columns can be in any order, but this is the order the template and the export use.

Required means required for a row that creates a new recipe. A file that only updates recipes you already have can leave out any column, and the recipes keep their current values for it (see Update only some columns below).

Column Required Description
recipe_id No Leave empty for new recipes. Fill in an existing recipe's ID to update that recipe.
recipe_title Yes The name of the recipe.
recipe_author No Author name.
recipe_description No Full description text.
recipe_category No Category, for example "Dessert" or "Main Course".
recipe_cuisine No Cuisine, for example "Italian" or "American".
recipe_calories No Calorie count. A whole number between -32,768 and 32,767.
serving_size No Serving size text, for example "4 servings" or "24 cookies".
prep_time No Preparation time. See Time fields below.
cook_time No Cooking time. See Time fields below.
recipe_image No URL of the recipe's main image.
recipe_image_alt_text No Alt text for the main image.
recipe_video No URL of a recipe video.
recipe_note No Additional notes about the recipe.
faqs No Frequently asked questions shown under the recipe card. See FAQs below. Recipe FAQs are part of the Growth plan, and other plans ignore this column.
enable_rating No 1 to turn on star ratings, 0 to turn them off.
blog_id Conditional The Shopify blog to connect to. Exports write this as gid://shopify/Blog/106700000000. A plain numeric ID works too. Required when Create blog posts is ticked.
article_id No An existing Shopify blog post to link to. Exports write this as gid://shopify/Article/1234567890123. A plain numeric ID works too.
recipe_tags No Tags separated by semicolons.
recipe_ingredients Yes Ingredients separated by semicolons.
recipe_equipment No Equipment separated by semicolons.
recipe_directions Yes Directions separated by semicolons.
direction_images No Image URLs for direction steps.
nutrition_data No Nutrition as Title:Value pairs separated by semicolons.
nutrition_template No Nutrition label layout: linear, horizontal or vertical. Defaults to linear.
nutrition_serving_size No Nutrition serving size text, for example "Per cookie".
custom_field_1_key No Label for the first custom field.
custom_field_1_value No Value for the first custom field.
custom_field_2_key No Label for the second custom field.
custom_field_2_value No Value for the second custom field.
update_blog_post_tags No 1 to copy the recipe's tags to the linked blog post, 0 to skip.
update_blog_post_image No 1 to copy the recipe image to the linked blog post, 0 to skip.
status Yes 1 for published, 0 for hidden. A blank cell is rejected, so fill this in on every row.

Formatting rules

Ingredients

Separate ingredients with semicolons. Each entry becomes one ingredient line.

2 cups flour;1 cup sugar;2 eggs;1 cup butter

Directions

Separate directions with semicolons. Each entry becomes one numbered step.

Preheat oven to 350F;Cream butter and sugar;Add eggs one at a time;Mix in flour;Bake for 12 minutes

Equipment

Separate equipment with semicolons.

Large mixing bowl;Whisk;Baking sheet;Parchment paper

Equipment can link to a product in your store the same way an ingredient can, with ||shopify: and the product handle (see Product links below):

Large mixing bowl;Cast iron skillet||shopify:cast-iron-skillet;Whisk

Section headings

Start an entry with HEAD: to turn it into a section heading. This works the same way in ingredients, directions and equipment.

HEAD:Dry Ingredients;2 cups flour;1 cup sugar;1 tsp baking powder;HEAD:Wet Ingredients;2 eggs;1 cup milk;1/2 cup butter

That example gives you two groups, Dry Ingredients with three items and Wet Ingredients with three items.

Directions and equipment use the same pattern:

HEAD:Preparation;Preheat oven to 350F;Grease a 9x13 pan;HEAD:Mix Batter;Cream butter and sugar;Add eggs;HEAD:Bake;Pour into pan;Bake for 30 minutes
HEAD:Mixing;Large mixing bowl;Whisk;HEAD:Baking;Baking sheet;Parchment paper

Semicolons inside an item

A semicolon starts a new item, so a semicolon you want to keep needs a backslash in front of it:

Reduce by half\; stirring often;Season to taste

If you'd rather not escape anything, use a comma instead.

Semicolons that end an HTML entity, such as & or ', never split an item, so exported recipes with apostrophes and ampersands re-import unchanged.

The same rules apply to the equipment, directions, direction_images and faqs columns. The nutrition_data column doesn't support escaping, so keep semicolons out of nutrition values.

Main image

Put a full image URL in recipe_image:

https://example.com/images/chocolate-cake.jpg
  • Images already on Shopify's CDN are used as they are, without re-uploading.
  • Images from other sites are downloaded and uploaded to your Shopify store.
  • When you update a recipe and the image URL hasn't changed, the upload is skipped.
  • If an image fails to upload, the recipe still imports, without the image.

Direction step images

Put image URLs in direction_images, separated by semicolons, in the same positions as the steps in recipe_directions. Leave a position empty for a step with no image.

https://example.com/step1.jpg;;https://example.com/step3.jpg;

Here step 1 gets step1.jpg, step 2 has no image, step 3 gets step3.jpg and step 4 has no image.

Headings count as positions too, and they always stay empty. For example, these directions have six entries, two headings and four steps:

HEAD:Prep;Preheat oven to 350F;Grease pan;HEAD:Bake;Pour batter;Bake 30 min

So direction_images needs six positions:

;;https://example.com/preheat.jpg;;;https://example.com/baking.jpg

Exports write direction images in this format already. Step images are uploaded the same way as the main image, except that if an upload fails, the original URL is kept.

Product links

Link an ingredient or a piece of equipment to a product with a double pipe (||) after its text.

Products in your store. Use shopify: followed by the product handle. The handle is the last part of the product's URL, so yourstore.com/products/organic-flour has the handle organic-flour.

2 cups flour||shopify:organic-flour;1 cup sugar;2 eggs||shopify:free-range-eggs

Recipe Kit looks up the product and attaches it, the same as linking it in the recipe editor. The ingredient or equipment links to the product page and can show the Add to Cart button. Exports write linked products on both ingredients and equipment in this shopify: format, so they survive a re-import. If a handle doesn't match a product in your store, the item imports without a link.

Any other URL. Put the full URL after the double pipe:

2 cups flour||https://yourstore.com/products/organic-flour;1 cup sugar;2 eggs

The ingredient text becomes a link to that URL. For products in your own store, use shopify: instead, because a plain URL doesn't carry variants, images or Add to Cart. Equipment takes shopify: links only, so a plain URL on equipment is dropped.

Each ingredient or piece of equipment can have only one ||. Spaces around the link are trimmed. For more on product links, see Linking products to recipes.

Tags

Separate tags with semicolons. Commas work too.

dessert;cookies;chocolate;baking;holiday

Use plain text only. HTML is stored as literal text and shows up inside the tag. If the recipe is linked to a blog post and update_blog_post_tags is 1, the tags are also written to the blog post.

Nutrition data

Write each nutrient as Title:Value, separated by semicolons:

Calories:250;Carbs:30;Fat:12;Protein:3;Sugar:18;Sodium:200
  • Every entry needs a colon. An entry without one, like Calories 250, is reported as invalid in the preview and the row is skipped.
  • Enter numbers only. The unit comes from the title, so write Fat:12, not Fat:12g.
  • A title with no value, like Fiber:, imports as an empty value.
  • A title that isn't in the list below still imports and displays, but without a unit or Google recipe markup. To give it a unit, add it as a free field (see below).
  • To set the % daily value shown on the label, add it as a third part ending in %: Carbs:30:12% is 30 grams and 12% of the daily value.
Title Unit
Calories calories
Carbs grams
Fat grams
Protein grams
Sugar grams
Fiber grams
Sodium milligrams
Cholesterol milligrams
Saturated Fat grams
Trans Fat grams
Unsaturated Fat grams
Iron milligrams
Potassium milligrams
Polyunsaturated Fat milligrams
Monounsaturated Fat milligrams

Free fields. For a nutrient that isn't in the list, put its unit in square brackets after the name: [g], [mg] or [mcg]. These are the free fields in the recipe editor's Nutrition section.

Net Carbs [g]:6;Vitamin D [mcg]:2:10%

Exports write daily values and free fields in this format, so they survive a re-import.

The nutrition_template column sets the label layout:

Value Layout
linear Minimal text
horizontal Horizontal
vertical Vertical (US standard label)

Any other value falls back to linear. Use nutrition_serving_size for what the figures apply to, for example "Per cookie".

FAQs

Write each question and answer as question||answer, and separate the pairs with semicolons:

Can I freeze it?||Yes, for up to 3 months;Can I use honey?||Yes, swap it 1:1.

Escape a semicolon inside a question or answer with a backslash. An empty cell leaves a recipe's existing FAQs as they are. See Recipe FAQs.

Custom fields

Each recipe can have up to two custom fields. A value needs a key. A key with no value is fine: it shows the field's name with nothing after it, the same as leaving the value empty in the recipe editor.

Column Example
custom_field_1_key Difficulty
custom_field_1_value Easy
custom_field_2_key Best Time
custom_field_2_value Afternoon

Time fields

prep_time and cook_time accept these formats:

Input Read as
15 minutes 15 minutes
1 hour 12 minutes 1 hour and 12 minutes
2 hours 2 hours
15 15 minutes (a bare number is minutes)

Blog post connections

  • Link to an existing blog post. Fill in both blog_id and article_id.
  • Create a new blog post. Fill in blog_id only and tick Create blog posts when you import.
  • No blog post. Leave both empty.

Set update_blog_post_tags and update_blog_post_image to 1 or 0 to choose whether the recipe's tags and image are copied to the linked blog post.

Recipe Kit exports blog and blog post IDs in the gid://shopify/Blog/106700000000 form on purpose. Excel treats a bare 11 to 13 digit ID as a number and rewrites it as 1.067E+11 when you save, and the real ID is lost. The gid form contains letters, so spreadsheets leave it alone. If you type IDs in yourself, use the gid form, or format the blog_id and article_id columns as Text before you paste.

Import your recipes

  1. Open Recipe Kit and go to Tools.
  2. Select CSV import.
  3. Drop your file on the upload area, or click Add CSV file to pick it. A file can be up to 50MB and 10,000 rows. Split a bigger collection into several files and import them one after another.
  4. The Verify CSV Import window shows how many rows are Matches (existing recipes), New Recipes and Invalid (rows with errors that will be skipped). Click Show Invalid Entries to see each error with its row number.
  5. Set the import options (see Import options below).
  6. Click Confirm Import.

The Verify CSV Import window showing 1 match, 2 new recipes and 1 invalid row

A progress bar shows how far the import has got. The import runs in the background, so you can leave the page and come back to check on it.

Import options

The Verify CSV Import window has two options:

  • Update existing recipes is ticked by default, and only appears when the file matches recipes you already have. Matched recipes are updated with the data in the file. Untick it and matched rows are created as new recipes instead.
  • Create blog posts is off by default. Tick it and Recipe Kit creates a new Shopify blog post for every row that has a blog_id but no article_id. With it ticked, every row needs a blog_id. Creating posts makes the import slower, because Shopify limits how fast posts can be created.

The Import Options section with the Update existing recipes and Create blog posts checkboxes

How rows match existing recipes

  1. By recipe_id. If the row has a recipe ID that belongs to your store, it updates that recipe.
  2. By title. Otherwise Recipe Kit looks for one of your recipes with the same title. Capital letters don't matter.

To update recipes in bulk, export them first, edit the file and re-import it. The recipe_id column from the export makes the matching exact.

Update only some columns

You don't need every column to update recipes. Keep recipe_id (or recipe_title, to match by title) and the columns you want to change, and delete the rest. For example, a file with just recipe_id and recipe_image replaces the images and leaves everything else as it is: ingredients, product links, FAQs, tags and custom fields.

  • Columns you leave out keep their current values.
  • A blank cell in a column you kept clears that field on the recipe. The one exception is faqs, where a blank cell keeps the recipe's FAQs.
  • The custom field columns work one at a time. A file with only custom_field_1_key renames the first custom field and keeps its value and the second field.

When the file leaves columns out, the Verify CSV Import window says Only the columns in this file will change, lists the columns the file will update, and warns about any blank cells in them. Read that list before you click Confirm Import.

A file like this can only update recipes. Keep Update existing recipes ticked. A row that doesn't match a recipe you have would have to create one, and a new recipe needs every required column, so that row is skipped as invalid with a message saying why.

Stop an import

While an import runs, click Stop import. Recipes already processed stay, and nothing after them is imported.

After the import

When the import finishes, a summary lists any rows under Failed to import: and any rows that saved with a problem under Imported, but not finished:, for example a recipe that saved without its blog post. Click Download import report to get those rows as a CSV, fix them, and import them again.

Export your recipes

Tools > CSV export downloads your recipes in the same format the import reads, so you can edit the file and re-import it. For the steps, see Exporting recipes from your store.

Troubleshooting

Missing recipe_title. A recipe title is required

Every row needs a value in recipe_title. Look for empty rows, or rows where the title cell was left blank.

Invalid status. Must be 0 (hidden) or 1 (published)

status accepts only 0 or 1. A blank cell counts as missing, so fill it in on every row.

Missing blog_id (required when "Create blog posts" is enabled)

With Create blog posts ticked, every row needs a blog_id. Fill it in on every row, or untick the option.

Invalid blog_id. Must be the gid:// form the CSV export writes

The cell holds something that isn't an ID. Use the gid form from an export (gid://shopify/Blog/106700000000), or the plain number from the end of your blog's address in Shopify admin (/admin/blogs/106700000000). The same applies to article_id.

Invalid blog_id. "1.067E+11" is what a spreadsheet writes when it converts a long ID to a number

Excel read the ID as a number and saved a shortened version, and the original can't be recovered from it, so the row is rejected before the recipe is created. To fix it, do one of these:

  • Download a fresh export. Its blog and blog post IDs are in the gid form, which spreadsheets don't change.
  • Format the blog_id and article_id columns as Text, then enter the IDs again.

Recipes imported before this check existed were saved without their blog post, with the warning "Saved, but no blog post was created". Re-import those rows with a correct ID and Update existing recipes ticked.

Invalid nutrition_data entry

An entry in nutrition_data is missing its colon, or has nothing before it. Each entry must read Title:Value, separated by semicolons:

Calories:250;Fat:12;Protein:3

The message quotes the entry it couldn't read, so search your file for that text.

Only one || delimiter allowed per ingredient (or per item)

An ingredient or a piece of equipment has more than one ||. Each can have only one link. Look for an accidental extra double pipe.

Custom field 1: custom_field_1_value needs a custom_field_1_key

The row has a value for custom field 1 but no key. Add the key, or clear the value. The same goes for custom field 2.

This file only has some recipe columns, so it can only update recipes that already exist

The file leaves columns out, and this row would create a new recipe. The message says which of these happened:

  • Update existing recipes is unticked. Tick it.
  • "No recipe with recipe_id ... was found" or "No recipe titled ... was found". Neither the row's recipe_id nor its title matched one of your recipes. Recipe Kit tries the title when the ID isn't found, so check the title's spelling too.

To create new recipes, use a file with every required column.

Calories don't import, or a row fails on calories

recipe_calories takes whole numbers from -32,768 to 32,767. Text, commas and decimals are ignored and the recipe imports with no calorie count. A number outside the range fails the row.

Missing recipe_ingredients. At least one ingredient is required

recipe_ingredients can't be empty. Add at least one ingredient.

Missing recipe_directions. At least one direction is required

recipe_directions can't be empty. Add at least one step.

Product links don't work after import

  • Check the handle. It's the last part of the product URL, like organic-flour from yourstore.com/products/organic-flour.
  • The product must exist in your store. A handle that matches nothing imports without a link.
  • Check the format is exactly shopify:handle after the ||, for example 2 cups flour||shopify:organic-flour. Don't put the full URL after shopify:.

Step images don't show after import

  • Check the positions in direction_images line up with recipe_directions, including headings, which take an empty position.
  • Both columns should have the same number of semicolons.
  • The image URLs must be public.

Images don't show after import

  • Check the image URLs open in a browser without a login.
  • A failed upload doesn't stop the recipe importing. The recipe imports without the image. Fix the URL and re-import, or add the image in the recipe editor.

The import is slow

Recipe Kit pauses briefly between recipes so it doesn't overload Shopify: about 150ms between recipes and 500ms after every 25. Image uploads add time to each recipe, so a file with thousands of recipes can take a while. You can leave the page. Come back to Tools > CSV import to see progress.

Odd characters after import

  • Save the file with UTF-8 encoding. In Excel, choose CSV UTF-8 (Comma delimited) when you save.
  • Wrap values that contain commas, double quotes or line breaks in double quotes. Most spreadsheet apps do this for you.

I stopped an import but some recipes were created

Stopping an import doesn't undo recipes that were already created or updated. Delete the ones you don't want from the Recipes page.

What's next

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.

Still need help? Contact Us Contact Us