# Quick Start Guide

Welcome to Growcado — your new favorite way to show the right content to the right people (without losing your mind). In this quick start, we’re gonna create a recipe that can shapeshift depending on who’s looking at it.

Imagine this: You’ve got a tasty lasagna recipe. But some folks are into veggies 🌱, while others dream about cheese-pulling meaty goodness 🍖. With Growcado, you can create *one* recipe and serve the perfect version to each person. Neat, right?

## 👨‍🍳 What We’re Cooking

We’re building a **Recipe** content type, which lets us add all the tasty details about a dish. Then we’ll use Growcado's personalization magic to serve:

* A **vegan lasagna** to customers who prefer plant-based meals 🥬
* A **classic meat lasagna** to those who love the carnivore life 🐮

No forks required yet — we’re just prepping the ingredients.

## 📋 Prerequisites

Before we dive in, make sure you have these ready:

#### **Growcado Account & Studio Access**

You'll need access to [Growcado Studio](https://studio.growcado.io/) to create content types and set up audience segments.You'll \
**Don't have an account?** [Fill out our form](https://growcado.io/#join-form) or [send us an email](mailto:hello@growcado.io?subject=Growcado%20Account%20Request\&body=Hi%20Growcado%20team%2C%0A%0AI%27d%20like%20to%20request%20a%20Growcado%20account.%0A%0AName%3A%20%0ACompany%3A%20%0A%0AThanks%21) and we'll create one for you ASAP! 🚀

#### **Development Environment**

Any environment that can make HTTP requests — whether you're building web apps with React/Vue/Angular, mobile apps with React Native/Flutter, server-side projects with Node.js, or adding personalization to an existing project.

**All you need:** A way to consume APIs and handle JSON responses. That's pretty much everything these days! ✨

## 🧱 1- Content Type Structure (a.k.a. Our Recipe Blueprint)

Before we dive in, let’s clear something up: a **content type** is like a blueprint — it defines what kind of data you’ll store and how it’s structured. Think of it as the mold for your content. For example, a “Recipe” content type might have fields like name, image, and ingredients. Once you’ve got that set, you can start adding actual **content**, like a lasagna recipe, a chocolate cake recipe, or your grandma’s mystery stew. The content type doesn’t hold specific recipes — it just tells the system what every recipe *should* look like.

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2Fq4pNG0jVDWcgjAH9Aeiu%2Fimage.png?alt=media&#x26;token=059bec26-6cfd-4493-90ed-1fbf9190fae0" alt="" width="563"><figcaption></figcaption></figure>

Here’s how our recipe content is structured:

* `Name` (text): What are we cooking? “Lasagna”, obviously.
* `Image` (media): A delicious photo to make people drool.
* `Ingredients` (reference): A list of ingredients, like pasta, tomato sauce, or vegan cheese.

Inside each **Ingredient**, we’ve got:

* `Name` (text): Like "Tofu" or "Mozzarella".
* `Amount` (text): Maybe "200g" or “a sprinkle.”

This setup gives us superpowers — we can mix and match different ingredient sets (like meat vs. vegan), and plug them into the same recipe layout.

### 🛠️ Step-by-Step: Building the Content Types

Let’s roll up our sleeves and set up the content types we need: `Recipe` and `Ingredient`. You'll only have to do this once — think of it like prepping your pantry before the cooking starts.

#### 🧄 Step 1: Create the `Ingredient` content type

Head to the **Content Type Builder** and click **“+ New”**.

* **Model Name:** `Ingredient`
* **Description:**\
  \&#xNAN;*"Stores individual ingredients with their name and amount. Used inside recipes to build full ingredient lists."*
* Model Identifier: `ingredient` (automatically filled)

➡️ **Don’t forget to click Save** once you're done filling it out.

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2Fy8tX6Hg8sebABitNX26E%2Fcm-ing-st1.png?alt=media&#x26;token=b4f7d880-ca73-433a-86a1-673965d33b2b" alt="" width="563"><figcaption><p>Creating the <code>Ingredient</code> content type with name, description, and identifier.</p></figcaption></figure>

#### ✍️ Step 2: Add your first field — `Name`

Click **Add Field**, and you’ll see a list of field types.

* Choose the `Text` field
* Set **Field Name** to `Name`
* Mark it as required (because even the humblest of onions needs a name)

<div><figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FCbm75AaethpbpPx3RNsE%2Fcm-rec-st2.png?alt=media&#x26;token=4dfe2532-a4ba-4212-964c-cf97b16cbf78" alt=""><figcaption><p>Choosing the <code>Text</code> field type — the first field we’ll use.</p></figcaption></figure> <figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FYMj8UbmtHD855ka0y30S%2Fcm-rec-st3.png?alt=media&#x26;token=3eefbffd-a864-4f46-85e5-b933fee80a44" alt=""><figcaption><p>Configuring the <code>Name</code> field as a required text input.</p></figcaption></figure></div>

➡️ Once done, **click Save** to lock in the field.

#### 🧪 Step 3: Add the `Amount` field

Add another `Text` field called `Amount`.

* Example values: “200g”, “2 cups”, “a dash”
* Same flow as the previous one

➡️ Hit **Save** when finished.

#### 🥘 Step 4: Create the `Recipe` content type

With your ingredients ready, let’s create a new content type: `Recipe`.

* **Model Name:** `Recipe`
* **Description:**\
  \&#xNAN;*"Use this to create recipes with name, image, and a list of ingredients."*
* Model Identifier: `recipe`

➡️ Yep, you guessed it — **click Save**.

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FS4SAg9S6DOrIXKgKt1y8%2Fcm-rec-st1.png?alt=media&#x26;token=5c0c8d15-e3c8-4860-94d9-136b39f74476" alt="" width="563"><figcaption><p>Creating the <code>Recipe</code> content type with a description and model identifier.</p></figcaption></figure>

#### ✏️ Step 5: Add the `Name` field

Just like you did for Ingredient, add a `Text` field called `Name`.

#### 🖼️ Step 6: Add the `Image` field

Choose the `Media` field to add a photo to your recipe.

* **Field Name:** `Image`
* Yes, it’s required — people eat with their eyes first.

➡️ Click **Save** when you're done.

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FJndHtsuNxTFoRfwHLVhr%2Fcm-rec-st4.png?alt=media&#x26;token=61fa7017-f00d-4f6a-8301-87249ee19528" alt="" width="563"><figcaption><p>Adding an image field to upload a photo of the recipe.</p></figcaption></figure>

