Build a Recipe Generator Flow

This example walks you through a simple three-step Flow that’s perfect as your first project. By the end, you’ll have a working pipeline that:

  1. Generates structured recipe data (names, ingredients, instructions, and image prompts) using an LLM.
  2. Illustrates each dish with AI-generated images.
  3. Bundles everything into a single downloadable zip.

No training step needed — this is a great way to learn the basics before tackling more complex Flows.

What you’ll learn

  • Defining user input with a JSON Schema
  • Using ObjectGenerator to produce structured JSON
  • Mapping over generated data with @Map to create images
  • Bundling results with Zipper
  • Configuring result widgets so users see their output

Create the Flow skeleton

Every Flow starts with top-level metadata. We’ll keep this one private for now — you can always make it public later.

{
  title: 'Recipe Generator',
  description: 'Generate creative recipes with beautiful dish photos, bundled for download.',
  public: false,
  input: {},
  steps: [],
  ui: {},
}

Define the input

We only need one thing from the user — what kind of recipes they want:

input: {
  type: 'object',
  required: ['cuisine'],
  properties: {
    cuisine: {
      type: 'string',
      title: 'What kind of recipes?',
      placeholder: 'Homemade Japanese comfort food',
      minLength: 4,
      maxLength: 500,
    },
  },
},

When the Flow renders, this becomes a simple text field. The user types something like “rustic Italian pasta dishes” and hits submit.

Step 1: Generate recipes with ObjectGenerator

The ObjectGenerator step calls an LLM and forces it to return JSON matching your schema. Here we’re asking for an array of recipes, each with a name, ingredients, instructions, and an image prompt we’ll use later.

{
  id: 'recipes',
  type: 'ObjectGenerator',
  parameters: {
    description: "You are an experienced chef. Generate creative, detailed recipes that match the user's request. For each recipe, write a vivid image prompt describing the finished dish — this will be used to generate a photo.",
    input: '$.input.cuisine',
    amount: 5,
    schema: {
      type: 'object',
      required: ['recipes'],
      properties: {
        recipes: {
          type: 'array',
          items: {
            type: 'object',
            required: ['name', 'ingredients', 'instructions', 'imagePrompt'],
            properties: {
              name: { type: 'string' },
              ingredients: {
                type: 'array',
                items: { type: 'string' },
              },
              instructions: {
                type: 'array',
                items: { type: 'string' },
              },
              imagePrompt: {
                type: 'string',
                description: 'A detailed prompt for generating a beautiful photo of this finished dish. Include lighting, plating style, and background details.',
              },
            },
          },
        },
      },
    },
  },
}

When this step runs, you’ll get structured JSON like:

{
  "recipes": [
    {
      "name": "Miso Ramen with Chashu Pork",
      "ingredients": ["pork belly", "miso paste", "ramen noodles", "..."],
      "instructions": ["Marinate the pork belly...", "..."],
      "imagePrompt": "A steaming bowl of miso ramen with sliced chashu pork, soft-boiled egg, and green onions, shot from above on a dark wooden table with warm lighting"
    }
  ]
}

The description field in your schema properties acts as instructions to the LLM. Use it to guide the output format — like we did for imagePrompt above.

Step 2: Generate dish photos with ImageGenerator

Now here’s the fun part — we map over each recipe and use its imagePrompt to generate a photo of the finished dish.

{
  id: 'dishPhotos',
  type: 'ImageGenerator',
  parameters: {
    baseModel: 'stable-diffusion-v1-5/stable-diffusion-v1-5',
    images: [
      [
        '@Map($.results.recipes.recipes)',
        {
          prompt: '$$.imagePrompt',
          negativePrompt: 'blurry, low quality, text, watermark',
          steps: 30,
          width: 1024,
          height: 1024,
        },
      ],
    ],
  },
}

The @Map operator iterates over the recipes array. For each recipe, $$ refers to the current item — so $$.imagePrompt grabs that recipe’s image prompt. The result is one generated image per recipe.

