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:
- Generates structured recipe data (names, ingredients, instructions, and image prompts) using an LLM.
- Illustrates each dish with AI-generated images.
- 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
ObjectGeneratorto produce structured JSON - Mapping over generated data with
@Mapto 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: trueand add arunPriceto 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.