Bulk Menu Image Generation from CSV
Transform your entire restaurant menu into AI-generated food photography in minutes. This script reads a CSV of menu items and generates appetizing images using Seedream V1.5 with automatic negative prompts for realistic results.
Prepare Your Menu CSV
Create a CSV file with your menu items. The script expects these columns:
| Column | Required | Description |
|---|---|---|
| item_name | Yes | Name of the dish |
| category | Yes | Entree, Sides, Dessert, etc. |
| description | Yes | Detailed description for prompt |
| style | Optional | Custom photography style |
Example CSV:
item_name,category,description,style Artisan Burger,Entree,Gourmet smash burger with aged cheddar and caramelized onions on brioche,casual dining overhead shot Truffle Fries,Sides,Crispy fries drizzled with truffle oil and parmesan,rustic wooden board Grilled Salmon,Entree,Atlantic salmon with lemon butter sauce and seasonal vegetables,fine dining plating Caesar Salad,Starter,Crisp romaine with house-made dressing and shaved parmesan,bright natural lighting Chocolate Lava Cake,Dessert,Warm chocolate cake with molten center and vanilla ice cream,elegant plating dark background
Customize Your Brand Template
Edit the BRAND_TEMPLATE in the script to match your restaurant's visual identity. This ensures all images have consistent branding.
Template Options:
lightingwarm golden side lighting, bright natural light, dramatic moody lighting
angleoverhead shot, 45-degree angle, close-up detail shot
backgrounddark slate surface, rustic wooden table, white seamless, marble counter
plateceramic plate, rustic board, slate platter, minimalist white dish
Run the Generation Script
Install dependencies and run:
pip install openai httpx export CREATIVEAI_API_KEY=your_key_here python menu_batch_generator.py menu.csv
Full Script:
#!/usr/bin/env python3
"""
Bulk Restaurant Menu Image Generator
Transforms a CSV of menu items into AI-generated food photography.
Requirements:
pip install openai httpx
Usage:
export CREATIVEAI_API_KEY=your_key_here
python menu_batch_generator.py menu.csv
"""
import asyncio
import csv
import json
import os
import sys
from dataclasses import dataclass
from datetime import datetime
import httpx
# Configuration
API_KEY = os.environ.get("CREATIVEAI_API_KEY", "")
BASE_URL = "https://api.creativeai.run/v1/images/generations"
MODEL = "seedream-v1.5" # Best for food photography
MAX_CONCURRENCY = 4 # Parallel requests
OUTPUT_DIR = "menu_images"
# Food photography defaults
DEFAULT_STYLE = "professional food photography, appetizing, warm lighting, shallow depth of field"
DEFAULT_NEGATIVE = "plastic, artificial, cartoon, illustration, text, watermark, blurry, oversaturated, neon colors"
# Brand template - customize for your restaurant
BRAND_TEMPLATE = {
"lighting": "warm golden side lighting",
"angle": "45-degree angle",
"background": "dark slate surface",
"plate": "ceramic plate with subtle texture",
"garnish_style": "fresh herbs, minimal garnish",
}
@dataclass
class MenuItem:
item_name: str
category: str
description: str
style: str = ""
def build_food_prompt(item: MenuItem) -> str:
"""Build a detailed food photography prompt."""
parts = [
f"Appetizing photo of {item.item_name}",
item.description,
item.style or DEFAULT_STYLE,
BRAND_TEMPLATE["lighting"],
BRAND_TEMPLATE["angle"],
f"on {BRAND_TEMPLATE['background']}",
f"served on {BRAND_TEMPLATE['plate']}",
BRAND_TEMPLATE["garnish_style"],
"photorealistic, high detail, commercial food photography",
]
return ", ".join(parts)
async def generate_menu_image(
client: httpx.AsyncClient,
semaphore: asyncio.Semaphore,
item: MenuItem,
index: int
) -> dict:
"""Generate a single menu image with retry logic."""
prompt = build_food_prompt(item)
payload = {
"model": MODEL,
"prompt": prompt,
"negative_prompt": DEFAULT_NEGATIVE,
"size": "1024x1024",
"n": 1,
}
async with semaphore:
for attempt in range(3): # Max 3 retries
try:
resp = await client.post(BASE_URL, json=payload)
if resp.status_code == 429: # Rate limited
await asyncio.sleep(2 ** attempt)
continue
resp.raise_for_status()
data = resp.json()
if data.get("data") and data["data"][0].get("url"):
return {
"item_name": item.item_name,
"category": item.category,
"status": "success",
"image_url": data["data"][0]["url"],
"prompt_used": prompt,
"index": index,
}
return {
"item_name": item.item_name,
"status": "failed",
"error": "No image URL in response",
"index": index,
}
except Exception as e:
if attempt == 2:
return {
"item_name": item.item_name,
"status": "failed",
"error": str(e),
"index": index,
}
await asyncio.sleep(1.5 ** (attempt + 1))
return {
"item_name": item.item_name,
"status": "failed",
"error": "Max retries exceeded",
"index": index,
}
def load_menu_csv(path: str) -> list[MenuItem]:
"""Load menu items from CSV file."""
items = []
with open(path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
items.append(MenuItem(
item_name=row.get("item_name", "").strip(),
category=row.get("category", "").strip(),
description=row.get("description", "").strip(),
style=row.get("style", "").strip(),
))
return [i for i in items if i.item_name] # Filter empty rows
async def generate_all_images(items: list[MenuItem]) -> list[dict]:
"""Generate images for all menu items with controlled concurrency."""
semaphore = asyncio.Semaphore(MAX_CONCURRENCY)
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
async with httpx.AsyncClient(headers=headers, timeout=60.0) as client:
tasks = [
generate_menu_image(client, semaphore, item, idx)
for idx, item in enumerate(items)
]
return await asyncio.gather(*tasks)
async def main():
if not API_KEY:
print("Error: Set CREATIVEAI_API_KEY environment variable")
sys.exit(1)
if len(sys.argv) < 2:
print("Usage: python menu_batch_generator.py <menu.csv>")
sys.exit(1)
csv_path = sys.argv[1]
print(f"Loading menu from {csv_path}...")
items = load_menu_csv(csv_path)
print(f"Found {len(items)} menu items")
if not items:
print("No valid menu items found in CSV")
sys.exit(1)
print(f"Generating images with {MODEL}...")
start_time = datetime.now()
results = await generate_all_images(items)
# Create output directory
os.makedirs(OUTPUT_DIR, exist_ok=True)
# Save results
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = os.path.join(OUTPUT_DIR, f"menu_images_{timestamp}.json")
with open(output_file, "w", encoding="utf-8") as f:
json.dump({
"generated_at": datetime.now().isoformat(),
"model": MODEL,
"total_items": len(items),
"results": results,
}, f, indent=2)
# Summary
success = sum(1 for r in results if r["status"] == "success")
failed = sum(1 for r in results if r["status"] == "failed")
elapsed = (datetime.now() - start_time).total_seconds()
print(f"\n{'='*50}")
print(f"Generation Complete!")
print(f"{'='*50}")
print(f"Success: {success}/{len(items)} images")
print(f"Failed: {failed}")
print(f"Time: {elapsed:.1f} seconds")
print(f"Output: {output_file}")
if success > 0:
print(f"\nGenerated Image URLs:")
for r in results:
if r["status"] == "success":
print(f" [{r['category']}] {r['item_name']}: {r['image_url']}")
if __name__ == "__main__":
asyncio.run(main()))
Quick Test (Single Image)
Test the API with a single dish before running the full batch:
# Quick test with 3 menu items
import os
from openai import OpenAI
os.environ["CREATIVEAI_API_KEY"] = "your_key_here"
client = OpenAI(
api_key=os.environ["CREATIVEAI_API_KEY"],
base_url="https://api.creativeai.run/v1"
)
# Test with a single dish
result = client.images.generate(
model="seedream-v1.5",
prompt="""Artisan Burger with aged cheddar, caramelized onions,
on brioche bun, professional food photography, warm golden
lighting, 45-degree angle, appetizing, photorealistic""",
negative_prompt="plastic, artificial, cartoon, text, watermark",
size="1024x1024",
n=1
)
print(f"Image URL: {result.data[0].url}")
Output Format
The script saves results to menu_images/menu_images_YYYYMMDD_HHMMSS.json:
{
"generated_at": "2026-03-16T15:30:00",
"model": "seedream-v1.5",
"total_items": 5,
"results": [
{
"item_name": "Artisan Burger",
"category": "Entree",
"status": "success",
"image_url": "https://cdn.creativeai.run/...",
"prompt_used": "Appetizing photo of Artisan Burger..."
}
]
}Next Steps
- β’ Download images and upload to your POS or delivery app
- β’ Use the URLs directly in your website or ordering system
- β’ Generate promo videos from top images using the video API
- β’ Integrate into n8n/Make/Zapier for automated workflows
Ready to Automate Your Menu Photography?
Get 50 free credits to test the API. No credit card required.