Each image costs $0.02. With 5 recipes, this step costs $0.10.

Step 3: Bundle everything with Zipper

The final step pulls the recipe JSON and all the dish photos into a single downloadable zip:

{
  id: 'bundle',
  type: 'Zipper',
  parameters: {
    filename: 'recipes.zip',
    sources: ['recipes', 'dishPhotos'],
  },
}

The Zipper collects images from dishPhotos (as .jpg files) and the JSON output from recipes (as a .json file), then uploads the archive.

Configure the results UI

Tell lensless what to show the user when the Flow finishes:

ui: {
  submitLabel: 'Generate Recipes',
  results: {
    widgets: [
      { type: 'Images', sources: ['dishPhotos'] },
      { type: 'Objects', sources: ['recipes'] },
      { type: 'Download', sources: ['bundle'] },
    ],
  },
},

The user sees a gallery of dish photos, the full recipe JSON, and a download button for the zip — all on one page.

The complete Flow

Here’s everything put together:

{
  title: 'Recipe Generator',
  description: 'Generate creative recipes with beautiful dish photos, bundled for download.',
  public: false,
  input: {
    type: 'object',
    required: ['cuisine'],
    properties: {
      cuisine: {
        type: 'string',
        title: 'What kind of recipes?',
        placeholder: 'Homemade Japanese comfort food',
        minLength: 4,
        maxLength: 500,
      },
    },
  },
  ui: {
    submitLabel: 'Generate Recipes',
    results: {
      widgets: [
        { type: 'Images', sources: ['dishPhotos'] },
        { type: 'Objects', sources: ['recipes'] },
        { type: 'Download', sources: ['bundle'] },
      ],
    },
  },
  steps: [
    {
      id: 'recipes',
      type: 'ObjectGenerator',
      parameters: {
        description: "You are an experienced chef. Generate creative, detailed recipes that match the user's request. For each recipe, write a vivid image prompt describing the finished dish — this will be used to generate a photo.",
        input: '$.input.cuisine',
        amount: 5,
        schema: {
          type: 'object',
          required: ['recipes'],
          properties: {
            recipes: {
              type: 'array',
              items: {
                type: 'object',
                required: [
                  'name',
                  'ingredients',
                  'instructions',
                  'imagePrompt',
                ],
                properties: {
                  name: { type: 'string' },
                  ingredients: {
                    type: 'array',
                    items: { type: 'string' },
                  },
                  instructions: {
                    type: 'array',
                    items: { type: 'string' },
                  },
                  imagePrompt: {
                    type: 'string',
                    description: 'A detailed prompt for generating a beautiful photo of this finished dish. Include lighting, plating style, and background details.',
                  },
                },
              },
            },
          },
        },
      },
    },
    {
      id: 'dishPhotos',
      type: 'ImageGenerator',
      parameters: {
        baseModel: 'stable-diffusion-v1-5/stable-diffusion-v1-5',
        images: [
          [
            '@Map($.results.recipes.recipes)',
            {
              prompt: '$$.imagePrompt',
              negativePrompt: 'blurry, low quality, text, watermark',
              steps: 30,
              width: 1024,
              height: 1024,
            },
          ],
        ],
      },
    },
    {
      id: 'bundle',
      type: 'Zipper',
      parameters: {
        filename: 'recipes.zip',
        sources: ['recipes', 'dishPhotos'],
      },
    },
  ],
}

Cost breakdown

For a run generating 5 recipes:

  • ObjectGenerator: ~$0.01 (depends on token count)
  • ImageGenerator: 5 images × $0.02 = $0.10
  • Zipper: ~$0.02 (depends on archive size)
  • Private Flow run: $0.01
  • Total: ~$0.14

Next steps

  • Make it public — set public: true and add a runPrice to start earning. See the monetization guide.
  • Add a training step — let users upload photos to train a model, then generate images of them cooking. Check out the DnD Adventure Generator example for a Flow that includes training.
  • Customize the UI — add background images and preview assets to make the Flow page pop. See the UI guide.

Last updated on March 19, 2026