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.