#### 🔗 Step 7: Link the ingredients

* Choose the `Reference` field
* Name it `Ingredients`
* Limit accepted content types to `Ingredient` so only real food shows up here

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FwA9QNkUb1GABaqJE4Cyf%2Fcm-rec-st5.png?alt=media&#x26;token=32e32e10-deb1-4be3-bc95-be205a3fb68f" alt="" width="563"><figcaption><p>Linking ingredients to the recipe using a reference field.</p></figcaption></figure>

#### ✅ Step 8: Final check

At the end, your `Recipe` should have:

* `Name` (Text)
* `Image` (Media)
* `Ingredients` (Reference to `Ingredient`)

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FWUKuNfFHW4e2wVcDHkBM%2Fcm-rec-st6.png?alt=media&#x26;token=e9263769-c4af-4bc1-9327-aa0b0e9f665c" alt="" width="563"><figcaption><p>Final structure of the <code>Recipe</code> content type with all fields in place.</p></figcaption></figure>

## 🥘 2- Creating the Content Entries

Now that your content types are set, it’s time to create some real entries — like filling your digital kitchen with actual food. We'll start with adding ingredients and then create the *classic* version of our Lasagna recipe (no personalization yet — we’re saving the fun stuff for the next round 😏).

#### 🧄 Step 1: Add Your Ingredients

Head to the **Content** section in the sidebar and select **Ingredient**. Click the big, beautiful **+ Add Entry** button.

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FYKfRDZTO5i76Iqm6zKBy%2Fcontent-list-empty.png?alt=media&#x26;token=1c7f198a-0b3d-4a75-90d9-a56240dd3784" alt="" width="563"><figcaption><p>No ingredients yet — let’s add some!</p></figcaption></figure>

Start with your first ingredient, for example:

* **Name:** Lasagna Noodles
* **Amount:** 9 sheets

Don’t forget to hit **Save & Publish** when done!

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FFxt3EIdmUtEhzFqijaoO%2Fcontent-ing-create.png?alt=media&#x26;token=5351c522-dbf0-4780-988a-409ecd6ad8c7" alt="" width="563"><figcaption><p>Creating a single ingredient entry.</p></figcaption></figure>

Repeat this process to add the rest:

* Tomato Sauce – 500ml
* Mozzarella Cheese – 200g
* Ground Beef – 300g
* Vegan Ricotta – 250g
* Vegan Mozzarella – 200g

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FmtIyMwqq5SQfjyz9dIyC%2Fcontent-ing-all.png?alt=media&#x26;token=9312c9e2-52f0-40ca-ad9f-9f0f7959c01c" alt="" width="563"><figcaption><p>All six ingredients published and ready to use.</p></figcaption></figure>

#### 🍽️ Step 2: Create the Lasagna Recipe

Now that we’ve stocked the pantry, it’s time to cook.

Head back to **Content**, switch to the **Recipe** tab, and hit **+ Add Entry**.

* **Name:** Lasagna
* **Image:** Upload a delicious photo of lasagna (bonus points if it makes your users hungry)

Here's a neat trick: we can change the auto-generated **Content ID** to something more readable — like `lasagna` — by clicking the **Content ID field in the right menu**. We did exactly that so it’s easier to reference this content later on.

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FgzOliNfTZwegNHWGg6Cp%2Fcontent-rec-create.png?alt=media&#x26;token=83b860d8-865d-4632-bd8b-6ebc4e13e575" alt="" width="563"><figcaption><p>Filling in the recipe details — name and image first</p></figcaption></figure>

#### Step 3: Add Ingredients to the Recipe

Scroll down to the **Ingredients** field and click **Add Existing Content**. This will open a list of all the ingredients you just created. Select the ones that belong in this version of the recipe.

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FdIyBBZMszQ5bp9MxPn3n%2Fcontent-rec-create-reference.png?alt=media&#x26;token=f979b3b0-672a-45b2-a3d1-e12f0b95a634" alt="" width="563"><figcaption></figcaption></figure>

➡️ Finally, click **Save & Publish**. Boom — you’ve got your first recipe online!

## 📡 3- Getting Your Content via API

Alright, your content is cooked, published, and ready to serve — but how do you *actually* get it into your app or website? That’s where Growcado’s API comes in.

When you’re viewing a content entry (like our **Lasagna** recipe), just look at the **sidebar on the right**. Under the **“Link”** section, you’ll find the full API URL already built for you — including your tenant, model, and identifier.

Copy it, paste it, call it. Boom. 🍝

Let’s take a look at how to fetch the classic Lasagna recipe using a simple GET request.

```bash
curl 'https://api.growcado.io/cms/tenant/{tenantId}/published/{modelIdentifier}/{identifier}'
```

<table><thead><tr><th width="172.97186279296875">Param</th><th width="85.3482666015625">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>tenantId</code></td><td>✅</td><td>Your project’s unique tenant ID</td></tr><tr><td><code>modelIdentifier</code></td><td>✅</td><td>The content type you’re fetching — for our case, <code>recipe</code></td></tr><tr><td><code>identifier</code></td><td>✅</td><td>The unique content ID you set earlier — in our case, <code>lasagna</code></td></tr></tbody></table>

#### 🔍 What You Get Back

When you call the API, here's exactly what the response looks like for our Lasagna recipe:\
Don't panic when you see this response — it might look like a lot at first glance, but it's actually super straightforward once you know what you're looking at.

{% code lineNumbers="true" %}

