Author and execute multi-step media pipelines on fal.ai. Two modes: (A) declarative workflow JSON files for the fal.ai workflow runtime ("create workflow", "chain models", "image to video pipeline"); (B) imperative genmedia CLI orchestration when scripting locally ("design pipeline", "fan-out generation", "frame bridging", "narrated video", "dataset generation").
Runtime: Two modes. Mode A authors
.jsonfiles for the fal.ai workflow runtime (executed in the cloud, no local CLI needed). Mode B drives the genmedia CLI for local orchestration. See thegenmediaskill for Mode B command syntax; rungenmedia initonce if not yet installed.
This skill covers two complementary ways to build multi-step media pipelines on fal.ai. They share endpoints and concepts but differ in deliverable.
| Mode | Deliverable | When to use |
|---|---|---|
| A. Workflow JSON | A portable .json file the fal.ai workflow runtime executes | The user wants a reusable, shareable workflow asset; the pipeline graph is fixed at design time |
| B. genmedia CLI orchestration | A sequence of genmedia run / status / upload calls (often scripted) | Local scripting, exploratory pipelines, dataset jobs, branching logic, or pipelines that need conditional steps |
If unsure: prefer Mode A when the result is a deliverable for someone else to run; Mode B when you are running it yourself or steps need runtime decisions.
Generate 100% working, production-ready fal.ai workflow JSON files. Workflows chain multiple AI models together via a declarative graph.
JSON-mode references:
Plan and execute a sequence of genmedia calls with clear inputs, outputs, dependencies, and quality checks. Use this when a single model call is not enough and the orchestration happens in your shell, not on fal.ai.
CLI-mode references:
For default endpoint choices in Mode B, consult fal-models-catalog. Always run genmedia schema <endpoint_id> --json before executing and genmedia pricing <endpoint_id> --json when cost matters.
⚠️ ONLY TWO VALID NODE TYPES EXIST:
| Type | Purpose |
|---|---|
"run" | Execute a model/app |
"display" | Output results to user |
❌ INVALID: type: "input" - This does NOT exist! Input is defined ONLY in schema.input.
{
"name": "my-workflow",
"title": "My Workflow",
"contents": {
"name": "workflow",
"nodes": {
"output": {
"type": "display",
"id": "output",
"depends": ["node-image"],
"input": {},
"fields": { "image": "$node-image.images.0.url" }
},
"node-image": {
"type": "run",
"id": "node-image",
"depends": ["input"],
"app": "fal-ai/flux/dev",
"input": { "prompt": "$input.prompt" }
}
},
"output": { "image": "$node-image.images.0.url" },
"schema": {
"input": {
"prompt": {
"name": "prompt",
"label": "Prompt",
"type": "string",
"required": true,
"modelId": "node-image"
}
},
"output": {
"image": { "name": "image", "label": "Generated Image", "type": "string" }
}
},
"version": "1",
"metadata": {
"input": { "position": { "x": 0, "y": 0 } },
"description": "Simple text to image workflow"
}
},
"is_public": true,
"user_id": "",
"user_nickname": "",
"created_at": ""
}
| Reference | Use Case | Example |
|---|---|---|
$input.field | Input value | $input.prompt |
$node.output | LLM text output | $node-llm.output |
$node.images.0.url | First image URL | $node-img.images.0.url |
$node.image.url | Single image URL | $node-upscale.image.url |
$node.video.url | Video URL | $node-vid.video.url |
$node.audio_file.url | Audio URL | $node-music.audio_file.url |
$node.frame.url | Extracted frame | $node-extract.frame.url |
⚠️ NEVER mix text with variables! Variable MUST be the ENTIRE value.
// ❌ WRONG - WILL BREAK
"prompt": "Create image of $input.subject in $input.style"
// ✅ CORRECT - Variable is the ENTIRE value
"prompt": "$input.prompt"
"prompt": "$node-llm.output"
To combine values: Use fal-ai/text-concat or fal-ai/workflow-utilities/merge-text. See Model Reference.
// ❌ WRONG
"node-b": {
"depends": [],
"input": { "data": "$node-a.output" }
}
// ✅ CORRECT
"node-b": {
"depends": ["node-a"],
"input": { "data": "$node-a.output" }
}
// ❌ WRONG
"my-node": { "id": "different-id" }
// ✅ CORRECT
"my-node": { "id": "my-node" }
openrouter/router → Text only, no image_urlsopenrouter/router/vision → ONLY when analyzing images"schema": {
"input": {
"field": { "modelId": "first-consuming-node" }
}
}
"output": {
"depends": ["node-a", "node-b", "node-c"],
"fields": {
"a": "$node-a.video",
"b": "$node-b.images.0.url"
}
}
| Model Type | Output Reference |
|---|---|
| LLM | $node.output |
| Text Concat | $node.results |
| Merge Text | $node.text |
| Image Gen (array) | $node.images.0.url |
| Image Process (single) | $node.image.url |
| Video | $node.video.url |
| Music | $node.audio_file.url |
| Frame Extract | $node.frame.url |
Use genmedia models "<query>" --json or genmedia models --category <cat> --json to discover current models. See references/MODELS.md for workflow code templates.
"schema": {
"input": {
"text_field": {
"name": "text_field",
"label": "Display Label",
"type": "string",
"description": "Help text",
"required": true,
"modelId": "consuming-node"
},
"image_urls": {
"name": "image_urls",
"type": { "kind": "list", "elementType": "string" },
"required": true,
"modelId": "node-id"
}
}
}
Before outputting any workflow, verify:
type: "run" or type: "display" ONLY (NO type: "input"!)$node.xxx has matching depends entryid matches object keymodelId for each fieldAuthor the JSON file by hand following the structure shown above. There is no script wrapper; the agent writes the file directly. Validate before delivery:
$node.xxx reference appears in depends.modelId for each field.depends includes every node it references.For each model used, inspect the schema first:
genmedia schema <endpoint_id> --json
Then write the corresponding input block in the workflow JSON.
Error: unexpected value; permitted: 'run', 'display', field required
Cause: You created a node with type: "input" which does NOT exist.
Solution: Remove ANY node with type: "input". Define input fields ONLY in schema.input.
Error: Node references $node-x but doesn't depend on it
Solution: Add the referenced node to the depends array.
Error: Node key "my-node" doesn't match id "different-id"
Solution: Ensure the object key matches the id field exactly.
Error: image_urls provided but using text-only router
Solution: Switch to openrouter/router/vision when analyzing images.
Every model's input/output schema:
https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=[endpoint_id]
Example:
https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=fal-ai/nano-banana-pro
When the deliverable is local execution rather than a portable JSON file, plan a pipeline of genmedia calls.
Ask only for missing information that changes the pipeline:
input assets -> planner -> generation nodes -> utility nodes -> QA -> final outputs
fal-models-catalog.genmedia models --endpoint_id <endpoint_id> --json
Use text search only as fallback discovery for roles not covered:
genmedia models "image generation product photography" --json
genmedia docs "fal.ai workflow utility endpoints" --json
genmedia schema <endpoint_id> --json
genmedia pricing <endpoint_id> --json
genmedia upload ./input.png --json
--async for slow generation; poll with genmedia status.genmedia run <endpoint_id> --<field> "<value>" --json
genmedia run <endpoint_id> --<field> "<value>" --async --json
genmedia status <endpoint_id> <request_id> --download "./outputs/workflow/{request_id}_{index}.{ext}" --json
For downstream nodes, pass the media URL from the previous result. Only upload local intermediate files when no URL is available.
Download final assets with templates that cannot collide:
--download "./outputs/workflow/{request_id}_{index}.{ext}"
validation_errors, re-inspect schema, fix the exact field.Before returning, verify:
--download.If the workflow becomes too complex, stop expanding and ask the user to choose between faster iteration, higher fidelity, or broader variation.
skillbazaar install fal-workflow --agent claudeSign in (free) to install skills with the CLI.
Published by