Authoring a block
A block is written as a short YAML manifest. This is the reference for what goes in one. For what blocks are and how installing one feels, see Blocks.
A complete example
block: 1 # manifest format version, always 1
name: talking_head_polish # unique slug: lowercase, digits, underscores
title: Talking Head Polish
description: Trim dead air, caption the cut, and add sound effects.
version: 1.4.0 # your version, in the form 1.2.3
icon: 🎙
tags: [captions, sfx]
params:
- key: style
type: select
label: Style
options: [minimal, punchy]
default: punchy
- key: add_sfx
type: boolean
default: true
- key: silence_sec
type: number
min: 0.1
max: 5
default: 0.8
- key: broll_dirs
type: list
default: []
inputs:
- id: transcript
required: true
- id: backbone
required: true
requires:
levels:
- { name: captions, order: 30, type: title }
- { name: sfx, order: 40, type: audio }
secrets: [ELEVENLABS_API_KEY]
steps:
- id: trim_dead
processor: deadspace_trimmer
input:
- { id: transcript, from: inputs.transcript }
level: main
params:
min_silence_duration: "{{ params.silence_sec }}"
- id: caps
processor: captions
input:
- { id: transcript, from: inputs.transcript }
- { id: backbone, from: steps.trim_dead }
level: captions
- id: sfx
when: "params.add_sfx && params.style != 'minimal'"
processor: sound_fx
input:
- { id: source, from: inputs.backbone }
level: sfx
- for_each: params.broll_dirs
id: "broll_{{ loop.index }}"
processor: broll_place
input:
- { id: transcript, from: inputs.transcript }
level: broll
params:
broll_place_dir: "{{ loop.item }}"
exports:
main: steps.trim_dead
captions: steps.caps
Parameters
Each entry under params becomes a field in the insert dialog. Types are string, text, number, duration, boolean, select, color, level, list and file. A select needs options; number and duration accept min and max, which are enforced before anything expands. Give a default wherever you can: a block that inserts sensibly with no configuration is a block people will actually try.
Inputs
inputs are the only way data reaches a block. Each one is a slot the installer wires to a step or a file in their own template. Mark an input required: true when the block cannot work without it; an optional input that nobody wires simply contributes nothing.
Steps
Each entry under steps becomes a real pipeline step. processor names a processor from the processor reference, and it must be written literally: you cannot compute a processor name from a parameter. That is deliberate, and it is what lets a reviewer read a block's processor list and know it is complete.
input[].from reads either inputs.NAME (something the installer wired) or steps.NAME (another step in this block, by its id inside the block).
Conditions
when decides whether a step is included at all. It is evaluated once, when the block is inserted, from the installer's settings, so it is a way of shaping what gets added rather than a decision made during a run.
The language is small on purpose: params.NAME, loop.item, loop.index and inputs.NAME.present, compared with ==, != and in, combined with &&, || and !. There is no arithmetic, and nothing outside those four names is readable, because nothing else exists yet when it runs.
If you need a decision that depends on the video itself, that is a job for an AI step at run time, with the instruction carried as a parameter.
Repetition
for_each iterates a list parameter and emits one step per item. Inside it, loop.item is the value and loop.index counts from zero, both usable in the step's id and settings. Up to 32 items.
Nesting
A step with use_block includes another published block:
- use_block: caption_safety_pass
alias: safety
with:
strictness: "{{ params.style }}"
input:
captions: steps.caps
The child sees only what with passes it. Nesting goes three deep, and a cycle is refused.
Requirements
requires.levels are timeline levels the block's steps write to. They are added to the installer's template if missing. If a level with the same name already exists with a different type, or with a different backbone setting, the insert is refused rather than changing it: which level is the backbone decides the timeline's whole duration, and that is the installer's decision.
requires.secrets names service keys the block needs. It is informational: what the installer is actually shown is derived from the steps themselves.
Exports
exports names outlets other steps can wire to, mapping a name to one of your steps. An export pointing at a step that a when dropped is simply absent, rather than a wire to nothing.
What is deliberately not available
There is no way to include raw YAML, and there never will be. It is the one feature that would turn a reviewed, readable block into arbitrary configuration injection.
There is also no fan-out over files matched at run time, no arithmetic, and no way to edit the installer's existing steps. A block adds its own steps and nothing else.
Limits
Nesting depth 3, 32 iterations per for_each, 200 emitted steps in total, 60 steps declared in one manifest, 40 parameters.
Sharing it
Open Account settings, My submissions and choose Share a block, then paste the manifest. It is parsed and checked immediately: an unknown processor, a computed processor name, a bad version, a missing id, or anything that looks like a credential is refused on the spot rather than sent to a reviewer. After that it goes through the same review as a template, and you can follow it in the same place you submitted it.
Versions are immutable once published. An install refers to a version, so the same number cannot come to mean two different things. To publish a change, raise the version; sharing a version that is already live is refused with that as the reason.