```json
{
  "id": "32226494780751473240104532705280",
  "modelIdentifier": "recipe",
  "identifier": "lasagna",
  "fields": [
    {
      "identifier": "name",
      "type": "TEXT",
      "value": ["Lasagna"]
    },
    {
      "identifier": "image", 
      "type": "MEDIA",
      "value": ["https://api.growcado.io/storage/api/184549109729170412"]
    },
    {
      "identifier": "ingredients",
      "type": "REFERENCE", 
      "value": [
        {
          "id": "32226492677280317212325595578368",
          "modelIdentifier": "ingredient",
          "identifier": "58a42cb8-48b1-42",
          "fields": [
            {
              "identifier": "name",
              "type": "TEXT", 
              "value": ["Mozzarella Cheese"]
            },
            {
              "identifier": "amount",
              "type": "TEXT",
              "value": ["200g"]
            }
          ]
        },
        {
          "id": "32226492453750652613221511331840",
          "modelIdentifier": "ingredient", 
          "identifier": "e105ac68-9b40-47",
          "fields": [
            {
              "identifier": "name",
              "type": "TEXT",
              "value": ["Ground Beef"]
            },
            {
              "identifier": "amount", 
              "type": "TEXT",
              "value": ["300g"]
            }
          ]
        },
        {
          "id": "32226491725652835616848097574912",
          "modelIdentifier": "ingredient",
          "identifier": "95270311-4f17-47", 
          "fields": [
            {
              "identifier": "name",
              "type": "TEXT",
              "value": ["Tomato Sauce"]
            },
            {
              "identifier": "amount",
              "type": "TEXT", 
              "value": ["500ml"]
            }
          ]
        },
        {
          "id": "32226484768957203194547015778304",
          "modelIdentifier": "ingredient",
          "identifier": "bb4b4597-a34a-49",
          "fields": [
            {
              "identifier": "name", 
              "type": "TEXT",
              "value": ["Lasagna Noodles"]
            },
            {
              "identifier": "amount",
              "type": "TEXT",
              "value": ["9 sheets"]
            }
          ]
        }
      ]
    }
  ]
}
```

{% endcode %}

#### 🧠 Understanding the Response Structure

Notice how the response is structured:

* **Top level:** Contains the main content metadata (id, modelIdentifier, identifier)
* **Fields array:** Each field has an identifier, type, and value array
* **Reference fields:** When you reference other content (like ingredients), the full nested objects come back automatically
* **Values are arrays:** Even single values come wrapped in arrays — this keeps the structure consistent

### 💻  Quick Integration Example

{% tabs %}
{% tab title="Javascript" %}

```javascript
async function fetchRecipe(recipeId) {
  const response = await fetch(`https://api.growcado.io/cms/tenant/YOUR_TENANT_ID/published/recipe/${recipeId}`);
  const recipe = await response.json();
  
  // Get the recipe name
  const name = recipe.fields.find(f => f.identifier === 'name').value[0];
  
  // Get the image URL
  const imageUrl = recipe.fields.find(f => f.identifier === 'image').value[0];
  
  // Parse ingredients with their name and amount
  const ingredientsData = recipe.fields.find(f => f.identifier === 'ingredients').value;
  const ingredients = ingredientsData.map(ingredient => {
    const name = ingredient.fields.find(f => f.identifier === 'name').value[0];
    const amount = ingredient.fields.find(f => f.identifier === 'amount').value[0];
    return { name, amount };
  });
  
  return { name, imageUrl, ingredients };
}

// Example usage:
fetchRecipe('lasagna').then(recipe => {
  console.log(recipe.name); // "Lasagna"
  console.log(recipe.ingredients); 
  // [
  //   { name: "Mozzarella Cheese", amount: "200g" },
  //   { name: "Ground Beef", amount: "300g" },
  //   { name: "Tomato Sauce", amount: "500ml" },
  //   { name: "Lasagna Noodles", amount: "9 sheets" }
  // ]
});
```

{% endtab %}

{% tab title="Swift (iOS)" %}

```swift
import Foundation

struct Ingredient {
    let name: String
    let amount: String
}

struct Recipe {
    let name: String
    let imageUrl: String
    let ingredients: [Ingredient]
}

