Script for Recipe Websites

Structured Data Requirements

After you've added the happycart script to your pages, all recipes contained in the website will be parsed and made shoppable. The script can only parse recipes which have our custom script tag.

Custom json data for happycart

Since schema data doesn’t natively support ingredient groups, and most recipes rely on them, we require a custom-structured data format for parsing when crawling recipe pages. This allows us to efficiently match recipes during the initial crawl and track changes (like updates to images, amounts, units, or ingredients) when revisiting the page.

To achieve this, each ingredient group and ingredient must have a stable, unique ID that remains consistent across requests. If a new ingredient or group is added, that’s acceptable, but existing ones should retain their original IDs. In the event an ingredient is modified, our system will handle those changes accordingly.

Here are the typescript definitions for the json data:

interface RecipeData {
  recipe_id: string // In the form of {YOUR_CHANNEL_SLUG}-{YOUR_RECIPE_ID}. Replace `YOUR_RECIPE_ID` with a unique recipe id and replace `YOUR_CHANNEL_SLUG` with your channel slug provided by happycart.
  name: string // name of the recipe
  description: string // short description of the recipe
  image: string // URL to the image, ideally a high resolution image
  servings: number // integer
  serving_type: string // either "PORTION" or "WHOLE"
  instruction_groups?: RecipeInstructionGroupData[] // optional, if not provided, there are no instruction groups
  ingredient_groups: RecipeIngredientGroupData[]
}


interface RecipeInstructionGroupData {
  id: string // id of the group, which shouldn't change between requests, so we can identify the group and compare if it has changed
  name?: string // name of the group
  instructions: RecipeInstructionData[]
}


interface RecipeInstructionData {
  id: string // id of the instruction, which shouldn't change between requests, so we can identify the instruction and compare if it has changed
  text: string // text of the instruction
  image_url?: string // optional, image associated with the instruction
}


interface RecipeIngredientGroupData {
  id: string // id of the group, which shouldn't change between requests, so we can identify the group and compare if it has changed
  name?: string // name of the group
  ingredients: RecipeIngredientData[]
}


interface RecipeIngredientData {
  id: string // id of the recipe ingredient, which shouldn't change between requests, so we can identify the recipe ingredient and compare if it has changed
  amount: string // string
  unit: string // abbreviated unit (e.g. "g", "ml", "Stk.") - we can handle pretty much any unit string here, but we recommend using the abbreviated form
  prefix?: string // prefix of the ingredient (e.g. "halbierte, entsteinte")
  name: string // name of the ingredient (e.g. "Zwetschken")
  suffix?: string // suffix of the ingredient (e.g. "frisch")
  optional: boolean // if the ingredient is optional to use or not
}

Example:

<script id="happycart-data" type="application/json">
{
  "recipe_id": "happyplates-43015",
  "name": "Grilled Rib-Eye Steak mit Bratkartoffeln",
  "image": "https://assets.happyplates.dev/media/2367/happy-plates-rezept-grilled-rib-eye-steak-mit-bratkartoffeln-fleisch-43015.jpg",
  "servings": 4,
  "serving_type": "PORTION",
  "ingredient_groups": [
    {
      "id": "g-6982",
      "name": "",
      "ingredients": [
        {
          "id": "ri-49427",
          "amount": "1000",
          "unit": "g",
          "prefix": "",
          "name": "Rib Eye Steak",
          "suffix": ""
        },
        {
          "id": "ri-49428",
          "amount": "3",
          "unit": "g",
          "prefix": "",
          "name": "Rosmarin",
          "suffix": "frisch"
        },
        {
          "id": "ri-49429",
          "amount": "2",
          "unit": "EL",
          "prefix": "",
          "name": "Olivenöl",
          "suffix": ""
        },
        {
          "id": "ri-123123", // notice the different ID here. should be a unique id for the recipe ingredient and not the ingredients id!
          "amount": "1",
          "unit": "Schuss",
          "prefix": "",
          "name": "Olivenöl",
          "suffix": ""
        },
        {
          "id": "ri-49430",
          "amount": "1",
          "unit": "Prise",
          "prefix": "",
          "name": "Fleur de Sel",
          "suffix": ""
        },
        {
          "id": "ri-49431",
          "amount": "1",
          "unit": "Prise",
          "prefix": "",
          "name": "Pfeffer",
          "suffix": "frisch gemahlen"
        },
      ]
    },
    {
      "id": "g-6983",
      "name": "Bratkartoffeln ",
      "ingredients": [
        {
          "id": "ri-49432",
          "amount": "1000",
          "unit": "g",
          "prefix": "",
          "name": "Kartoffel",
          "suffix": "festkochend"
        },
        {
          "id": "ri-49433",
          "amount": "3",
          "unit": "g",
          "prefix": "",
          "name": "Rosmarin",
          "suffix": "frisch"
        },
        {
          "id": "ri-49434",
          "amount": "1",
          "unit": "Stk",
          "prefix": "",
          "name": "Zitrone",
          "suffix": "unbehandelt"
        },
        {
          "id": "ri-49435",
          "amount": "2",
          "unit": "Stk",
          "prefix": "",
          "name": "Knoblauchzehe",
          "suffix": ""
        },
        {
          "id": "ri-49436",
          "amount": "6",
          "unit": "EL",
          "prefix": "",
          "name": "Olivenöl",
          "suffix": ""
        },
        {
          "id": "ri-49437",
          "amount": "1",
          "unit": "TL",
          "prefix": "",
          "name": "Fleur de Sel",
          "suffix": ""
        }
      ]
    }
  ]
}
</script>
Previous
Script Installation