func fetchRecipe(recipeId: String) async throws -> Recipe {
    let url = URL(string: "https://api.growcado.io/cms/tenant/YOUR_TENANT_ID/published/recipe/\(recipeId)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
    
    let fields = json["fields"] as! [[String: Any]]
    
    // Get recipe name
    let nameField = fields.first { $0["identifier"] as! String == "name" }!
    let name = (nameField["value"] as! [String])[0]
    
    // Get image URL
    let imageField = fields.first { $0["identifier"] as! String == "image" }!
    let imageUrl = (imageField["value"] as! [String])[0]
    
    // Parse ingredients
    let ingredientsField = fields.first { $0["identifier"] as! String == "ingredients" }!
    let ingredientsData = ingredientsField["value"] as! [[String: Any]]
    
    let ingredients = ingredientsData.map { ingredientData in
        let fields = ingredientData["fields"] as! [[String: Any]]
        let nameField = fields.first { $0["identifier"] as! String == "name" }!
        let amountField = fields.first { $0["identifier"] as! String == "amount" }!
        
        return Ingredient(
            name: (nameField["value"] as! [String])[0],
            amount: (amountField["value"] as! [String])[0]
        )
    }
    
    return Recipe(name: name, imageUrl: imageUrl, ingredients: ingredients)
}

// Example usage:
Task {
    let recipe = try await fetchRecipe(recipeId: "lasagna")
    print(recipe.name) // "Lasagna"
    print(recipe.ingredients)
    // [Ingredient(name: "Mozzarella Cheese", amount: "200g"), ...]
}
```

{% endtab %}

{% tab title="Kotlin (Android)" %}

```kotlin
import kotlinx.coroutines.*
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL

data class Ingredient(
    val name: String,
    val amount: String
)

data class Recipe(
    val name: String,
    val imageUrl: String,
    val ingredients: List<Ingredient>
)

suspend fun fetchRecipe(recipeId: String): Recipe = withContext(Dispatchers.IO) {
    val url = URL("https://api.growcado.io/cms/tenant/YOUR_TENANT_ID/published/recipe/$recipeId")
    val connection = url.openConnection() as HttpURLConnection
    val response = connection.inputStream.bufferedReader().readText()
    val json = JSONObject(response)
    
    val fields = json.getJSONArray("fields")
    
    // Get recipe name
    val nameField = (0 until fields.length()).map { fields.getJSONObject(it) }
        .first { it.getString("identifier") == "name" }
    val name = nameField.getJSONArray("value").getString(0)
    
    // Get image URL
    val imageField = (0 until fields.length()).map { fields.getJSONObject(it) }
        .first { it.getString("identifier") == "image" }
    val imageUrl = imageField.getJSONArray("value").getString(0)
    
    // Parse ingredients
    val ingredientsField = (0 until fields.length()).map { fields.getJSONObject(it) }
        .first { it.getString("identifier") == "ingredients" }
    val ingredientsData = ingredientsField.getJSONArray("value")
    
    val ingredients = (0 until ingredientsData.length()).map { i ->
        val ingredient = ingredientsData.getJSONObject(i)
        val ingredientFields = ingredient.getJSONArray("fields")
        
        val nameField = (0 until ingredientFields.length()).map { ingredientFields.getJSONObject(it) }
            .first { it.getString("identifier") == "name" }
        val amountField = (0 until ingredientFields.length()).map { ingredientFields.getJSONObject(it) }
            .first { it.getString("identifier") == "amount" }
        
        Ingredient(
            name = nameField.getJSONArray("value").getString(0),
            amount = amountField.getJSONArray("value").getString(0)
        )
    }
    
    Recipe(name, imageUrl, ingredients)
}

// Example usage:
runBlocking {
    val recipe = fetchRecipe("lasagna")
    println(recipe.name) // "Lasagna"
    println(recipe.ingredients)
    // [Ingredient(name=Mozzarella Cheese, amount=200g), ...]
}
```

{% endtab %}

{% tab title="Python" %}

```python
import requests
from typing import List, Dict, Any

def fetch_recipe(recipe_id: str) -> Dict[str, Any]:
    url = f"https://api.growcado.io/cms/tenant/YOUR_TENANT_ID/published/recipe/{recipe_id}"
    response = requests.get(url)
    data = response.json()
    
    fields = data["fields"]
    
    # Get recipe name
    name_field = next(f for f in fields if f["identifier"] == "name")
    name = name_field["value"][0]
    
    # Get image URL
    image_field = next(f for f in fields if f["identifier"] == "image")
    image_url = image_field["value"][0]
    
    # Parse ingredients
    ingredients_field = next(f for f in fields if f["identifier"] == "ingredients")
    ingredients_data = ingredients_field["value"]
    
    ingredients = []
    for ingredient_data in ingredients_data:
        fields = ingredient_data["fields"]
        name_field = next(f for f in fields if f["identifier"] == "name")
        amount_field = next(f for f in fields if f["identifier"] == "amount")
        
        ingredients.append({
            "name": name_field["value"][0],
            "amount": amount_field["value"][0]
        })
    
    return {
        "name": name,
        "image_url": image_url,
        "ingredients": ingredients
    }

# Example usage:
recipe = fetch_recipe("lasagna")
print(recipe["name"])  # "Lasagna"
print(recipe["ingredients"])
# [
#   {"name": "Mozzarella Cheese", "amount": "200g"},
#   {"name": "Ground Beef", "amount": "300g"},
#   ...
# ]
```

{% endtab %}

{% tab title="Java" %}

```java
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.util.*;
import com.fasterxml.jackson.databind.ObjectMapper;

public class GrowcadoClient {
    
    public static class Ingredient {
        public String name;
        public String amount;
        
        public Ingredient(String name, String amount) {
            this.name = name;
            this.amount = amount;
        }
    }
    
    public static class Recipe {
        public String name;
        public String imageUrl;
        public List<Ingredient> ingredients;
        
        public Recipe(String name, String imageUrl, List<Ingredient> ingredients) {
            this.name = name;
            this.imageUrl = imageUrl;
            this.ingredients = ingredients;
        }
    }
    
    public static Recipe fetchRecipe(String recipeId) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.growcado.io/cms/tenant/YOUR_TENANT_ID/published/recipe/" + recipeId))
            .build();
        
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> data = mapper.readValue(response.body(), Map.class);
        
        List<Map<String, Object>> fields = (List<Map<String, Object>>) data.get("fields");
        
        // Get recipe name
        Map<String, Object> nameField = fields.stream()
            .filter(f -> "name".equals(f.get("identifier")))
            .findFirst().orElseThrow();
        String name = ((List<String>) nameField.get("value")).get(0);
        
        // Get image URL
        Map<String, Object> imageField = fields.stream()
            .filter(f -> "image".equals(f.get("identifier")))
            .findFirst().orElseThrow();
        String imageUrl = ((List<String>) imageField.get("value")).get(0);
        
        // Parse ingredients
        Map<String, Object> ingredientsField = fields.stream()
            .filter(f -> "ingredients".equals(f.get("identifier")))
            .findFirst().orElseThrow();
        List<Map<String, Object>> ingredientsData = (List<Map<String, Object>>) ingredientsField.get("value");
        
        List<Ingredient> ingredients = new ArrayList<>();
        for (Map<String, Object> ingredientData : ingredientsData) {
            List<Map<String, Object>> ingredientFields = (List<Map<String, Object>>) ingredientData.get("fields");
            
            Map<String, Object> nameField2 = ingredientFields.stream()
                .filter(f -> "name".equals(f.get("identifier")))
                .findFirst().orElseThrow();
            Map<String, Object> amountField = ingredientFields.stream()
                .filter(f -> "amount".equals(f.get("identifier")))
                .findFirst().orElseThrow();
            
            ingredients.add(new Ingredient(
                ((List<String>) nameField2.get("value")).get(0),
                ((List<String>) amountField.get("value")).get(0)
            ));
        }
        
        return new Recipe(name, imageUrl, ingredients);
    }
    
    // Example usage:
    public static void main(String[] args) throws Exception {
        Recipe recipe = fetchRecipe("lasagna");
        System.out.println(recipe.name); // "Lasagna"
        System.out.println(recipe.ingredients);
        // [Ingredient(name=Mozzarella Cheese, amount=200g), ...]
    }
}
```

{% endtab %}
{% endtabs %}

## 🎯 4- Time for the Magic: Adding Personalization

Now comes the fun part! Remember how we promised you could serve vegan lasagna to plant-lovers and meaty goodness to carnivores? Let's make that happen.

The secret sauce is **audience segments** — these let you define rules that automatically sort your visitors into groups based on how they found you, what device they're using, or other criteria. When someone visits your site, Growcado checks if they match any of your segment rules and serves them the right content version.

Here's what we are going to build: if someone clicks a link from a vegan blog to reach your lasagna recipe, they'll automatically see the plant-based ingredient list. Everyone else gets the classic meat-and-cheese version.

Ready to get personal? Let's cook! 👨‍🍳

### **👥** Create Your First Audience

We're going to start by creating a segment for visitors who come from vegan websites. These are folks who probably found your site through vegan recipe blogs, plant-based cooking sites, or health-focused referrers.

#### 🏷️ Setting Up the "Vegans" Segment

Head over to the **Audience** section in your sidebar and click **+ Add Segment**.

You'll see a friendly 2-step process:

**Step 1: Metadata (Set name and description)**

* **Segment Name**: `Plant-Based Visitors`
* **Description**: `Visitors who arrive from vegan recipe blogs and plant-based websites`

Keep the description clear — future you (and your team) will thank you when you have 20+ segments!

➡️ Hit **Next** when you're ready to get specific.

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FAJQl1GQbx5LzlBtYDmcs%2Fstudio.growcado.io_main_audience_edit_id%3D200958423125315764(HighRes%20Screenshot).png?alt=media&#x26;token=e458a844-ca1b-41bb-a1ea-90d82950c6f4" alt=""><figcaption></figcaption></figure>

#### 🎯 Define Who Belongs

Now for the targeting magic! This is where you tell Growcado exactly who should be considered a "Vegan" visitor.

In the **Context** section, you'll build rules like this:

```
Who [are] [visiting through a] [UTM Source] [vegan.shop]
```

Here's what this means in plain English:

* **Who are**: We're defining people who match certain criteria
* **visiting through a**: They came to your site via a specific channel
* **UTM Source**: The tracking parameter that shows where they came from
* **vegan.shop**: The specific source value (like a vegan recipe blog or plant-based store)

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FNbWMXMbDmQYP31nPbJGK%2Fstudio.growcado.io_main_audience_edit_id%3D200958423125315764(HighRes%20Screenshot)%20(1).png?alt=media&#x26;token=d79f14a3-e854-404d-8a4a-1e3850a90ab2" alt=""><figcaption></figcaption></figure>

#### 🔗 Real-World Example

Let's say you partner with a popular vegan blog called "PlantPowerRecipes.com". When they link to your lasagna recipe, the URL might look like:

```
https://yoursite.com/recipes/lasagna?utm_source=vegan.shop
```

Anyone clicking that link gets automatically tagged as part of your "Plant-Based Visitors" segment. Smart, right?

### 🪄 Creating Content Variants

Now that you've got your "Vegans" audience segment set up, it's time for the real magic — creating different versions of your lasagna recipe that automatically show up for different people.

Think of variants like having multiple recipe cards for the same dish. Your default recipe has ground beef and mozzarella, but your vegan variant swaps those out for plant-based alternatives. Same delicious lasagna, different ingredients based on who's looking!

#### 🔄 Step 1: Create a Vegan Variant

Head back to your Lasagna recipe in the Content section. You'll see your current ingredient list with all the meaty, cheesy goodness. At the bottom, there's a magical little button called **+ Add Variant**.

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FlrxoKYw2S2rM42Kc2BQ7%2Fstudio.growcado.io_main_content_edit_content_id%3D32226494780751473240104532705280%26step%3Dcontent_field(HighRes%20Screenshot).png?alt=media&#x26;token=0d3cfe89-e110-418a-a9c7-15723dee6e03" alt=""><figcaption></figcaption></figure>

Click that button — it's time to create some personalized magic! ✨

#### 🎯 Step 2: Choose Your Target Audience

A modal will pop up with a simple 2-step process:

First, choose your content variant (vegan ingridients in this case)

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FlwAC9zifwflujf5FL1zw%2Fstudio.growcado.io_main_content_edit_content_id%3D32226494780751473240104532705280%26step%3Dcontent_field(HighRes%20Screenshot)%20(1).png?alt=media&#x26;token=9cc6bbd2-c367-4f77-88f9-4ecdb2c473c9" alt=""><figcaption></figcaption></figure>

Second, Choose Segment, this is where you pick who gets this variant

You'll see your "Vegans" segment right there, complete with the description you wrote earlier: *"Visitors who arrive from vegan recipe sites and plant-based cooking blogs"*

Select the **Vegans** segment and hit **Save**.

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2Fj7t0XA28XSDFPOPuwixS%2Fstudio.growcado.io_main_content_edit_content_id%3D32226494780751473240104532705280%26step%3Dcontent_field(HighRes%20Screenshot)%20(2).png?alt=media&#x26;token=bed16079-41a6-4992-a0ea-c18f14d093bc" alt=""><figcaption></figcaption></figure>

Beautiful! Now you've got a dedicated vegan version. Notice how Growcado has cleverly organized everything:

<figure><img src="https://2954878336-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXRgdB807IY7a5OGV3UeL%2Fuploads%2FCHPtoB44H6t3CW1skzc2%2Fstudio.growcado.io_main_content_edit_content_id%3D32226494780751473240104532705280%26step%3Dcontent_field(HighRes%20Screenshot)%20(3).png?alt=media&#x26;token=36eade64-3235-4cd5-95af-bd72401d5206" alt=""><figcaption></figcaption></figure>

**Variant 1: 👥 Vegans** This is where you added the plant-based ingredients.&#x20;

* Vegan Mozzarella
* Vegan Ricotta
* Tomato Sauce (this one stays the same!)
* Lasagna Noodles (also the same)

**Default value: 🌐 For other users**\
This shows your original ingredients that everyone else will see:

* Mozzarella Cheese
* Ground Beef
* Tomato Sauce
* Lasagna Noodles

#### 🎉 What Just Happened?

You've just created **personalized content**! Here's how it works:

* **Vegan blog visitor** clicks a link with `utm_source=vegan.shop` → Gets the plant-based ingredient list
* **Everyone else** visits normally → Gets the classic meat-and-cheese version
* **Same recipe, same API call** → Growcado automatically serves the right version

#### 🚀 Test It Out

Let's go back to our integration code from earlier and add personalization to it! Remember those multi-language examples? Now they can automatically serve different content:

{% tabs %}
{% tab title="Javascript" %}

```javascript
async function fetchRecipe(recipeId, userContext = {}) {
  const headers = {};
  
  // Add UTM header based on user context (referrer, user preferences, etc.)
  if (userContext.utmSource) {
    const utmParams = new URLSearchParams();
    if (userContext.utmSource) utmParams.append('source', userContext.utmSource);
    if (userContext.utmCampaign) utmParams.append('campaign', userContext.utmCampaign);
    if (userContext.utmMedium) utmParams.append('medium', userContext.utmMedium);
    
    headers['X-UTM'] = utmParams.toString();
  }
  
  const response = await fetch(`https://api.growcado.io/cms/tenant/YOUR_TENANT_ID/published/recipe/${recipeId}`, {
    headers
  });
  const recipe = await response.json();
  
  // Get the recipe name
  const name = recipe.fields.find(f => f.identifier === 'name').value[0];
  
  // Get the image URL
  const imageUrl = recipe.fields.find(f => f.identifier === 'image').value[0];
  
  // Parse ingredients with their name and amount
  const ingredientsData = recipe.fields.find(f => f.identifier === 'ingredients').value;
  const ingredients = ingredientsData.map(ingredient => {
    const name = ingredient.fields.find(f => f.identifier === 'name').value[0];
    const amount = ingredient.fields.find(f => f.identifier === 'amount').value[0];
    return { name, amount };
  });
  
  return { name, imageUrl, ingredients };
}

// Real-world usage examples:

// 1. Regular visitor (gets default version)
const recipe = await fetchRecipe('lasagna');
console.log('Default ingredients:', recipe.ingredients);

// 2. User came from a vegan blog
const veganUser = {
  utmSource: 'vegan.shop',
  utmCampaign: 'recipe-share',
  utmMedium: 'blog'
};
const veganRecipe = await fetchRecipe('lasagna', veganUser);
console.log('Vegan ingredients:', veganRecipe.ingredients);

// 3. Detect from current page URL parameters
const urlParams = new URLSearchParams(window.location.search);
const currentUserContext = {
  utmSource: urlParams.get('utm_source'),
  utmCampaign: urlParams.get('utm_campaign'),
  utmMedium: urlParams.get('utm_medium')
};
const contextualRecipe = await fetchRecipe('lasagna', currentUserContext);
```

{% endtab %}

{% tab title="Swift (iOS)" %}

```swift
import Foundation

struct Ingredient {
    let name: String
    let amount: String
}

struct Recipe {
    let name: String
    let imageUrl: String
    let ingredients: [Ingredient]
}

struct UserContext {
    let utmSource: String?
    let utmCampaign: String?
    let utmMedium: String?
}

func fetchRecipe(recipeId: String, userContext: UserContext = UserContext(utmSource: nil, utmCampaign: nil, utmMedium: nil)) async throws -> Recipe {
    var request = URLRequest(url: URL(string: "https://api.growcado.io/cms/tenant/YOUR_TENANT_ID/published/recipe/\(recipeId)")!)
    
    // Add UTM header based on user context
    if let utmSource = userContext.utmSource {
        var utmParams = [String]()
        utmParams.append("source=\(utmSource)")
        if let campaign = userContext.utmCampaign { utmParams.append("campaign=\(campaign)") }
        if let medium = userContext.utmMedium { utmParams.append("medium=\(medium)") }
        
        request.setValue(utmParams.joined(separator: "&"), forHTTPHeaderField: "X-UTM")
    }
    
    let (data, _) = try await URLSession.shared.data(for: request)
    let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
    
    let fields = json["fields"] as! [[String: Any]]
    
    // Get recipe name
    let nameField = fields.first { $0["identifier"] as! String == "name" }!
    let name = (nameField["value"] as! [String])[0]
    
    // Get image URL
    let imageField = fields.first { $0["identifier"] as! String == "image" }!
    let imageUrl = (imageField["value"] as! [String])[0]
    
    // Parse ingredients
    let ingredientsField = fields.first { $0["identifier"] as! String == "ingredients" }!
    let ingredientsData = ingredientsField["value"] as! [[String: Any]]
    
    let ingredients = ingredientsData.map { ingredientData in
        let fields = ingredientData["fields"] as! [[String: Any]]
        let nameField = fields.first { $0["identifier"] as! String == "name" }!
        let amountField = fields.first { $0["identifier"] as! String == "amount" }!
        
        return Ingredient(
            name: (nameField["value"] as! [String])[0],
            amount: (amountField["value"] as! [String])[0]
        )
    }
    
    return Recipe(name: name, imageUrl: imageUrl, ingredients: ingredients)
}

// Real-world usage examples:
Task {
    // 1. Regular visitor (gets default version)
    let defaultRecipe = try await fetchRecipe(recipeId: "lasagna")
    print("Default ingredients: \(defaultRecipe.ingredients)")
    
    // 2. User came from a vegan blog
    let veganContext = UserContext(
        utmSource: "vegan.shop",
        utmCampaign: "recipe-share", 
        utmMedium: "blog"
    )
    let veganRecipe = try await fetchRecipe(recipeId: "lasagna", userContext: veganContext)
    print("Vegan ingredients: \(veganRecipe.ingredients)")
    
    // 3. User from health & wellness site
    let healthContext = UserContext(utmSource: "wellness.com", utmCampaign: "healthy-recipes", utmMedium: "newsletter")
    let healthRecipe = try await fetchRecipe(recipeId: "lasagna", userContext: healthContext)
    print("Health-focused ingredients: \(healthRecipe.ingredients)")
}
```

{% endtab %}

{% tab title="Kotlin (Android)" %}

```kotlin
import kotlinx.coroutines.*
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL

data class Ingredient(
    val name: String,
    val amount: String
)

data class Recipe(
    val name: String,
    val imageUrl: String,
    val ingredients: List<Ingredient>
)

data class UserContext(
    val utmSource: String? = null,
    val utmCampaign: String? = null,
    val utmMedium: String? = null
)

suspend fun fetchRecipe(recipeId: String, userContext: UserContext = UserContext()): Recipe = withContext(Dispatchers.IO) {
    val url = URL("https://api.growcado.io/cms/tenant/YOUR_TENANT_ID/published/recipe/$recipeId")
    val connection = url.openConnection() as HttpURLConnection
    
    // Add UTM header based on user context
    userContext.utmSource?.let { source ->
        val utmParams = mutableListOf("source=$source")
        userContext.utmCampaign?.let { utmParams.add("campaign=$it") }
        userContext.utmMedium?.let { utmParams.add("medium=$it") }
        
        connection.setRequestProperty("X-UTM", utmParams.joinToString("&"))
    }
    
    val response = connection.inputStream.bufferedReader().readText()
    val json = JSONObject(response)
    
    val fields = json.getJSONArray("fields")
    
    // Get recipe name
    val nameField = (0 until fields.length()).map { fields.getJSONObject(it) }
        .first { it.getString("identifier") == "name" }
    val name = nameField.getJSONArray("value").getString(0)
    
    // Get image URL
    val imageField = (0 until fields.length()).map { fields.getJSONObject(it) }
        .first { it.getString("identifier") == "image" }
    val imageUrl = imageField.getJSONArray("value").getString(0)
    
    // Parse ingredients
    val ingredientsField = (0 until fields.length()).map { fields.getJSONObject(it) }
        .first { it.getString("identifier") == "ingredients" }
    val ingredientsData = ingredientsField.getJSONArray("value")
    
    val ingredients = (0 until ingredientsData.length()).map { i ->
        val ingredient = ingredientsData.getJSONObject(i)
        val ingredientFields = ingredient.getJSONArray("fields")
        
        val nameField = (0 until ingredientFields.length()).map { ingredientFields.getJSONObject(it) }
            .first { it.getString("identifier") == "name" }
        val amountField = (0 until ingredientFields.length()).map { ingredientFields.getJSONObject(it) }
            .first { it.getString("identifier") == "amount" }
        
        Ingredient(
            name = nameField.getJSONArray("value").getString(0),
            amount = amountField.getJSONArray("value").getString(0)
        )
    }
    
    Recipe(name, imageUrl, ingredients)
}

// Real-world usage examples:
runBlocking {
    // 1. Regular visitor (gets default version)
    val defaultRecipe = fetchRecipe("lasagna")
    println("Default ingredients: ${defaultRecipe.ingredients}")
    
    // 2. User came from a vegan blog  
    val veganContext = UserContext(
        utmSource = "vegan.shop",
        utmCampaign = "recipe-share",
        utmMedium = "blog"
    )
    val veganRecipe = fetchRecipe("lasagna", veganContext)
    println("Vegan ingredients: ${veganRecipe.ingredients}")
    
    // 3. Track user journey from analytics
    val analyticsContext = UserContext(
        utmSource = getAnalyticsReferrer(), // Your analytics function
        utmCampaign = getCurrentCampaign(),
        utmMedium = "social"
    )
    val personalizedRecipe = fetchRecipe("lasagna", analyticsContext)
    println("Personalized ingredients: ${personalizedRecipe.ingredients}")
}
```

{% endtab %}

{% tab title="Python" %}

```python
import requests
from typing import List, Dict, Any, Optional

class UserContext:
    def __init__(self, utm_source: Optional[str] = None, utm_campaign: Optional[str] = None, utm_medium: Optional[str] = None):
        self.utm_source = utm_source
        self.utm_campaign = utm_campaign
        self.utm_medium = utm_medium

def fetch_recipe(recipe_id: str, user_context: UserContext = None) -> Dict[str, Any]:
    url = f"https://api.growcado.io/cms/tenant/YOUR_TENANT_ID/published/recipe/{recipe_id}"
    headers = {}
    
    # Add UTM header based on user context
    if user_context and user_context.utm_source:
        utm_params = [f"source={user_context.utm_source}"]
        if user_context.utm_campaign:
            utm_params.append(f"campaign={user_context.utm_campaign}")
        if user_context.utm_medium:
            utm_params.append(f"medium={user_context.utm_medium}")
        
        headers['X-UTM'] = '&'.join(utm_params)
    
    response = requests.get(url, headers=headers)
    data = response.json()
    
    fields = data["fields"]
    
    # Get recipe name
    name_field = next(f for f in fields if f["identifier"] == "name")
    name = name_field["value"][0]
    
    # Get image URL
    image_field = next(f for f in fields if f["identifier"] == "image")
    image_url = image_field["value"][0]
    
    # Parse ingredients
    ingredients_field = next(f for f in fields if f["identifier"] == "ingredients")
    ingredients_data = ingredients_field["value"]
    
    ingredients = []
    for ingredient_data in ingredients_data:
        fields = ingredient_data["fields"]
        name_field = next(f for f in fields if f["identifier"] == "name")
        amount_field = next(f for f in fields if f["identifier"] == "amount")
        
        ingredients.append({
            "name": name_field["value"][0],
            "amount": amount_field["value"][0]
        })
    
    return {
        "name": name,
        "image_url": image_url,
        "ingredients": ingredients
    }

# Real-world usage examples:

# 1. Regular visitor (gets default version)
default_recipe = fetch_recipe("lasagna")
print("Default ingredients:", default_recipe["ingredients"])

# 2. User came from a vegan blog
vegan_context = UserContext(
    utm_source="vegan.shop",
    utm_campaign="recipe-share",
    utm_medium="blog"
)
vegan_recipe = fetch_recipe("lasagna", vegan_context)
print("Vegan ingredients:", vegan_recipe["ingredients"])

# 3. Detect from Flask request (web framework example)
# from flask import request
# def get_user_context_from_request():
#     return UserContext(
#         utm_source=request.args.get('utm_source'),
#         utm_campaign=request.args.get('utm_campaign'),
#         utm_medium=request.args.get('utm_medium')
#     )
# 
# contextual_recipe = fetch_recipe("lasagna", get_user_context_from_request())
```

{% endtab %}

{% tab title="Java" %}

```java
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;

public class GrowcadoClient {
    
    public static class Ingredient {
        public String name;
        public String amount;
        
        public Ingredient(String name, String amount) {
            this.name = name;
            this.amount = amount;
        }
        
        @Override
        public String toString() {
            return "Ingredient(name=" + name + ", amount=" + amount + ")";
        }
    }
    
    public static class Recipe {
        public String name;
        public String imageUrl;
        public List<Ingredient> ingredients;
        
        public Recipe(String name, String imageUrl, List<Ingredient> ingredients) {
            this.name = name;
            this.imageUrl = imageUrl;
            this.ingredients = ingredients;
        }
    }
    
    public static class UserContext {
        public String utmSource;
        public String utmCampaign;
        public String utmMedium;
        
        public UserContext() {}
        
        public UserContext(String utmSource, String utmCampaign, String utmMedium) {
            this.utmSource = utmSource;
            this.utmCampaign = utmCampaign;
            this.utmMedium = utmMedium;
        }
    }
    
    public static Recipe fetchRecipe(String recipeId, UserContext userContext) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
            .uri(URI.create("https://api.growcado.io/cms/tenant/YOUR_TENANT_ID/published/recipe/" + recipeId));
        
        // Add UTM header based on user context
        if (userContext != null && userContext.utmSource != null) {
            List<String> utmParams = new ArrayList<>();
            utmParams.add("source=" + userContext.utmSource);
            if (userContext.utmCampaign != null) utmParams.add("campaign=" + userContext.utmCampaign);
            if (userContext.utmMedium != null) utmParams.add("medium=" + userContext.utmMedium);
            
            requestBuilder.header("X-UTM", String.join("&", utmParams));
        }
        
        HttpRequest request = requestBuilder.build();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> data = mapper.readValue(response.body(), Map.class);
        
        List<Map<String, Object>> fields = (List<Map<String, Object>>) data.get("fields");
        
        // Get recipe name
        Map<String, Object> nameField = fields.stream()
            .filter(f -> "name".equals(f.get("identifier")))
            .findFirst().orElseThrow();
        String name = ((List<String>) nameField.get("value")).get(0);
        
        // Get image URL
        Map<String, Object> imageField = fields.stream()
            .filter(f -> "image".equals(f.get("identifier")))
            .findFirst().orElseThrow();
        String imageUrl = ((List<String>) imageField.get("value")).get(0);
        
        // Parse ingredients
        Map<String, Object> ingredientsField = fields.stream()
            .filter(f -> "ingredients".equals(f.get("identifier")))
            .findFirst().orElseThrow();
        List<Map<String, Object>> ingredientsData = (List<Map<String, Object>>) ingredientsField.get("value");
        
        List<Ingredient> ingredients = new ArrayList<>();
        for (Map<String, Object> ingredientData : ingredientsData) {
            List<Map<String, Object>> ingredientFields = (List<Map<String, Object>>) ingredientData.get("fields");
            
            Map<String, Object> nameField2 = ingredientFields.stream()
                .filter(f -> "name".equals(f.get("identifier")))
                .findFirst().orElseThrow();
            Map<String, Object> amountField = ingredientFields.stream()
                .filter(f -> "amount".equals(f.get("identifier")))
                .findFirst().orElseThrow();
            
            ingredients.add(new Ingredient(
                ((List<String>) nameField2.get("value")).get(0),
                ((List<String>) amountField.get("value")).get(0)
            ));
        }
        
        return new Recipe(name, imageUrl, ingredients);
    }
    
    // Convenience method for default version
    public static Recipe fetchRecipe(String recipeId) throws Exception {
        return fetchRecipe(recipeId, null);
    }
    
    // Real-world usage examples:
    public static void main(String[] args) throws Exception {
        // 1. Regular visitor (gets default version)
        Recipe defaultRecipe = fetchRecipe("lasagna");
        System.out.println("Default ingredients: " + defaultRecipe.ingredients);
        
        // 2. User came from a vegan blog
        UserContext veganContext = new UserContext("vegan.shop", "recipe-share", "blog");
        Recipe veganRecipe = fetchRecipe("lasagna", veganContext);
        System.out.println("Vegan ingredients: " + veganRecipe.ingredients);
        
        // 3. Spring Boot controller example
        // @GetMapping("/recipe/{id}")
        // public Recipe getRecipe(@PathVariable String id, HttpServletRequest request) {
        //     UserContext context = new UserContext(
        //         request.getParameter("utm_source"),
        //         request.getParameter("utm_campaign"),
        //         request.getParameter("utm_medium")
        //     );
        //     return fetchRecipe(id, context);
        // }
    }
}
```

{% endtab %}
{% endtabs %}

## 🌟 You've Just Scratched the Surface

Congratulations! You've just built your first personalized content system that automatically detects vegan-interested visitors and serves appropriate content without any code changes.

But here's where it gets *really* exciting — **what we just did was the simplest possible example**. We used basic UTM source detection to keep this intro digestible, but Growcado's audience targeting is incredibly sophisticated. You can create mind-blowingly precise segments using three powerful categories:

#### 🎯 **Customer Context** (What we just used)

Beyond UTM parameters, target users based on:

* **Location**: Show regional ingredients ("Show tikka masala spices to users in the UK")
* **Time intelligence**: Different content for breakfast vs dinner hours, holidays vs regular days
* **Language & locale**: Automatic localization without managing separate content
* **Custom headers**: Send any data from your app to trigger personalization

#### 👤 **User Attributes** (The real power)

Sync rich user data from anywhere and personalize based on demographics, subscription tiers, purchase history, interests, and more:

* **CRM systems**: Salesforce, HubSpot, Pipedrive — sync customer profiles and subscription data
* **Analytics platforms**: Segment, Mixpanel, Google Analytics — leverage behavioral insights
* **Data warehouses**: Snowflake, BigQuery, Redshift — tap into your entire data ecosystem
* **E-commerce platforms**: Shopify, WooCommerce, Stripe — use purchase history and preferences
* **Customer platforms**: Intercom, Zendesk, Klaviyo — incorporate support and communication data
* **Any API or database**: Custom integrations to pull user attributes from anywhere

#### 🧠 **User Behaviors** *(Coming Soon)*

Target users based on their actions and patterns:

* **Search patterns**: Mark users as "health-conscious" if they search for low-calorie recipes
* **Content engagement**: Show advanced recipes to users who spend time reading detailed instructions
* **Navigation behavior**: Detect recipe preferences from browsing patterns
* **Cross-session learning**: Build user profiles that get smarter over time

#### 🔥 Beyond Variants: One-to-One Hyper Personalization

But wait, there's more! Beyond swapping entire ingredient lists, you can make **any text field** hyper-personalized with live JavaScript expressions:

* Recipe titles: `"${profile.firstName}'s Perfect ${profile.dietaryPreference} Lasagna"`
* Serving suggestions: `"This recipe serves ${profile.familySize} people perfectly"`
* Personal touches: `"Welcome back ${profile.firstName}! Since you loved our ${profile.lastFavoriteRecipe}, you'll love this one"`

Every piece of content becomes dynamically personal — **in real-time**! 🤯

#### 🚀 **Real-World Examples**

Imagine these scenarios — **all possible with the same simple API you just learned**:

```javascript
// Same fetchRecipe() function, but now it can:

// Show premium wine pairings to Gold subscribers from your CRM
const premiumUser = { userTier: 'gold', interests: ['wine'] };

// Display halal ingredients to Muslim users in the Middle East  
const regionalUser = { location: 'UAE', dietaryNeeds: ['halal'] };

// Recommend quick recipes to busy parents on weekday evenings
const timeContext = { timeOfDay: 'evening', dayOfWeek: 'tuesday', hasKids: true };
```

**The best part?** Your integration code stays exactly the same. One API call, infinite personalization possibilities.

**Hungry for more?** Our complete documentation serves up advanced targeting, enterprise integrations, and real-world case studies that'll transform how you think about personalization. **Bon appétit!** 👨‍🍳

## 🚀 Where to Go Next

If you're building with **JavaScript/TypeScript**, check out our official SDK that handles all the UTM tracking, referrer detection, and header management automatically. What took us several lines of manual code becomes a simple `GrowcadoSDK.getContent()` call with built-in personalization, automatic customer journey tracking, SSR support, and zero configuration. The SDK doesn't just make integration easier — it makes advanced personalization effortless! 🍽️✨
