Build with an AI coding tool
Use a complete portable authoring prompt with your own coding assistant.
Start with the SDK contract
Use the button below to copy the complete authoring guide, then add a short description such as “Make a Block that converts an image to grayscale while preserving alpha.” The guide includes the package format, supported runtime, typed ports, permissions, fixtures, and a complete example.
Download the paste-ready guide or download the SDK.
Review the result
Ask the assistant to return source files or one .stillmade.json package. Run the CLI validation, tests, and preview. Import it into StillMade and review before confirming. An LLM’s claim that tests passed is not a test report from the sandbox.
Complete authoring guide
# StillMade authoring contract for coding agents
<!-- builder-contract:start (generated by npm run build:sdk; do not edit) -->
## Block builder contract (stillmade.block-builder@1.0.0)
Contract `stillmade.block-builder@1.0.0`. Humans, coding agents, imported source, GitHub repositories and generated code all adapt to the Block platform; anything that becomes a StillMade Block obeys this contract.
### Source of truth
Treat the current SDK (`stillmade.block.json` manifest, `packages/block-sdk` contracts, validators, examples and published docs) as the source of truth. Before writing code, read the manifest contract, at least two working first-party Blocks closest to the request, the runtime you will use, the typed port and resolver contracts, and the shared-interface contract when you need a custom view. Do not invent runtimes, operations, permissions, ports, host services or bridge calls. If the documentation and the validator disagree, the validator wins and the mismatch should be reported.
### Reuse first
Before building, check what already exists (`find_existing_blocks` over MCP, or the Marketplace and your library) by the inputs, results and operations the goal needs. Use an installed or reviewed Block that already does it; remix a close one so its license, notices and creator credit stay attached; chain existing Blocks and build only the missing step. Never republish another creator's source without remix lineage: public copies are refused. Build a new Block when nothing fits or the person asks for their own version, and say which similar Blocks you considered.
### Define the capability first
Translate the request into an explicit capability contract before implementation: what the Block does; each input with its type, meaning and whether it is required (prefer optional settings with safe defaults over extra required questions); each output with its type and meaning; persistent state; project context it reads; permissions; UI; long-running behavior; expected failures; cancellation; determinism; and whether it needs hosted models, media, desktop tools or shared editing. Keep the public interface as small and general as possible and keep implementation details out of it.
### Blocks compose
A Block is not a mini-app. Its outputs become other Blocks' inputs and its inputs come from other Blocks, project context or the user. Use the typed port system: shared StillMade types (`image`, `video`, `audio`, `asset`, `script`, `shot[]`, ...), a `semantic` meaning on every port, `required` and a short `description`. Mark exactly one primary input and one primary output so Project Type Steps connect automatically, and use `kind: "task"`: a `workspace` Block owns a project document and is never connected automatically. Inputs marked `context` read the project and cannot receive a connection from another Step. Output reusable StillMade values: media references (`{kind,assetId,versionId,url}`) and structured production values with `schemaVersion: 1`, preserving provenance, versions and relationships downstream Blocks need. Never make ordinary users wire JSON, copy IDs, understand internal paths or translate one Block's output into another: `createUniversalResolver()` (the same engine as the Project Type composer) resolves inputs from compatible upstream outputs, permitted project context and, for optional non-context inputs, `StillMade.requestFromUser`.
### Use platform primitives
Prefer, in order: an SDK capability; a documented host capability (the hosted operations are text.generate, audio.speech, audio.transcribe, audio.music, audio.sfx, image.generate, video.generate, image.describe, media.analyze, web.fetch, web.research, timeline.propose, workspace.propose, connection.execute; connected third-party apps go through `connection.execute`); an approved dependency the SDK already bundles; small Block-local code. Do not duplicate what StillMade provides. A Block never contains its own asset system, file picker, authentication, project-context system, input resolver, Block connection system, credit system, secrets store, permission system, progress system, multiplayer system, persistence system, model-provider abstraction or execution protocol.
### No hidden workarounds
Never make a Block look functional through hard-coded local paths, developer-machine dependencies, undocumented environment variables, admin or database access, hidden core modifications, direct database writes, fake or mocked production results, hand-prepared test assets that hide incompatibilities, credentials in code, or assumptions about one user's machine. The finished Block must work through the same interfaces available to any installed Block; `permissions.network`, `permissions.filesystem` and `permissions.secrets` must be empty arrays.
### Interface
If the capability needs UI, use the Block UI SDK: generated `manifest.ui` controls first; a sandboxed custom view (`view.html`/`view.css`/`view.js`) only when the core interaction genuinely needs one. Expose the capability, not the implementation: meaningful controls, sensible defaults, preview, direct manipulation, progress, clear errors and visible outputs. Do not show raw JSON, internal IDs, filesystem details, provider terminology or developer settings unless the Block is itself a developer tool. Follow the host appearance (Light, Dark and White) and the mobile layout contract: one interface for every screen size that fits a 320 CSS-pixel phone without horizontal scrolling.
### Shared editing
Projects are multiplayer. A custom view subscribes through `StillMade.onShared(callback)` and publishes every editable field: give static controls a unique `id` and `data-stillmade-share="field"` (the host owns their synchronization), use `StillMade.bindShared(field, elementId)` for controls created dynamically, and `StillMade.updateShared(values, before, options)` for structured values; private variables and DOM values are not shared. Mark a truly device-only control (playback, hover, device selection) with `data-stillmade-local`. Observe shared outputs as well as state, honor `canEdit` in every mutating callback (read-only participants cannot write), never run, click or call a paid integration in response to a remote update, and preserve conflicting drafts. Shared background work uses `sharedWork` with the `work.shared` permission.
### External services
Reach external services only through StillMade's hosted operations, the connections service (`connection.execute`), or, in a module Block, `stillmade.net.fetch` to origins declared in `permissions.network` (with the key each needs) and `stillmade.connections.call` to adapters or catalog apps in `permissions.connections`; never put keys in code, inputs or headers: StillMade adds each person's own key on its servers. The host owns the model and provider choice, BYOK keys, StillMade credits, spending consent, authorization, token refresh, rate limits, retries and provider errors; a Block never receives a secret and never names a provider unless it must. Treat provider unavailability, timeout, cancellation and partial failure as typed outcomes the Block reports, not crashes. Generated images, video, voices, music and sound effects come from hosted operations or the `media.generate` action, paid with the user's StillMade credits at StillMade's prices; for structured model output declare a `json` output with `capability.schema` instead of parsing prose. When a Block needs npm libraries, WebAssembly or heavy compute, use `runtime: "module"` and reach hosted operations through `stillmade.hosted.run` with the operations listed in `permissions.capabilities`. Decode, cut, join, re-encode, extract or normalise audio and video with `stillmade.media.transform` on the device; never ask for a server render. For a rich interactive interface (React or another framework, SVG, canvas, drag), use a frame view: `src/view.config.json` with `{"runtime":"frame"}` and `src/view.js` bundled as one IIFE, colored with StillMade's theme variables. For files and structured data use the `file`, `document`, `table`, `url`, `date` and `color` types and their generated controls and presentations, not JSON text.
### Dependencies
A Block must declare everything it requires and must not assume anything is installed. Portable packages carry no package dependencies (`portable.dependencies` is empty): inline small, license-compatible code and record its license and notice in `manifest.license` and `manifest.provenance`. Desktop-only tools are reached solely through declared desktop permissions and the host desktop bridge. If a dependency cannot be delivered through the Block runtime, report the incompatibility instead of assuming the user's machine has it.
### Security
Treat Block code, external data, files, repositories, model output and network responses as untrusted. Request the minimum permissions and capabilities. Do not weaken the sandbox (QuickJS has no fetch, DOM, timers, modules or Node APIs). Validate and bound every value crossing a boundary. Never expose secrets through logs, outputs, project context, UI, error messages or serialized state.
### Execution lifecycle
A successful run means more than a returned value. Validate inputs before work; return only declared output ports with values matching their types; run long work as a cooperative job (`job.pending` with monotonic progress) instead of blocking; honor cancellation and the host timeout; keep persistent state in `manifest.state` (module Blocks: `stillmade.storage`) with migration routes so a reopened project and a repeated run behave the same; never leave orphaned jobs, temporary values or corrupted project state after cancellation or failure.
### Useful failures
Errors must say what failed, why it likely failed, whether a retry makes sense and what the user can do, so the native project chat and controller can troubleshoot. In JavaScript, throw an `Error` with that message: the host reports it as a `JAVASCRIPT` run failure carrying your message (guest code cannot set its own error code). Reject invalid or oversized input up front with a message naming the input and the limit. Never reduce a failure to "Something went wrong".
### Adapt to the platform
Do not modify StillMade core because a request does not fit immediately. First implement the capability entirely through documented SDK and platform primitives. If that is impossible, classify why: A Block implementation problem (fix it), B missing SDK exposure of an existing host capability, C missing generic platform primitive, D security or permission restriction, E runtime or language incompatibility, F fundamentally incompatible capability. For B or C, name the smallest general primitive that would serve this Block and a meaningful class of future Blocks (shaped like `files.watch()` or `media.transform()`, never `supportThisSpecificBlock()`). Return `{"unsupported":"exact reason","gapClass":"B|C|D|E|F","primitive":"..."}` instead of a package that pretends to work.
### What StillMade verifies
StillMade runs the readiness check on every Block it admits: manifest and contract validation, the source security scan, every fixture in the sandbox, wrong-type and over-limit inputs, declared output types, cancellation and timeout cleanup, state reopen and repeated runs, composition with a real upstream and downstream catalog Block through the resolver, a Project Type preview, the shared-interface contract with two participants editing a custom view in the real preview runtime (their edits must converge and a read-only participant must not write), and phone and desktop layout. Design for all of them and include normal and edge-case fixtures (1 to 50; stateful Blocks also declare `expectedState`). Test materially different inputs within the declared capability, and document limitations instead of overclaiming generality. Two of these checks need StillMade itself: two-participant shared editing and the phone and desktop UI profile run in StillMade's preview runtime when the Block is imported or released, so `ready` in the downloaded SDK reports them as not verified rather than passed.
### Project Types
A Project Type is an ordered production workflow. Each Step's primary output must be a compatible input for the next Step, secondary values come from permitted project context, and every member Block obeys this contract. Install Blocks into a realistic Project Type: project chat must understand each Step, inputs must resolve intuitively, outputs and execution state must be visible, failures must be diagnosable, and the user must be able to continue through the workflow.
### Readiness report
Finish with a Block Readiness Report: capability, contract (inputs, outputs, permissions, dependencies), platform APIs used, tests actually performed and their results, the composition test (upstream, Block, downstream), known limitations, unsupported requirements and any platform gap with its smallest general primitive. Choose exactly one status: PRODUCTION READY (every applicable check passed), READY WITH DOCUMENTED LIMITATIONS (works reliably within stated boundaries), BLOCKED BY PLATFORM CAPABILITY (needs a missing generic primitive) or FAILED. Never label a Block production ready to satisfy a request, and never claim a check ran when it did not.
### Definition of done
A Block is done only when every applicable item holds. Passing a compile, one unit test, a rendered UI or one happy-path sample is not done.
- DOD-01 Installs through the normal Block mechanism
- DOD-02 Manifest and contracts validate
- DOD-03 Runs through the normal runtime
- DOD-04 Required permissions are declared
- DOD-05 Required dependencies are reproducible
- DOD-06 Real inputs work
- DOD-07 Invalid inputs fail correctly
- DOD-08 Outputs conform to their declared types
- DOD-09 Outputs are usable by compatible downstream Blocks
- DOD-10 Universal resolver integration works
- DOD-11 Applicable project context works
- DOD-12 Applicable persistence works
- DOD-13 Applicable multiplayer behavior works
- DOD-14 Cancellation and failure cleanup work
- DOD-15 UI works in relevant states and screen sizes
- DOD-16 No hidden developer setup is required
- DOD-17 Security boundaries remain intact
- DOD-18 Relevant integration tests pass
- DOD-19 Works after application restart or project reopen
- DOD-20 A realistic Project Type execution succeeds when applicable
### Platform gap classes
- A: Block implementation problem
- B: Missing SDK exposure of an existing host capability
- C: Missing generic platform primitive
- D: Security or permission restriction
- E: Runtime or language incompatibility
- F: Fundamentally incompatible capability
<!-- builder-contract:end -->
## Build new Blocks directly with the SDK
When the user supplies an idea (for example, a photo editor) and this guide, build
an original StillMade Block from scratch. A GitHub repository is not required.
Create the SDK folder as the working product from the first iteration:
`stillmade.block.json`, `src/run.js` (or the documented declarative runtime file),
`tests/fixtures.json`, and optional `src/view.html`, `src/view.css`, `src/view.js`.
Implement the task through typed SDK inputs/outputs and the `StillMade` UI bridge.
The exact finished folder must import with **Choose folder** without source edits
or adaptation. Keep development tools and extracted SDK examples outside it.
After extracting `/block-sdk/sdk-docs.zip`, `node packages/block-cli/cli.js create
my-block javascript` scaffolds an isolated-JavaScript Block (`create --list` shows a
template for every runtime and hosted operation). Edit that source,
then run `validate my-block` and `test my-block` with the same CLI; add `--json`
for machine-readable results. Blocks that use hosted operations, files, network,
connections or storage test offline with `test my-block --mock` (recorded
stand-ins, or answers recorded in `tests/mocks.json`); a stand-in pass is never a
live verification. For an original
downloadable artifact run `pack my-block my-block.stillmade-block --original`.
Before delivering a plain original source folder, run `pack` with `--original`
into a temporary path outside that folder: original packing checks admission requirements
(including explicit semantic/required declarations) that ordinary `validate`
does not fully cover. Packaging for this check does not require changing the
chosen folder delivery format. An already-portable folder uses ordinary `pack`
without `--original`; keep its existing evidence aligned with its source. Include normal and edge-case fixtures and complete applicable license notices.
The folder remains a supported direct import; packaging is an alternate delivery
format, not a conversion to another implementation. Portable folders may use the
same split interface files or `src/view.json`, never both.
For a photo editor, implement pixel transformations in `src/run.js`, return typed
image outputs, and show images through `StillMade.previewImage`. The UI calls
`StillMade.run` and shares edits using the documented shared-interface contract.
Observe saved image outputs through `onShared`; display them with
`StillMade.previewImage` just as you display run results. In saved Steps, shared
write promises wait for an acknowledged save before the run flush barrier proceeds.
Use host-controlled output delivery. Do not first build an unrelated React/Node
website and leave a future adaptation task to the user. When the requested feature
requires an unsupported runtime or host service, identify that specific SDK gap
before claiming the finished Block can import or run.
The repository-inspection and adaptation instructions below apply only when the
user actually supplies existing source to reuse. For an original Block, follow
the runtime and interface contracts directly; do not invent repository evidence.
## Shared interfaces are required
<!-- shared-view:start (generated by npm run build:sdk; do not edit) -->
### Shared project interfaces
Every meaningful edit and generated result must be visible to collaborators.
Prefer ordinary manifest.ui controls and declared outputs when those express
the interaction. Repository-local DOM state is not automatically shared.
For a custom view, observe StillMade.onShared(shared => ...) to render saved
outputs (including another participant's result), interface state and canEdit.
For an ordinary static control, add a unique id and
data-stillmade-share="fieldName"; the host owns its complete synchronization
adapter. Use StillMade.bindShared(fieldName, elementId) only for a control created
or replaced dynamically. Account for every ordinary static control; mark a truly
device-only playback or filter control with data-stillmade-local.
Use StillMade.setSharedDefaults for input-derived defaults without replacing a
dirty draft. Do not overwrite bound control values during remote rendering or
replace a bound input during a gesture. For structured interface values, use
bounded StillMade.updateShared patches with stable named fields; observe them
through onShared instead of keeping the only copy in a private variable.
For nested objects, updateShared(values, exactBasePreconditions, {merge:true})
can merge independent fields and arrays with stable string item IDs. Always use
the actual base used to construct the edit. Overlapping changes still conflict;
this does not merge simultaneous text typing or expand the 384 KiB state limit.
Requests including before-values are limited to 800,000 UTF-8 bytes; queued and
in-flight requests share a 2 MiB budget. Preserve drafts when a limit is reached.
Give dynamic rows and controls stable IDs derived from item identity, not array
position. StillMade.render preserves matching nodes, focus and selection across
updates; it does not merge conflicting values or preserve an unshared draft.
Never dispatch a run, click or paid integration in response to a remote update.
Respect read-only access, preserve conflicting drafts, and surface write errors.
Only explicit user actions may run this Block. Shared acknowledgements are
locally queued, not proof of a durable server save. Preview localOnly state is
not proof of multiplayer operation. Test two interfaces exchanging actual edits
and saved outputs, permission loss, reconnect and conflicts before claiming
collaboration support. Never claim arbitrary imported JavaScript state syncs
merely because the package passed its computation fixtures.
<!-- shared-view:end -->
New custom interfaces must subscribe through `StillMade.onShared(callback)` and
publish every editable field: `data-stillmade-share` on static controls,
`StillMade.bindShared(key, elementId)` on dynamic ones, or
`StillMade.updateShared(values, before, options)` for structured values; private
JavaScript variables and DOM values are not shared automatically. This applies equally to original,
remixed and GitHub-adapted Blocks. Observe shared outputs as well as state, and
respect `canEdit` in every mutating callback. Never replay a generation when a
remote update arrives. Playback, hover and device microphone selection may stay
local. Use direct SDK calls so the minimum source check can recognize them.
The SDK/local-admission source gate rejects interfaces missing a shared subscription
or detected editable controls missing publication calls. Passing it does NOT
verify reachability or coverage of every control. Test two separate interfaces:
each editable field, remote results, read-only access, independent simultaneous
changes, conflicting same-field drafts, reconnect and pending-edit navigation.
Do not describe fixture execution or the source check as realtime acceptance.
Use the complete shared interface API and limits in SDK.md. Preserve old pinned
releases; publish a new version when adding this contract.
Hosted creation/import paths also enforce this minimum source check; it does
not substitute for actual interface interaction tests.
## Mandatory layout and review rules
Use the available workspace width with normal gutters, not a centered modal-like
card. Do not nest decorative boxes. Import/admission check lists start collapsed:
show passed, needs-attention and untested counts plus remaining actions up front.
Keep actual failures and required approvals visible; never relax validation.
These are requirements, separate from optional visual conventions.
## Updating existing Blocks through the SDK
“Update these Blocks using the SDK” means edit their actual owned implementation
and preserve their SDK contracts—not reskin a host feature or make a separate
demo. This procedure applies to first-party and creator Blocks alike.
### Mandatory update procedure
1. Resolve each requested Block's actual identity, installed version, package
source, and workflow placement. Read its manifest, implementation, custom view,
typed inputs/outputs, state schema, permissions, dependencies, fixtures, and
license/lineage before editing. Inspect the adjacent Blocks' contracts when
changing a handoff. A name or screenshot is not enough to establish compatibility.
2. Identify the smallest change that fixes the user's task. Preserve working
behavior, user media, draft state, selections, saved outputs, and approval
boundaries. Keep the existing Block-owned implementation; do not replace it
with a wrapper importing application internals or a new host-only branch.
3. Apply the mandatory interface rules in `INTERFACE_DESIGN.md`: one task surface,
normal reachable scrolling, no sticky media/header by default, no repeated
identity/navigation, no decorative boxes inside boxes, and no unsolicited
technical-result/source panels, even collapsed. Show the real task output.
Keep necessary approvals and failure feedback. Show only relevant controls;
secondary task controls may use a clearly named disclosure, not a mystery gear.
4. Use declared SDK context, typed results, sandbox execution, and supported host
services. Missing capability is a specific SDK gap to report or implement when
authorized—not permission to bypass the sandbox, credentials, billing, or
review. A UI change does not authorize paid generation or a production run.
5. Preserve immutable published source and installed pins. Create a new release
for a changed published Block; retain its listing identity only with publishing
authority. Otherwise use the normal remix identity and lineage. Preserve old
releases and specify any state/input migration before upgrading. Do not silently
repin saved projects. Use the ordinary explicit update/import review path.
6. Review the final package against its actual upstream/downstream contracts and
include fixtures for changed behavior. Run SDK validation, fixtures, and browser
review only when authorized; never disable runtime admission. Distinguish source
inspection, checks actually run, and interactions actually exercised. Record
untested scrolling, rendering, saved-state compatibility, or integrations.
7. Report each Block's old/new version, changed behavior, compatible contracts or
migrations, and whether it is merely authored, imported, installed, or applied
to this project. A committed source change is not proof that the preview uses it.
When parallel agent work is authorized, give each agent a distinct Block or shared
SDK/host responsibility and this same contract. Agree shared input/output changes
before integration. Parallel authoring does not mean executing dependent workflow
steps simultaneously; keep Frame Review approval before Motion Designer consumes
those frames. Do not dispatch paid calls or replace pins merely to exercise an update.
### Enforcement versus conventions
The procedure above and `INTERFACE_AUTHORING_RULES` are mandatory instructions for
authors, not a claim that the SDK can automatically inspect every design choice.
`INTERFACE_DESIGN_RULES` lists implemented static admission checks separately;
`INTERFACE_DESIGN_CONVENTIONS` contains recommendations. Passing checks does not
prove useful visuals, unclipped scrolling, or a working end-to-end workflow.
## Host-controlled exports
Return typed outputs or a `file-bundle`; StillMade owns every download, media
export and external delivery action. Never create a download link, invoke a
save-file picker, or add a separate export, referral or payment bypass. Upload
admission requires the **Host-controlled exports** check (`host-export-1`). The
host can apply a referral step, payment or another access requirement to every
creator's Block without changing Block code. Creator pricing is separate and
cannot override this policy.
Completed project MP4 and MP3 files use the SDK `CompletedExportRecord` shape.
StillMade creates that record after delivery and places it in Library → Full
Exports. A Block must not write export-history records or classify intermediate
media as a completed export.
## Keep the working UI focused on the task
The manifest name and description are listing metadata. Keep them accurate and
concise, but do not turn them into a hero, marketing introduction, or large empty
header inside the Block. Put the actual inputs, primary action, and useful output
first. StillMade already provides a compact name and an About disclosure around
the sandbox. Do not repeat that framing inside a custom view. Use short labels
and explain only what helps someone complete the task; put optional help behind
a disclosure. Apply this to local SDK previews as well as imported Blocks.
Treat transport and source as implementation detail. MCP, JSON, schemas, ports,
prompts, and logs must not become the main experience unless editing source is
the Block's advertised job. Lead with the object being made and one obvious
primary action. Prefer visual cards, previews, timelines, and direct manipulation.
Keep peer choices to seven or fewer; use a strong default, grouping, search, or a
labelled disclosure for the rest. Put advanced task controls behind progressive
disclosure. Do not add diagnostic controls unless explicitly requested or part
of an explicitly source/debug-oriented task; collapsing them is not an exception.
Use recognizable line icons for familiar actions and objects, following the
StillMade icon language. Never use emoji or decorative circular icon backgrounds.
Icon-only controls need an accessible name, tooltip, keyboard focus, and a generous
hit area; retain visible text when the symbol could be unclear. The host already
shows the Block name, so do not repeat it as a large hero inside the custom view.
SDK admission reports `Task interface design` and rejects a custom workspace that
is dominated by a large raw-text editor without a task visualization.
## Understand StillMade and its Block ecosystem
StillMade AI is an AI-assisted platform where people make things by using and
combining reusable tools called Blocks. Its current production tools include
script writing, voiceover, shot planning, Blueprint references, Canvas, image
panels, video assets, and editing. These are examples of existing capabilities,
not a limit on what users may want to build. The goal is an inviting, easy-to-use
workspace where AI helps people make and adapt useful tools without writing code
or manually wiring technical ports.
A Block is one independently versioned tool with its own implementation, interface,
and declared SDK contract. A Step is an instance of a Block placed in a workflow.
A Project Type is a reusable workflow made from those steps and their settings.
A project is the user's actual work using that workflow, with its own inputs,
media, state, and results. Chat is available when planning is useful; it is not
required to be the first step of every workflow. Blank Canvas starts in Canvas.
Do not confuse creating a reusable Block with creating one user's project output.
Your Block will join other first-party and creator-made Blocks in StillMade. Before
building, identify what the user needs that existing tools do not already provide.
When network access is available, resolve these paths against the public origin
of the SDK URL the user supplied, not a guessed production hostname or localhost:
- Browse `/marketplace` for user-facing examples.
- Search `/api/public-listings/discover?kind=block&search=<encoded-query>` for
Blocks, or use `kind=type` for Project Types. Follow `nextCursor` with `after`
when more results are relevant; a single page is not the complete catalog.
- Use returned listing identities and versions to inspect
`/api/public-listings/<kind>/<id>/<version>/source.json` where source inspection
is permitted. Read the real manifest and any declared dependency packages.
- In an offline SDK checkout, `packages/block-platform/catalog.js` provides bundled
first-party examples. It is a snapshot, not the complete live Marketplace.
Existing Blocks can inspire the design or provide neighboring workflow steps.
Reuse or remix permitted source when it fits the user's request; preserve its
license, attribution, lineage, and pinned version. A listing is not permission to
copy restricted source. Do not silently substitute an existing tool for the
custom behavior the user asked for. Explain useful existing options briefly and
continue toward the user's chosen result. If discovery is unavailable, say so
and avoid claiming the Block is unique or compatible with an uninspected tool.
Design the new Block to cooperate through declared inputs, outputs, semantic
roles, and authorized project context. Describe a practical workflow using real
Blocks, then verify matching contracts and supported connections with the SDK.
Names such as "script" or "image" alone do not establish compatibility. Respect
required fields, scalar versus array types, media references, permissions, and
runtime availability. Pin versions; never invent Block IDs, ports, host services,
or an unrestricted call to another Block. A useful standalone Block is acceptable
when no compatible neighbors exist—do not fabricate a workflow for appearance.
StillMade supplies the shared host: identity, navigation, storage, permissions,
billing, and execution. The Block supplies its capability and UI through the SDK.
Keep the runtime, sandbox, source/license, input/output, import, and spending
constraints below authoritative. Knowing the product does not relax them. A local
preview or available Marketplace listing does not grant credentials, paid execution,
or extra host access. Explain what this Block does, where it fits, and any real
limitations in language the user understands while iterating with them.
## The goal: a Block the user wants to use in StillMade
You are collaborating with the user to make a useful Block that will be imported
into StillMade. A running preview, passing tests, or a packaged archive alone does
not finish the task. The Block should do what the user needs, feel understandable
to them, and be something they want to keep using in their StillMade projects.
Keep the conversation going toward that outcome. Understand the result the user
wants and how they expect to use it. Ask short, concrete questions when a missing
answer affects the Block's behavior; make reasonable reversible choices and keep
building when you can. Explain each meaningful iteration in terms of what the
user can now do. Once a preview is available, invite them to try it and tell you
what feels wrong or is missing. Apply their feedback to the same Block and repeat.
Do not treat the first working version as accepted or stop at a technical handoff.
Do not require an unnecessary questionnaire or keep asking for approval of routine
edits. Respect an explicit request to finish, pause, or deliver without more review.
Throughout development, preserve the actual StillMade SDK contract: editable
Block source, typed inputs and outputs, its real interface, supported runtime,
declared permissions, and the required import evidence. The local preview exists
to help the user shape this Block; it is not a separate website or the final product.
Do not build a nice standalone demo that cannot become the requested StillMade Block.
If the user's desired behavior cannot be imported or run under the supported
contract, explain the specific gap early and work with them on a supported option.
Never silently replace their goal with an easier example.
When the user is happy with the result or asks to finalize, prepare the importable
`.stillmade-block` from the source they just reviewed. Perform the checks permitted
by the environment and the user's instructions; report any unfinished or unverified
behavior honestly. Deliver the Block for use in StillMade, not just a preview URL
or instructions to rebuild it themselves. The single-artifact final-delivery rule
does not prohibit questions, progress updates, preview links, or iterative discussion
while you are working together.
## Start a local preview early, then iterate
The default authoring experience is a working preview before the final import file.
As soon as the first runnable Block exists, launch its frontend and any required
local backend automatically when your coding environment supports running servers.
Do not wait until packaging, or make the user start the servers manually when you
can do it. Open the browser/IDE preview if supported and give the user the working
local URL. Keep the processes running while the user tries it and asks for edits.
Use the coding tool's existing preview environment where available. Otherwise,
create a small development-only preview harness around the actual Block source,
its declared input controls, and its outputs. Use the supplied SDK sandbox and
view bridge for execution and custom interfaces; do not run Block code with eval,
Node imports, or a new unrestricted execution endpoint. The preview must show the
real interface and results, not a screenshot, fake success, or a separate mock app.
A browser-only Block does not need a backend merely for appearance. If one is
needed to host the SDK or serve the preview, start it together with the frontend
using one documented development command. Keep that command reproducible.
The existing CLI command `node packages/block-cli/cli.js preview <package> <input.json>`
runs a sample and returns JSON. It is not a browser server and does not launch a
frontend or backend. Do not invent an SDK `serve` command. A separate preview
harness is development tooling; keep it outside the final portable Block package.
For a custom view, render the actual authored view through the SDK's existing
sandboxed view contract, with controls connected to real validated inputs/outputs.
Bind development servers to loopback by default, choose available ports, and
publish the actual URL only after both UI and required backend are ready. Use the
coding environment's normal preview forwarding if necessary. Restart or reload
affected processes after edits so the user sees the current source. Never stop
unrelated servers or claim a preview was opened or exercised when it was not.
Run offline examples with ordinary sample inputs. Keep runtime admission, typed
input/output validation, sandbox isolation, and declared permissions intact.
Local preview does not authorize paid provider calls, credentials, or unsupported
host capabilities. If a capability needs StillMade services that are unavailable
locally, show that limitation in the preview; clearly label any illustrative sample
and do not report the provider-backed behavior as verified. Honor the user's
execution and testing restrictions.
Once the preview is ready, invite the user to try it and request changes. Apply
feedback to the same Block source and keep the preview available. If the user
already requested a final artifact without an interactive review, proceed after
available checks. Otherwise, package when they say it is ready to import. Then run
the permitted SDK validation, sandbox fixtures, and packaging checks against that
same final source and return the single `.stillmade-block` artifact. The rule to
return one artifact applies to final delivery, not progress messages or preview URLs.
If the environment cannot run servers or open a browser, explain the precise
limitation and provide the exact local startup command instead. Do not describe
an unavailable preview as running. Preview availability is not proof that all
features or fixtures passed; report what was actually exercised.
Every finished Block must pass SDK **Remix readiness**: retain editable source,
typed I/O, controls, tests and complete license evidence; do not disable remixing
or hide source. No custom remix integration is needed. See [the remix contract](/docs/tools/block-remix).
Use this public SDK with a GitHub repository URL to develop outside StillMade. Follow [the complete portable contract](/block-sdk/PORTABLE.md)
and return ONE `.stillmade-block` archive. Independently inspect the repository,
licenses, implementation and dependencies; infer a useful capability without
requiring extra StillMade instructions or asking the user to open the app.
The JSON examples below describe the existing in-memory package and runtime
contracts. They are development source, not an alternative final deliverable for
repository adaptation. Add portable evidence/lineage, validate with the shared
CLI, and package the archive. No extra wrapping response object or setup steps.
Never claim tests passed unless the validator and sandbox actually ran them.
Unsupported runtime or unresolved licensing produces a clear blocked result.
A Block is a capability; a Step is its placement in a Project Type. Protected
starting Chat cannot be replaced. Use the existing sandbox and SDK interfaces.
Do not implement a new application shell or require manual connection wiring.
## Exact package shape
Use a standalone JavaScript Block:
{ "manifest": { "schemaVersion": 1, "sdkVersion": "0.1.0", "id": "creator.my-block", "version": "1.0.0", "name": "My Block", "description": "One concise sentence explaining its input, behavior and output.", "kind": "task", "runtime": "javascript", "license": "MIT", "inputs": { "text": {"type": "text", "primary": true} }, "outputs": { "text": {"type": "text", "primary": true} }, "permissions": {"project": [], "network": [], "filesystem": [], "secrets": []}, "ui": [{"control": "text", "port": "text"}] }, "code": "return {text: input.text.trim()};", "tests": [ {"name": "Trims surrounding spaces", "input": {"text": " Hello "}, "expected": {"text": "Hello"}}, {"name": "Handles empty text", "input": {"text": ""}, "expected": {"text": ""}} ] }
The example demonstrates the format; implement the requested capability instead.
The top-level keys for this format are `manifest`, `code`, `tests`, and optional `view`.
Do not add `dependencies`, `packageJson`, `handler`, `run`, or a `recipe` field.
The code is a JSON string containing a synchronous **function body** receiving the
variable `input`. Do not include `export`, a module wrapper, a function declaration
around the whole body, or a Markdown fence inside the string. Return an object whose
keys exactly match declared outputs. Escape embedded quotes/newlines as valid JSON.
Manifest IDs use a lowercase namespace and name, such as `creator.grayscale-image`.
Do not use `sm.*` or `stillmade.starting-chat`. Versions are `major.minor.patch`.
Names are 1–120 characters; descriptions are 1–1,000 characters. For original code
in this exercise use MIT. If adapting someone else's source, preserve their license
and attribution rather than assuming you can relicense it.
Visibility and private team sharing are configured in StillMade after release,
not inside the package. Never add team IDs, sharing grants, credentials or API
tokens to source. A team can receive an exact private released version without a
public listing; its members still review and confirm installation. Previously
delivered source cannot be retracted. Preserve third-party licenses, including
any restrictions on distributing source to a team.
## Runtime constraints
JavaScript runs in isolated QuickJS, not Node.js or the browser's JavaScript engine.
Ordinary synchronous JavaScript, arrays, objects, strings, and `Math` are available.
No network, DOM, files, modules, shell, timers, promises, or installed dependencies.
Do not use `fetch`, `require`, `process`, `globalThis`, `window`, `document`, `eval`,
`Function`, `WebAssembly`, dynamic constructors, `setTimeout`, or import/export.
Do not invent a `ctx` object or SDK host service. Do not return typed arrays, class
instances, `undefined`, NaN, Infinity, promises, or functions; use JSON values.
Computation code also has `StillMade.emit(outputName, value)` and
`StillMade.resolveRequirement(inputName)`. Emit only declared output names once,
and do not also return outputs or a cooperative pending result. The requirement
reader returns `{resolved:true, requirement, value}` from the fixed host-validated
input snapshot, or `{resolved:false, requirement}` for an absent optional input.
It does not query new sources, ask Chat or users, access credentials, or write a
project.
`StillMade.getProjectContext()` returns an isolated snapshot containing only
declared context-bound inputs, keyed by their `context` field names. Pass a
declared field name to read one value; an undeclared field throws, and an absent
optional value returns `undefined`. This is not unrestricted project access or
a live lookup. Host permissions still apply.
`StillMade.validate(value, schema)` returns a boolean using the shared supported
JSON/schema rules. It does not verify semantic meaning, permissions, provenance,
or media contents, and never replaces final host output validation. These methods
are not provided to `view.javascript`. Isolated computation code (not the view)
also has `StillMade.requestFromUser`, `StillMade.requestFromChat` (see
UNIVERSAL_RESOLVER.md) and `StillMade.patchProject` (see JAVASCRIPT_RESULTS.md);
none of these exist in `view.javascript`, and no other guest methods exist.
Persist custom semantic inheritance in `manifest.semantics`, for example
`[{name:"creator.example.spoken_copy",parents:["narration_script"]}]`.
Use namespaced names, at most 64 definitions per Block, eight parents per name,
and 500 description characters. Do not override standard meanings or introduce
cycles/conflicting parent definitions. Preserve the inherited meaning of existing
input/output contracts when refining a base; adding a parent also changes the
contract even if its semantic label stays the same. New independent ports may
declare new meanings. Changing a published meaning requires a new release.
Limits: 256 KiB source, 500 ms guest CPU time, 16 MiB guest heap, 32 MiB WASM memory,
4 MiB serialized input/output. Use small fixtures and bounded algorithms. A Block
requiring Python, models, network APIs, or unavailable dependencies cannot
be implemented by this standalone JavaScript runtime. Hosted text generation, speech and image generation can use the separate declarative capability formats below. For other unavailable services, explain the limitation instead of
pretending those APIs exist or returning a successful-looking placeholder.
## Typed inputs and outputs
Each input/output is a named port. Names start with a lowercase letter and contain
only letters, numbers and underscores. Every input of a new Block needs `semantic`,
`required` and `description`; every output needs `semantic` and `description`.
`validate` and `test` check structure and fixtures only; `pack --original` and
`ready` run full admission, which rejects a port missing these fields.
<!-- port-fields:start (generated by npm run build:sdk; do not edit) -->
Every port field the SDK accepts (generated from the validator; see [port fields](/docs/reference/ports)):
| Field | Meaning |
| --- | --- |
| `type` | Required. A shared StillMade type such as `text`, `image`, `shot-plan`; add `[]` for a list (`image[]`). |
| `required` | Required on every input of a new Block: `true` or `false`. A required input needs a generated control, a `context` binding or a `default`, or `pack` and import reject it. |
| `default` | A JSON value matching the type. Satisfies a required input when nothing else supplies it. |
| `min` | Lowest allowed value for a `number` or `integer` port. |
| `max` | Highest allowed value for a `number` or `integer` port. |
| `description` | Required on every input and output of a new Block. One short plain-language sentence about what the value is for. |
| `role` | Legacy distinct role (for example `character_reference`). Use only when the meaning must not mix with others; downstream roles must match exactly. Prefer `semantic`. |
| `primary` | Mark exactly one main input and one main output `true`; Project Type Steps connect primary ports automatically. |
| `context` | The project field this input reads (for example `script`); requires the matching `context.FIELD.read` permission. |
| `imageMode` | JavaScript image inputs only: `pixels` delivers decoded `{width,height,data}`; `reference` delivers the media reference without pixels. |
| `key` | Set by the host for resolver requirements. Leave unset in manifests. |
| `semantic` | Required on every input and output of a new Block. The exact meaning in lowercase snake_case or dotted form (`company_profile`, `production_shot_plan`). Downstream matching uses it; different meanings never connect. |
| `accepts` | Additional meanings this input also accepts, besides its own `semantic`. |
| `schema` | Required on every `json` or `object` port (admission rejects one without it). Not JSON Schema: a field-type word or a map of field names to field-type words, nested for objects. Words are `string`, `text`, `number`, `integer`, `boolean`, `object`, `json`; add `[]` for a list and `?` for an optional field, for example `{title:"string",tags:"string[]?",size:{width:"integer",height:"integer"}}`. |
| `sources` | Where the universal resolver may fill this input from: `upstream`, `project`, `adapter`, `chat`, `default`, `user`, `external`. Omit to allow all. `default` covers only the declared default; values a person enters in the Step or a view passes to `StillMade.run` count as `user`, so leave `user` allowed for anything the interface fills in. |
| `sensitivity` | A label for sensitive information; values marked secret, credential, password or token are never offered in chat. |
| `freshnessPolicy` | `{maxAgeMs}`: the resolver rejects values older than this. |
| `cardinality` | `one` or `many` values. |
| `batchable` | `true` when the input can run once per item of a selected media batch. |
<!-- port-fields:end -->
Simple types: `text`, `number`, `integer`, `boolean`, `json`.
Media types: `image`, `video`, `audio`, `asset`.
Production types: `script`, `scene`, `shot`, `character`, `location`, `style`,
`timeline`, `mask`, `depth`, `pose`, `metadata`, `project-context`, `transcript`,
`brief`, `bible`, `shot-plan`, `panel-document`, `board-document`, `scene-document`,
`timeline-range`, `research`, `pipeline`, `approval`.
Arrays use a `[]` suffix, such as `shot[]` or `text[]`.
Text values are strings. Numbers must be finite, and integer values must be whole.
`json` accepts bounded plain JSON. Structured production values are objects with
`schemaVersion: 1`; define/document the fields your capability requires. Do not
invent fields supplied by StillMade. Media references are JSON objects like
`{"kind":"image","assetId":"asset-1","versionId":"v1","url":"..."}`;
they are references, not permission to fetch/read media inside guest code.
Declare your capability's actual types. StillMade can connect a media reference to
`asset`, check an `asset`'s real kind before using it as image/video/audio, resolve
a scene to its authorized ordered shots, or use a shot's selected production image.
These host conversions preserve semantic roles and require the receiving Block's
context permissions where applicable. They do not decode video, invent missing
images, or select a character reference as production media. Do not implement
manual wiring or claim a conversion that your declared contract does not support.
The full SDK reference documents these as **Safe connection conversions**.
### Deterministic image processing
For a pixel-processing Block, declare type `image` and use decoded RGBA values:
{"width": 2, "height": 1, "data": [255, 0, 0, 255, 0, 0, 255, 128]}
Width/height are positive integers; data is a plain array of exactly
`width * height * 4` integers in `[0,255]`, row-major RGBA order. The format allows
at most 1,048,576 pixels, with the tighter serialized/memory limits above still
applying. Use tiny fixtures (1–4 pixels where possible). Return the same decoded
shape for image outputs. Allocate new output data; do not mutate input pixels.
Preserve dimensions and alpha unless the requested behavior explicitly changes
them. The production host handles media decoding/storage around the sandbox.
### Project context (only if the requested behavior needs it)
An input can request one of: `script` (type `script`), `shots` (`shot[]`),
`scenes` (`scene[]`), `characters` (`character[]`), `locations` (`location[]`),
`styles` (`style[]`), `assets` (`asset[]`), `generations` (`metadata[]`),
`versions` (`metadata[]`), `timeline` (`timeline`), or `metadata`
(`metadata`). For example an input
`"shots":{"type":"shot[]","context":"shots"}` requires
`permissions.project` to include `"context.shots.read"`. The host supplies a copy
at that named input; code still reads `input.shots`. Fixtures must provide the same
value explicitly. Request only the needed fields. Do not invent write APIs.
The host indexes saved Canvas, panel/board, voiceover and editor documents. Media
references preserve Canvas IDs and approved versions; `assets` contains current
media, while `versions` contains retained media and document-version metadata.
A media history record is `{schemaVersion:1,id,assetId,versionId,asset,source,active}`;
`asset` is a normal media reference. Document history records have `documentType`
and `versionId` but no `asset`. `generations` contains saved generated-media history
with `status:"completed"`; optional saved model/timestamp/duration may be absent.
For a scene/shot/asset selection, the host provides only related context: scoped
shots and scene `shotIds`, selected narration, selected timeline collections and
related visual references. It excludes unrelated transcript, timeline history and
unowned media. `metadata.scope` describes this selection. Packages cannot widen it.
Canvas-selected references remain the existing character-consistency authority;
do not create a competing identity store. Each new metadata binding needs its own
exact permission, such as `context.versions.read`.
## Controls
`ui` is an array of `{ "control": "...", "port": "inputName" }` objects.
Control must be `text`, `number`, `slider`, `checkbox`, or `asset`, bound to an
existing input. Labels come from port names/descriptions. For a numeric slider,
give its input `type`, `min`, `max`, and `default`. Use `asset` for an image input.
For a custom visual interface, add the optional `view` object below. Do not supply React components, external dependencies, or a hosted UI URL.
## Custom Block interfaces
A package can include an optional **`view`** beside `manifest`, `code`,
and `tests`. This is the Block's real interface in Preview: a user edits controls,
presses its action, and sees the code's result. Packages without `view` use
StillMade's generated controls and output viewer. `manifest.ui` remains the
fallback controls schema; it is not the custom interface source.
<!-- view-rules:start (generated by npm run build:sdk; do not edit) -->
Custom view code is checked before it runs. These rules come straight from the validator:
- **HTML elements:** only `div`, `section`, `article`, `header`, `footer`, `main`, `aside`, `nav`, `h1`, `h2`, `h3`, `h4`, `p`, `span`, `strong`, `em`, `i`, `small`, `br`, `hr`, `label`, `input`, `textarea`, `button`, `select`, `option`, `optgroup`, `fieldset`, `legend`, `output`, `progress`, `meter`, `ul`, `ol`, `li`, `dl`, `dt`, `dd`, `figure`, `figcaption`, `img`, `video`, `audio`, `source`, `canvas`, `table`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td`, `pre`, `code`, `details`, `summary`. No scripts, iframes, SVG, forms, comments or inline event attributes; media URLs must be `data:` or `blob:`.
- **Never use these names in view JavaScript:** `eval`, `Function`, `AsyncFunction`, `GeneratorFunction`, `require`, `importScripts`, `process`, `globalThis`, `window`, `self`, `top`, `parent`, `opener`, `frames`, `location`, `navigation`, `history`, `navigator`, `localStorage`, `sessionStorage`, `indexedDB`, `caches`, `cookieStore`, `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`, `Worker`, `SharedWorker`, `ServiceWorker`, `WebAssembly`, `Deno`, `Bun`, `Reflect`, `Proxy`, `DOMParser`, `MutationObserver`, `postMessage`, `open`, `close`, `print`, `alert`, `confirm`, `prompt`, `setInterval`, `setTimeout`, `queueMicrotask`, `arguments`, `constructor`, `__proto__`, `prototype`, `__lookupGetter__`, `__lookupSetter__`, `__defineGetter__`, `__defineSetter__`, `getPrototypeOf`, `setPrototypeOf`, `getOwnPropertyDescriptor`, `getOwnPropertyDescriptors`, `getOwnPropertyNames`, `getOwnPropertySymbols`, `defineProperty`, `defineProperties`, `defaultView`, `ownerGlobal`, `contentWindow`, `contentDocument`, `cookie`, `domain`, `documentURI`, `URL`, `baseURI`, `referrer`, `write`, `writeln`, `innerHTML`, `outerHTML`, `insertAdjacentHTML`, `createElement`, `createElementNS`, `createContextualFragment`, `createRange`, `setAttribute`, `setAttributeNS`, `attributes`, `attributeStyleMap`, `adoptNode`, `importNode`, `execCommand`, `replaceChildren`, `setHTML`, `setHTMLUnsafe`, `parseHTML`, `parseHTMLUnsafe`, `showSaveFilePicker`, `showDirectoryPicker`, `saveAs`, `FileSystemWritableFileStream`, `createWritable`, `msSaveBlob`, `msSaveOrOpenBlob`, `download`.
- **Never use these property names:** `assign`, `values`, `entries`, `fromEntries` (so no `Object.assign`, `Object.values`, `Object.entries`, `Object.fromEntries`; use arrays and `map`/`forEach`).
- **These words may not appear anywhere in view JavaScript, even in strings or comments:** `style`, `cssText`, `setProperty`, `removeProperty`, `insertRule`, `deleteRule`, `styleSheets`, `adoptedStyleSheets`, `animate`. Change appearance with CSS classes and `hidden`; put colors in the stylesheet.
- **Property access with brackets needs a literal key:** `items[0]` and `record["title"]` are accepted; `items[index]` is rejected. Iterate with `map`/`forEach` or call `items.at(index)`.
- **No named recursive functions** and no listeners on the global frame; attach events to declared controls.
- **Dynamic elements:** `StillMade.render` can create `div`, `section`, `article`, `header`, `footer`, `p`, `span`, `strong`, `em`, `i`, `small`, `h1`, `h2`, `h3`, `h4`, `label`, `button`, `input`, `textarea`, `select`, `option`, `ul`, `ol`, `li`, `table`, `thead`, `tbody`, `tr`, `th`, `td`, `details`, `summary`, `output`, `progress`, `img`; give repeated items stable IDs derived from their identity. An `img` gets its picture when you set its `src` to the data URL `StillMade.previewImage` returns, after rendering; the renderer keeps Block-owned `src`. `previewImage` works on items of an `image[]` input.
- **Shared writes are queued in order:** if one `StillMade.updateShared` write is rejected (conflict, read-only or timeout), the writes queued behind it are rejected too. Send dependent edits one at a time or through `StillMade.scheduleSharedEdit`, keep the person's draft, and retry from the latest `onShared` value. `{merge: true}` merges independent fields of an object value; create an absent field with the precondition `{exists: false}`.
- **Limits:** HTML 64 KiB, CSS 32 KiB, JavaScript 64 KiB; 30 runs and 60 image previews per minute.
<!-- view-rules:end -->
`view` optionally declares `appearance: "original"` or `"host"` and contains
`html` (required, up to 64 KiB), `css` (optional, up to 32 KiB),
and `javascript` (optional, up to 64 KiB). These are UTF-8 byte limits. For folder
packages, use separate `src/view.html`, `src/view.css`, and `src/view.js` files
(the last two are optional). Optional theme-specific CSS uses
`src/view.themed.css`. The older `src/view.json` object format is also supported,
but never combine it with separate interface files. CLI pack, folder import,
and ZIP import reconstruct the same `view` object and run the same validators.
Use **Choose folder** on the import page to select an edited SDK package directory.
`src/run.js` remains the production function; `src/view.js` controls the interface. No external framework, URL, npm module, or build step runs
inside the interface. If using React or another framework externally, adapt the
interface into these supported standalone controls before import.
### Interface bridge
The host provides `StillMade` inside the sandbox:
| API | Behavior |
| --- | --- |
| `StillMade.input` | Copy of the current preview input bindings after initialization |
| `StillMade.onInput(callback)` | Receives initial input and later host input changes; returns an unsubscribe function |
| `await StillMade.previewImage(value)` | Resolves a currently authorized image to a bounded, display-only PNG copy; preserves the original run input |
| `await StillMade.run(values)` | Validates the named input bindings, runs the package in the isolated computation worker, validates outputs, and resolves to the output bindings object |
Always use `onInput` to initialize controls; the interface may load before its
initial values arrive. Catch run errors and display them in the interface. Disable
the run control while a request is pending. One run can be active; the host limits
the view to 30 requests per minute, 4 MiB and 500,000 JSON nodes per message, and
15 seconds for local execution. Host-confirmed ComfyUI, text and speech requests
allow up to 6 minutes; image generation allows up to 15 minutes for confirmation
and its bounded fixture/sample sequence. These are interface wait budgets, not
permission to retry or extend provider deadlines. Raw RGBA messages must fit both
message budgets (roughly 124,000 pixels after reserving nodes for other inputs);
use smaller preview images.
The existing computation limits (including the JavaScript 500 ms guest deadline)
still apply. Reset discards the interface and aborts its pending computation.
Closing Preview disposes the frame and aborts outstanding work.
For a port named `prompt`, initialize its control from the host input callback:
const inputField = document.getElementById('prompt');
StillMade.onInput(input => { inputField.value = input.prompt || ''; });
Inside the async run button handler, call
`await StillMade.run({prompt: inputField.value})` and display its output.
Use `inputField` or `promptInput` for local variables; `prompt` is also a browser
API name and cannot be a local identifier. Its data-property exception applies
only to the single, unshadowed and unreassigned parameter of a direct inline
`StillMade.onInput` callback; declare that parameter name only once in the
interface source. Use ordinary `.prompt` access there, rather than
computed/destructured access or a property on an unrelated object. Object literal
`{prompt: value}` keys are supported. Browser dialogs remain unavailable.
Use the DOM for interaction and rendering: `document.getElementById`,
`querySelector`, `addEventListener`, `textContent`, `value`, `checked`, `disabled`,
`hidden`, and canvas are supported. Declare elements in `html`; show/hide existing
elements or draw into a declared canvas. Dynamic element creation, HTML insertion,
reflection, computed dynamic property lookups, module imports, browser navigation,
account APIs, parent/window access, and storage are rejected. Property access such
as `result.text` and `image.data[0]` is supported; use array methods for iteration.
Synchronous loops, function declarations, directly recursive helpers, timers, and
microtask queues are rejected. Every callback/helper also checks a private task
budget (10,000 calls or 50 ms between checks), including indirect recursion. This
guard cannot interrupt a single native browser operation, so it is not a
substitute for the computation worker. Use small native array operations such as `map`/`forEach` for
rendering; heavy work belongs in the isolated Block runtime.
Use `.textContent` for user text. Tags are ordinary controls, text, layout, media,
and canvas; no script, iframe, SVG, link, form, or document metadata tags. Markup
must use complete tags without HTML comments, inline event handlers, or navigation
attributes. JavaScript belongs in `view.javascript`, not the HTML string.
The frame has an opaque origin and `sandbox="allow-scripts"`, without same-origin,
popups, forms, downloads, or top navigation permissions. CSP denies connections,
workers, frames, external scripts, and resources. Image and media sources must be
local `data:` or `blob:` URLs. Styles cannot import resources. No token, account,
project mutation API, filesystem, or parent DOM handle is passed into the frame.
Only copied inputs, explicit run results, and authorized image display copies cross the bridge. Static checks
are an additional admission gate; they do not replace this browser boundary or
the separate computation sandbox. Keep the UI responsive: move computation into
`code`; DOM JavaScript is not the CPU-metered computation runtime.
A file input can read a user-selected image with `FileReader.readAsDataURL`, load
it into an existing `<img>`, and draw it into a declared `<canvas>`. Use
`getImageData` to pass `{width,height,data:Array.from(pixels.data)}` to an image
input, staying within the SDK image and message budgets. Nothing is uploaded by
the view. Use the host's file controls when you do not need a custom picker.
The host supplies StillMade's shared Light, Dark and White theme variables and basic controls.
For optional StillMade appearance, inherit its typography or use
`var(--font-body)` for controls and `var(--font-display)` with weight 200 and
italic style for display headings. Use shared spacing, radius and color tokens
such as `var(--bg)`, `var(--fg)`, `var(--fg-muted)`, `var(--border-tok)`, and
`var(--r-3)`. Original appearance may keep its own typography and colors within
the existing sandbox; importing external fonts or resources remains unavailable.
Use a clear input, primary action, and visible result. Preview runs
synthetic samples or the user's explicitly chosen input; it does not commit to a
production project. SDK fixture tests prove code behavior and input/output
compatibility. They do not claim to test browser interaction: review the actual
custom interface in Preview before **Confirm Import**.
### Appearance compatibility
StillMade has three modes: **Light** (cream), **Dark** (ink), and **White**
(neutral white surfaces with black text). Generated controls inherit the app mode.
A custom interface may keep its original design by explicitly setting
`view.appearance: "original"`. The surrounding StillMade app keeps its own theme.
Omitting this field, or setting it to `"host"`, preserves the existing requirement
to provide theme-compatible colors. Existing package sources and installed pins
are never rewritten to change appearance.
A minimal original-style view is:
{ "appearance": "original", "html": "<output id=\"result\"></output>", "css": "body{background:#14213d;color:#fff}",
"javascript": "StillMade.onShared(shared=>{document.getElementById('result').textContent=shared.outputs.text||'';});" }
Keep this object in `src/view.json` when using the explicit appearance field;
do not combine it with split `src/view.html`/CSS/JavaScript files. Folder, archive
and GitHub SDK-folder imports retain the field, as do source editing and export.
The stylesheets have separate purposes:
- `view.css` (up to 32 KiB) retains the original design. Original-style packages
use it inside projects as well as in Original design previews.
- `view.themedCss` (optional, up to 32 KiB) is a **complete replacement stylesheet**
for authors who also want StillMade appearance. Include the same layout and
responsive rules using host tokens. Preview offers this mode only when the
complete view passes the host-appearance check.
For example, original CSS can use `body { background:#14213d; color:#fff; }`.
Its optional themed stylesheet uses `body { background:var(--bg); color:var(--fg); }`.
Move fixed inline colors into the stylesheets when offering both appearances.
Never change image/video/canvas pixels to match the interface palette.
For StillMade appearance, use `var(--bg)`, `var(--bg-elev-1)`, `var(--bg-elev-2)`,
`var(--fg)`, `var(--fg-muted)`, `var(--border-tok)`, `var(--signal)`, and
`var(--shadow-1)`. The SDK exports `APPEARANCE_TOKENS`. Inherit host typography
or use `var(--font-body)` and `var(--font-display)`. Do not redefine host tokens
or use raw palette tokens such as `--cream-100` in theme-aware declarations.
Original appearance does not require these color or typography conventions.
**The appearance declaration does not widen the interface runtime.** Existing
HTML/JavaScript syntax restrictions, CSS restrictions on escapes, filters and
color blending, resource-loading bans, size limits, shared-state requirements,
permissions, CSP and typed input/output validation still apply. Use CSS classes
for interface changes; dynamic element-style mutation remains unavailable.
Static original colors are allowed only with the explicit original mode;
any optional themed stylesheet still needs valid semantic colors.
`scanPackage`, `admitPackage`, and CLI validation check the declared appearance
alongside all other admission gates. A styling pass does not prove working UI,
accessibility or full repository compatibility. Review the actual interface on
its declared devices and, when supplied, each themed mode. This option preserves
styling within the existing restricted view format; it does not load framework
bundles, arbitrary React applications, remote fonts or external scripts.
### Display an authorized image
Use **`await StillMade.previewImage(value)`** for an image selected in
StillMade, received from a connected Block, or present in explicitly granted
project context. The host also supports its registered sample pixels, local
uploads, and verified run outputs. A well-formed reference alone does not grant
access: a changed URL, version, role, unrelated asset, or expired preview session
is rejected. Catch and display the error; do not fall back to fetching the URL.
The result is exactly `{url, width, height}`. Its URL is a PNG data URL
that can be assigned to a declared `<img>`. It is a display copy, at most
512 pixels on either edge, with aspect ratio preserved. It does not generate,
upload, save, select, or replace an asset. Continue to pass the **original**
image value to `StillMade.run`; processing uses the original input within
the runtime's own limits. Do not use the display URL as a processing input.
Image display allows one pending request, up to 60 requests per minute, and a
15-second deadline. It is independent of the pending computation request, so an
input can be displayed while its Block runs. Both directions retain the 4 MiB
and 500,000-node message limits. Input changes, source/session reset, or closing
the workspace cancel unfinished display requests. Use a local revision counter
in asynchronous UI code so a late result cannot repaint an earlier selection.
StillMade's host image controls remain available with a custom interface.
A project lets the user select a compatible authorized image or upload one;
connected/context bindings remain read-only. A standalone preview keeps uploaded
files local to that preview. Host uploads accept PNG, JPEG, WebP or GIF up to
12 MB and one megapixel; larger uploads are rejected with a request to resize
a copy. Existing authorized project references can be displayed up to 16
megapixels, but the selected runtime still applies its processing limit. No
automatic processing resize occurs. The host validates permissions and exact media
identity before resolving bytes; no account token, native file handle, or remote
fetch capability enters the iframe.
This `view` example assumes one image input and one image output, both
named `image`. It shows the actual selected input and processed result
while retaining their original typed values:
{ "html": "<figure><img id=\"source\" alt=\"Selected input\" hidden><figcaption>Selected image</figcaption></figure><button id=\"run\">Run block</button><figure><img id=\"result\" alt=\"Processed result\" hidden></figure><p id=\"status\" role=\"status\"></p>", "javascript": "const source=document.getElementById('source'), result=document.getElementById('result'), button=document.getElementById('run'), status=document.getElementById('status');\nlet current={}, revision=0;\nStillMade.onInput(input=>{\n current=input;\n const started=++revision;\n source.hidden=true;\n result.hidden=true;\n if(!input.image)return;\n StillMade.previewImage(input.image).then(preview=>{\n if(started!==revision)return;\n source.src=preview.url;\n source.width=preview.width;\n source.height=preview.height;\n source.hidden=false;\n status.textContent='';\n }).catch(error=>{if(started===revision)status.textContent=error.message});\n});\nbutton.addEventListener('click',async()=>{\n const started=revision;\n button.disabled=true;\n try{\n const output=await StillMade.run(current);\n if(started!==revision)return;\n const preview=await StillMade.previewImage(output.image);\n if(started!==revision)return;\n result.src=preview.url;\n result.width=preview.width;\n result.height=preview.height;\n result.hidden=false;\n status.textContent='Finished';\n }catch(error){if(started===revision)status.textContent=error.message}\n finally{button.disabled=false}\n});" }
### Complete custom interface example
Add the following `view` to the complete JavaScript text-trimming example above.
The manifest, code, and fixtures stay unchanged. Adapt the controls to the requested Block:
{ "html": "<h1>Clean text</h1><label for=\"source\">Your text</label><textarea id=\"source\"></textarea><p><button id=\"run\">Clean text</button></p><h2>Result</h2><output id=\"result\" aria-live=\"polite\">Your result appears here.</output>", "css": "output { display:block; white-space:pre-wrap; padding:16px; border:1px solid var(--border-tok); border-radius:var(--r-3); }",
"javascript": "const source=document.getElementById('source'), button=document.getElementById('run'), result=document.getElementById('result'); StillMade.onInput(input=>{source.value=input.text||''}); button.addEventListener('click',async()=>{button.disabled=true;try{const output=await StillMade.run({text:source.value});result.textContent=output.text}catch(error){result.textContent=error.message}finally{button.disabled=false}});" }
### Dynamic lists and controls
Use `StillMade.render(containerId, nodes)` to replace the children of an existing
container with structured controls. Use `StillMade.onAction(callback)` for their
click, input, and change events; it returns an unsubscribe function. This supports
variable-length lists without raw HTML injection or direct DOM creation.
For example, with `<div id="choices"></div><output id="selection"></output>`:
StillMade.onInput(input => {
StillMade.render('choices', (input.shots || []).map(shot => ({
tag: 'button', text: shot.name || shot.id, value: shot.id, action: 'select' }))); }); StillMade.onAction(event => { document.getElementById('selection').textContent = event.value; });
Each node has `tag`, optional `text` or `children`, and optional `id`, `className`,
`label` (accessible name), `action`, `value`, `type`, `checked`, `disabled`,
`placeholder`, `min`, `max`, or `step`. Unknown fields are rejected. Supported tags
are div, section, article, header, footer, p, span, strong, em, small, h1–h4,
label, button, input, textarea, select, option, ul, ol, li, table, thead, tbody,
tr, th, td, details, summary, output, and progress. Input types are text, number,
range, checkbox, radio, and color. Use a div, section, article, main, aside, ul,
ol, or tbody as the existing root. Style nodes with classes in your view CSS.
An action belongs to a button, input, textarea, or select. Its callback receives
`{action, value, checked, event}`. Buttons emit click; checkbox, radio, and select
emit change; other fields emit input. Rerendering replaces controls, so keep edited
values in your own local variables and avoid rerendering a focused field on every
keystroke. Call `StillMade.run` explicitly to execute production code. Rendering
does not mutate project context or bypass execution permissions.
Limits: 1,000 nodes per render, 12 child levels, 256 KiB of serialized descriptors,
5,000 total frame elements, 16 action listeners, 10,000 characters per text/value,
120 per ID/action and 240 per class/name/placeholder. IDs must be unique. Invalid
renders leave the previous controls intact. Text is always literal; scripts,
inline handlers, links, file inputs, network attributes, and raw HTML are rejected.
### Audio and video playback
`await StillMade.previewMedia(reference)` opens an authorized `audio` or `video`
reference as a temporary playback copy and returns `{kind, url, mimeType, bytes}`.
Assign its URL to an existing `<audio controls>` or `<video controls>` element.
The host requires an exact reference from this Block’s bound input, an explicitly
selected project asset, permitted project context, or a host-recorded output.
A URL alone is insufficient. Preview has host file pickers for audio/video inputs;
project Steps can select existing project assets or receive connected media.
For `<audio id="player" controls></audio><p id="status"></p>`:
const player = document.getElementById('player'); const status = document.getElementById('status'); let revision = 0, activeUrl = ''; StillMade.onInput(input => { const started = ++revision; player.pause(); player.src = ''; if (activeUrl) StillMade.releaseMedia(activeUrl); activeUrl = ''; if (!input.audio) { status.textContent = 'Choose audio above.'; return; } status.textContent = 'Opening audio…'; StillMade.previewMedia(input.audio).then(preview => { if (started !== revision) { StillMade.releaseMedia(preview.url); return; } activeUrl = preview.url; player.src = preview.url; status.textContent = 'Ready to play.'; }).catch(error => { if (started === revision) status.textContent = error.message; }); });
`StillMade.releaseMedia(url)` revokes a playback copy. Release old copies before
loading another: at most two can remain active. Input changes and closing/resetting
the interface revoke all copies and cancel pending reads automatically. Requests
share the image-preview queue and its limit of 60 requests per minute; only one
image/audio/video read can run at a time. Keep the original reference for production
runs; temporary URLs must never be saved as project assets or returned as outputs.
`await StillMade.useOutput(outputs)` selects complete saved media references as the Block outputs without dispatching a generation run. This is available in a saved project workspace only. Every output must match the manifest, and every media identity must come from the current authorized Block input or the current trusted desktop recording session. The host rejects changed URLs, unknown assets, and non-media outputs.
Files must be at most 24 MB and load within 15 seconds. Supported containers include
MP4, WebM, Ogg, QuickTime, MP3, AAC, WAV, and FLAC; actual playback depends on browser
codec support. Remote assets need CORS support and an appropriate media content type.
The host omits credentials and rejects redirects. Network access remains disabled
inside the iframe; larger media should use the existing native Editor workspace.
## Tests and interconnection
Include 2–6 small deterministic fixtures covering normal behavior and edge cases.
Each fixture is exactly `{ "name": "...", "input": {...}, "expected": {...} }`.
Expected is the **output bindings object**, not one bare value and not
`{"outputs": ...}`. Fixture comparison is exact JSON, including numeric values,
array lengths and output keys. Compute expected values carefully; do not weaken a
test merely to make code pass. Test defaults by omitting a defaulted input in at
least one fixture. Include useful boundary settings for controls.
Connections use declared types and semantic roles, not port names. Generic image
output → generic image input connects automatically when each has a primary port.
If a downstream input requires a role, the upstream output must declare the same
role. Integer → number is safe; other differing types need explicit supported
conversion. Test a transform as image → image or text → text when appropriate so
it can sit between compatible Blocks without manual wiring. Secondary controls
should have defaults when practical.
## Deliver, test, and import
For repository adaptations, deliver one `.stillmade-block` archive using the
[portable packaging commands](/block-sdk/PORTABLE.md). The agent runs validation,
fixtures and packaging. The recipient uploads the file; StillMade repeats source,
license, security and sandbox checks, shows a preview and offers **Install**.
Development JSON examples and hosted capabilities retain their existing review
contracts; they do not replace the required portable archive.
For future edits, keep the ID and increase the semantic version before releasing
changed source. For a remix, use a new ID and preserve
`manifest.provenance.remixedFrom` with the source ID, version and SHA-256 digest
when available. Never guess a digest or erase attribution.
Now inspect the supplied GitHub URL and produce the portable artifact using this contract.
## Optional ComfyUI implementation
To adapt an existing API-format ComfyUI JSON, use `inspectComfyImport(raw)` and
`buildComfyImportPackage(raw, {manifest, inputs, outputs, tests})` from the SDK
index. Inspection returns candidates and `execution: "not-run"`; it never runs
Python or chooses semantic roles. LoadImage, ImageInvert, ImageScale, PreviewImage and the reviewed core diffusion nodes described below are supported. The browser import wizard can make these
mappings and small image fixtures for the user. A visual `nodes`/`links` export
must first be exported as API JSON in ComfyUI. Preserve licensing; the generated
manifest retains original API source in `provenance.comfyuiImport.source`.
Static conversion is not proof of runtime success: real host-controlled backend
tests, a sample preview and Confirm Import remain required.
Choose JavaScript or recipes for offline deterministic processing. A ComfyUI
Block supports reviewed `LoadImage`, `ImageInvert`, `ImageScale`, `PreviewImage`, `CheckpointLoaderSimple`, `CLIPTextEncode`, `EmptyLatentImage`, `KSampler`, `VAEEncode` and `VAEDecode` core nodes. Do not invent custom nodes, installed model names, API calls,
Python, dependency installers, local file access, or a backend URL in source.
The account chooses a public HTTPS connection outside the package.
Model workflows use one installed safetensors checkpoint basename (no paths/downloads), one KSampler, batch 1, at most 40 steps, and generation sides from 64 to 1024 in multiples of 8. The four-megapixel aggregate image/latent working budget still applies; 1024-square image-to-image workflows can exceed it. Text is bounded to 4000 characters without embedding-file directives. Supported samplers: euler, euler_ancestral, dpmpp_2m; schedulers: normal, karras, simple. CLIPTextEncode text can bind to a text input; seed/steps and numeric controls bind to integer/number inputs. Model names stay fixed in source. Required model names are checked on the selected backend, not their hashes or GPU readiness. Use the SDK example `comfy-text-to-image.stillmade.json` and replace its placeholder checkpoint with the user’s actual installed model. No fixture execution occurs during authoring.
Use `runtime: "comfyui"`, `entry: "src/comfyui.json"`, and
`permissions: {project: [], capabilities: ["comfyui.execute"]}` in the manifest.
The package fields are `manifest`, `comfyui`, `tests`, optional `view`; no code or
recipe. All existing typed-port, semantic-role, UI and identity rules apply.
`comfyui` has `schemaVersion: 1`, `prompt`, `inputs`, `outputs`:
{"schemaVersion":1,"prompt":{"1":{"class_type":"LoadImage","inputs":{"image":""}},"2":{"class_type":"ImageInvert","inputs":{"image":["1",0]}},"3":{"class_type":"PreviewImage","inputs":{"images":["2",0]}}},"inputs":{"image":{"node":"1","input":"image","encoding":"uploaded-image"}},"outputs":{"image":{"node":"3","collection":"images","index":0}}}
Declare image input/output ports named `image`, with `primary: true` and the
same `role: "source_image"`. Use fixture `input.image` as a small RGBA image and
`expectations.image: {kind: "image", count: 1, width: 1, height: 1}`. ComfyUI
fixtures use `expectations`, not `expected`. Optional `maxBytes` and lowercase
`sha256` check the actual host-reencoded PNG. Limits: 16 nodes, 8 ports, one
megapixel/single frame per image, four megapixels total working images. Every
node must contribute to a declared output. LoadImage filenames stay empty;
StillMade uploads the selected image. No secrets or connection IDs in source.
Offline `validate` and `pack` work; ComfyUI `pack` says `reviewRequired: true`.
Do not claim remote tests passed. Import into StillMade, explicitly select a
connection and run all fixtures and a sample; preview before confirming import.
Custom UI still uses `StillMade.run(input)` but the host asks the user to choose
and confirm remote execution. Do not build your own connection or credential UI
inside a Block. In a saved project, the host rechecks edit access, exact-source
review evidence and backend compatibility, then asks for Run in project. Review
fixtures are not silently rerun. A missing review needs an explicit test/sample
preview before a separate project run. Never send project IDs or receipts from
guest code. ComfyUI Project Types require an account review for every embedded
ComfyUI package through the import wizard. Unattended automation is not
available yet.
## Declare platform compatibility
Optional `manifest.platforms` declares all three targets: `phone` (phone browser),
`browser` (desktop web browser), and `desktop` (downloaded StillMade app). Each is
`{supported: boolean, reason?: string}`. Give a 1–240 character reason for every
unsupported target, and support at least one target. Example:
{"platforms":{"phone":{"supported":false,"reason":"This interface requires the StillMade desktop app."},"browser":{"supported":false,"reason":"This interface requires the StillMade desktop app."},"desktop":{"supported":true}}}
Declarations never verify support. All targets stay unverified until their
working UI and runtime checks pass. Include a usable phone layout with reachable
controls, 32 px touch targets and 16 px inputs, or exclude Phone. For a custom view
with multiple buttons, mark its real sample action `data-stillmade-action="run"`.
The host clicks it and validates the actual sandbox result. Declare interface
restrictions accurately and test every supported target. These listing indicators
do not grant filesystem, process, recording, or other native permissions and
cannot bypass the sandbox. Project Types inherit their enabled Blocks’ restrictions.
## Optional hosted text generation
When the requested Block generates text with an LLM, use the declarative hosted
runtime below. Do not implement it with `fetch` or an invented JavaScript host
API. Return one SDK JSON file; include no provider, model, endpoint, account key,
payment choice, test report, or saved user data. StillMade handles those after
source review and explicit user confirmation.
{ "manifest": { "schemaVersion": 1, "sdkVersion": "0.1.0", "id": "example.script-draft", "version": "1.0.0", "name": "Draft a short script", "description": "Turn a production brief into a short narration draft using an approved text-generation call.", "kind": "task", "runtime": "capability", "entry": "src/capability.json", "license": "MIT", "inputs": { "prompt": { "type": "text", "primary": true } }, "outputs": { "script": { "type": "script", "primary": true } }, "permissions": { "project": [], "capabilities": [ "text.generate" ], "network": [], "filesystem": [], "secrets": [] }, "ui": [ { "control": "text", "port": "prompt" } ] }, "capability": { "schemaVersion": 1, "operation": "text.generate", "prompt": { "$input": "prompt" }, "system": "Write a concise narration script. Return only the script text.", "maxTokens": 1200, "output": "script" }, "tests": [ { "name": "A short opening narration", "input": { "prompt": "Write two narration sentences about a quiet forest at sunrise." }, "expectations": { "script": { "kind": "script", "minLength": 20, "maxLength": 6000 } } } ] }
Implement the requested instructions and fixtures, choose a creator namespace,
and preserve the contract. `capability` has schemaVersion, operation,
prompt, system, maxTokens, output, and optionally `context` (a list of up to three
other read-only input names whose values are sent after the prompt as delimited
data; unlisted inputs are never sent). `operation` is only `text.generate`; `prompt`
is one named `$input` reference. One prompt input: text, script, or brief. One output:
text or script. A script/brief input requires {schemaVersion:1,text}; a generated
script uses that shape too. Prompt max 32,000 characters, system max 8,000,
maxTokens 1–4096, output max 32,768 characters, full package max 256 KiB. Mark primary
ports and semantic roles accurately. No automatic scene/character extraction.
If the requested result should feed StillMade's Voiceover Step, declare the
output as `script`, as in this example, and return the structured
`{schemaVersion:1,text}` value. A plain `text` output does not become narration
implicitly. A Project Type must embed the exact package and connect its selected
output to `sm.voiceover@1.1.0`'s `input`, for example:
{"from":{"stage":"draft","port":"script"},"to":{"stage":"narration","port":"input"}}
Here `draft` and `narration` are the corresponding Step IDs, not Block IDs.
Users run the source Block, continue to Voiceover, review the proposed narration,
and choose **Apply script** or **Keep current**. The handoff preserves audio takes
and history while invalidating stale timings; it never generates speech
automatically. Do not invent a Canvas node, write to protected Chat, or call a
project-update API from guest code. If asked only for a Block, return that Block;
do not append an invented Project Type or claim that a live handoff was tested.
Include 1–3 fixtures with `expectations`, not `expected`. Each named output has
kind text|script, minLength 1–32768 and maxLength between minLength and 32768.
Optional includes is at most 8 literal strings, each 1–256 characters. These are
checks to run, not a claim that the provider produced them. Include no regex.
Optional `view` uses the same sandbox contract and `StillMade.run(input)`. It
cannot confirm host charges or read keys. Store separate source as
stillmade.block.json, src/capability.json, tests/fixtures.json, and optionally
src/view.html plus optional src/view.css and src/view.js (or the older src/view.json object), or return the complete packed JSON shown above. CLI create
<folder> capability scaffolds it. Offline validate/pack work and report live
review required; offline test/preview cannot generate text. In StillMade, choose
a model/payment method, review the total cost for every fixture plus one sample,
confirm execution, inspect actual results, then Confirm Import. No paid call
should run merely because the file is uploaded or generated.
This declaration implements text.generate. For supplied text-to-speech use the
separate audio.speech contract below. Image generation uses image.generate below. Hosted video generation, arbitrary
tools, dependencies, endpoint overrides, and automatic multi-call workflows
remain unavailable. Deterministic recipe/JavaScript Blocks remain appropriate
for local text or pixel transformations.
## Text conversations
To continue a conversation in `text.generate`, add `"history":{"$input":"history"}`
bound to one optional `json` input holding up to 20 earlier turns of
`{"role":"user"|"assistant","content":"..."}`. They are sent before the prompt.
## Structured text replies
For a `text.generate` Block that must return data, declare one `json` output and
add `"schema"` (a JSON Schema subset: `type`, `description`, `title`, `enum`,
`properties`, `required`, `additionalProperties`, `items`, `minItems`,
`maxItems`, `minLength`, `maxLength`, `pattern`, `format`, `minimum`,
`maximum`; at most 8 levels and 8,192 characters) to the capability. StillMade
asks the model for exactly that shape, parses the reply and rejects anything
that does not match. Fixtures for a json output use `{"kind":"json"}` with
optional `includes`. Downstream Steps receive the parsed value.
## Module Blocks (modern JavaScript and WebAssembly)
When a Block needs npm libraries, WebAssembly (including C/C++ libraries such as
OpenCV), async work, timers, OffscreenCanvas or WebCodecs, use
`"runtime": "module"`. The folder holds `stillmade.block.json`, `module/main.js`
(an ES module that bundles its npm dependencies), optional `.wasm` or data files
in `module/`, and `tests/fixtures.json` with `{name,input,expected}`. The entry is
`export default async function run(input, stillmade)` and returns the declared
outputs. Use `stillmade.progress(value,label)`, `stillmade.log()`,
`stillmade.signal`, `stillmade.asset(path)` for the module's own files,
`stillmade.files.read(ref)` for saved media the Block received,
`stillmade.files.write(data,{mediaType,name})` to save results,
`stillmade.media.transform(request)` for on-device media work (`audio.decode`
to mono PCM samples, `audio.extract`, `audio.normalize`, `video.cut` with
ordered `keep` ranges, `video.concat`, `video.resize`, `image.thumbnail`; the
results are saved references), with helpers such as `silentRanges`,
`keepRanges` and `integratedLoudness` from `@stillmade/block-sdk/media`,
`stillmade.actions.run(action)` for declared actions such as `media.generate`,
and `stillmade.hosted.run(operation,request)` for hosted operations listed in
`permissions.capabilities`. There is no network, DOM or app storage. Never
include keys or prices. Pack with `stillmade-block pack` and import the
`.stillmade-module.json`. See `MODULES.md`.
## Frame views (any interface code)
When the interface needs React or another framework, SVG, canvas or WebGL,
pointer drag, timers or loops, make it a frame view: `src/view.html` (for
example `<div id="root"></div>`), `src/view.css`, `src/view.js` bundled as ONE
classic script (esbuild `--format=iife`), and `src/view.config.json` with
`{"runtime":"frame"}`. It uses the same `StillMade` object (`getShared`,
`onShared`, `updateShared`, `run`, `input`, `onInput`) plus
`StillMade.asset(path)` for files in `src/view-files/`. Keep each independently
edited value in its own shared field. Color with `var(--bg)`, `var(--fg)`,
`var(--accent)` and the other theme variables. There is no network, no
`eval`, no WebAssembly on the page and no navigation; one task may run 250 ms,
so move long work into a Worker or the Block runtime. See `FRAME_VIEWS.md`.
## Files, documents, tables, links, dates and colors
Use the data types instead of JSON text: `file` (a saved PDF, TXT, CSV,
Markdown, JSON or ZIP: `{kind:"file",assetId,versionId,mimeType,url,name,bytes}`),
`document` (`{schemaVersion:1,format:"markdown",text}`), `table`
(`{columns:["Name"],rows:[{"Name":"x"}]}`), `url`, `date` (`2026-10-09`) and
`color` (`#1a2b3c`). Bind controls in `manifest.ui`: `file`, `richtext`,
`grid` (a table editor), `url`, `date`, `color`, `list`. Present outputs with `table`,
`document`, `chart` (`x`, `y`, `chart:"bar"|"line"`), `code`, `player` or
`download`. A module Block reads an uploaded file with
`await stillmade.files.read(input.file)` and builds values with `createTable`
and `createDocument` from `@stillmade/block-sdk/data`. To hand shots to the
Shots workspace, output `{"type":"shot[]","semantic":"production_shot_plan"}`
where each shot has `schemaVersion:1` and a unique `id`.
## Outside APIs with the person's own account
A module Block may call outside APIs. List each origin in
`permissions.network` (`"https://api.example.com"`, or
`{"origin":"https://api.airtable.com","auth":{"scheme":"bearer","label":"Airtable personal access token","help":"https://airtable.com/create/tokens"}}`;
schemes `bearer`, `header` with `name`, `query` with `name`, or `oauth2` with a
reviewed `app` such as `notion`) and call
`await stillmade.net.fetch(url, {method, headers, body})`, which returns a
`Response`. Never put keys in code or headers: StillMade asks the person for
theirs and adds it on its servers. For a published OpenAPI adapter, list
`"connections":["app:openapi:notion"]` and call
`await stillmade.connections.call('app:openapi:notion','retrievePage',{path_page_id:id})`.
## Project storage, reads and assets
A module Block keeps its own data with `stillmade.storage`: `get(key)`,
`set(key, json)` (up to 1 MiB), `putFile(key, bytes, {mediaType})` and
`getFile(key)` (up to 64 MiB, returns `{data, mediaType, bytes}` or `null`),
`list(prefix)`, `remove(key)`. Keys look like `brand/logo.png`. Set
`"storage":{"schemaVersion":1,"scope":"project","desktop":"device-unmetered","cloud":"metered"}`
to share one store across the whole project (`"workspace"` keeps one per Step).
To read the project, declare `project.read` and `context.<field>.read` and call
`await stillmade.project.read(['timeline'])`. To add media to the project,
declare `asset.create` and call `stillmade.assets.create(await stillmade.files.write(bytes,{mediaType:'image/png'}),{name})`.
Change the project only through proposals (`timeline-edit` or `workspace-edit`
outputs) that people review.
## Running on its own
A recipe, JavaScript or hosted Block may declare `triggers`:
`{"event":"media.saved","input":"<primary media input>"}`,
`{"event":"schedule","everySeconds":3600}`, `{"event":"webhook","eventKind":"order.created"}`,
`{"event":"project.completed"}` or `{"event":"provider","appId":"stripe","eventKind":"charge.succeeded"}`.
Nothing runs until the project owner turns a trigger on. Webhook and event
Blocks read the event from an input with `"semantic":"workflow_event"` and
`"sources":["project"]`. Hosted Blocks run unattended only inside the credits
the owner approves; proposals always wait for a person.
## Optional media analysis
To ask about saved media, use runtime `capability`, permission `media.analyze`,
one `video`, `image` or `image[]` (one to four) input, one `text` or `json`
output, and
`{schemaVersion:1,operation:"media.analyze",media:{$input:"clip"},question:"what to check",output:"report"}`.
A video Block may add `range:{start:{$input:"start"},end:{$input:"end"}}` bound
to two optional `number` inputs (at most 12 seconds apart). A json output
receives `{schemaVersion:1,items:[{kind,summary,timeline,observations,detectedText,issues,confidence}]}`.
Each analyzed item is one StillMade-credit call.
## Optional web reading
To read a public page, use runtime `capability`, permission `web.fetch`, one
`text` URL input and one `text` output, with
`{schemaVersion:1,operation:"web.fetch",url:{$input:"url"},instruction:"what to extract",output:"summary"}`.
To search, use permission `web.research`, one `text` or `brief` topic input and
one `text` or `json` output, with
`{schemaVersion:1,operation:"web.research",topic:{$input:"topic"},output:"findings"}`
(a json output receives `{schemaVersion:1,summary,sources:[{url,title}]}`).
StillMade reads the web; keep `permissions.network` empty and never include
headers, cookies, keys or prices. Both are paid with StillMade credits.
## Optional hosted transcription
For stored audio to timed text, use runtime `capability`, permission `audio.transcribe`, exactly one `audio` input, one `text` language input, and one `transcript` output. The descriptor is exactly `{schemaVersion:1,operation:"audio.transcribe",audio:{$input:"audio"},language:{$input:"language"},output:"transcript"}`. Give language the default `auto` and provide both an `asset` control for audio and a language `select`. Fixtures use `{kind:"transcript",minWords,maxWords}` expectations. Do not fabricate provider output, embed credentials, choose endpoints, or request network/filesystem/secrets. StillMade uses owner-scoped stored media; the person running it chooses StillMade transcription on credits or OpenAI Whisper with their own key. Return one complete package and validate it with the SDK. See `TRANSCRIPTION.md` and `examples/transcribe-audio.stillmade.json`.
## Optional hosted music and sound effects
For music or a sound effect from a written prompt, use runtime `capability` with
permission `audio.music` or `audio.sfx`, one `text`, `script` or `brief` input,
and one `audio` output. The descriptor is
`{schemaVersion:1,operation:"audio.music",prompt:{$input:"brief"},output:"music",settings:{durationSec:30}}`
(or `operation:"audio.sfx"`). `settings.durationSec` is optional: music is 5 to
240 whole seconds, sound effects 0.5 to 22 seconds in tenths. The person running
the Block can change the length and pays with StillMade credits at the app's
music and sound-effect prices. Outputs are measured MP3 or WAV references.
Fixtures use `{kind:"audio",format:"any"|"mp3"|"wav",minDuration,maxDuration,minBytes,maxBytes}`
with durations up to 1,800 seconds and bytes from 45 to 134217728. Never include
provider, model, payment, endpoint, credentials or fabricated output. See
`packages/block-sdk/audio-generation-example.js` for `audioMusic` and `audioSfx`.
## Optional hosted speech
When the request is to speak supplied text, use this complete declarative
package shape. One input is `text` or `script`; one output is `audio`. A script
value contains `{schemaVersion:1,text}`. Text must be nonblank and no longer
than 3,800 characters. No script drafting, splitting, or multiple provider calls
are implied by this operation.
{ "manifest": { "schemaVersion":1,"sdkVersion":"0.1.0", "id":"example.narrate-script","version":"1.0.0", "name":"Narrate a script", "description":"Turn a short script into a reviewed speech recording using a host-selected voice.", "kind":"task","runtime":"capability","entry":"src/capability.json","license":"MIT", "inputs":{"script":{"type":"script","primary":true}}, "outputs":{"audio":{"type":"audio","primary":true}}, "permissions":{"project":[],"capabilities":["audio.speech"],"network":[],"filesystem":[],"secrets":[]}, "ui":[] }, "capability":{"schemaVersion":1,"operation":"audio.speech","text":{"$input":"script"},"output":"audio"}, "tests":[{ "name":"Read a short welcome", "input":{"script":{"schemaVersion":1,"text":"Welcome to the quiet forest."}}, "expectations":{"audio":{"kind":"audio","format":"wav","minDuration":0.1,"maxDuration":30,"minBytes":46,"maxBytes":1500000}} }] }
The capability object contains `schemaVersion`, `operation`, `text`, and
`output`, and optionally `settings:{voice,speed}` suggesting a StillMade catalog
voice id (for example `asteria`) and a speed from 0.25 to 4. The person running
the Block can change both. Its text is one exact `$input` binding; its output
names the single declared audio port. Do not include provider, payment,
endpoint, credentials, actual output, or receipts anywhere in the package.
Use your own creator ID and 1–3 fixtures. Every audio expectation requires exactly
the six fields shown. Duration bounds are finite seconds,
`0 <= minDuration <= maxDuration <= 1400`, with positive `maxDuration`. Byte
bounds are integers, `46 <= minBytes <= maxBytes <= 67108864`. Do not use exact
audio equality, hashes, transcript assertions, or regex as fixture expectations.
The host output is exactly a reference:
`{kind:"audio",assetId,versionId,url,mimeType,duration,bytes,sampleRate,channels}`.
StillMade catalog voices return measured MP3 or WAV; OpenAI TTS-1 returns
24,000 Hz mono WAV. The trusted host measures duration and size from the actual
bytes and stores the file. The package does not create a plausible-looking media URL or metadata.
Metadata alone cannot prove that a file exists or passes the audio checks.
Offline validate/pack check source and report live review required. Offline
test/preview cannot generate speech. No live provider speech test has been
completed for this implementation; schema checks and explicit mock tests are
not provider evidence. In StillMade, choose StillMade voices (credits, the same
catalog as Voiceover) or OpenAI TTS-1, a voice, and speed (0.25×–4×), review costs for all fixtures plus a sample,
then explicitly run them. Listen to the AI-generated recordings, finish review,
and Confirm Import. Project generation requires its own quote and confirmation.
Nothing is billed merely because a package is uploaded or authored.
The result can feed compatible SDK audio inputs and is playable in the host.
After a confirmed speech run in a saved project and **Use this audio** acceptance,
the user can open **Media** in the Editor, find **Audio from Blocks**, listen,
position the playhead, and choose **Add at playhead** on desktop or mobile. This explicitly
adds an Editor audio item with the recording's measured duration; it does not
generate speech again. New items require a current accepted project result.
Earlier inserted items keep their chosen recording when the source Block is
rerun or removed.
Editor insertion does not replace the native Voiceover master recording, select
its takes, or change word timings, transcripts, or shared narration. Voiceover's
primary input remains `script`. Declare the `audio.speech` package normally;
do not invent an audio-to-Voiceover adapter or a guest timeline-writing API.
An optional custom `view` uses the existing `StillMade.run` host flow; there is
no `StillMade.previewAudio` API. Return one complete `.stillmade.json` file.
## Optional hosted image generation
For a visual prompt → image Block, return this declarative shape with your own
id, name, description and realistic fixtures. Do not generate JavaScript that
calls OpenAI or reads keys. This is the complete portable package shape:
{ "manifest": { "schemaVersion": 1, "sdkVersion": "0.1.0", "id": "example.generate-image", "version": "1.0.0", "name": "Generate an image", "description": "Generate a square PNG from a short visual prompt using an approved host request.", "kind": "task", "runtime": "capability", "entry": "src/capability.json", "license": "MIT", "inputs": { "prompt": { "type": "text", "primary": true } }, "outputs": { "image": { "type": "image", "primary": true } }, "permissions": { "project": [], "capabilities": [ "image.generate" ], "network": [], "filesystem": [], "secrets": [] }, "ui": [ { "control": "text", "port": "prompt" } ] }, "capability": { "schemaVersion": 1, "operation": "image.generate", "prompt": { "$input": "prompt" }, "output": "image" }, "tests": [ { "name": "A simple forest illustration", "input": { "prompt": "A simple illustration of a quiet forest with clear dark outlines and soft green colors." }, "expectations": { "image": { "kind": "image", "format": "png", "width": 1024, "height": 1024, "minBytes": 67, "maxBytes": 12000000 } } } ] }
Declare one input of type text, script, or brief, optionally one `image` or
`image[]` input of reference images, and one image output. Text is a string;
script/brief is `{schemaVersion:1,text}`. The prompt is nonblank and at most
32,000 characters. The descriptor has schemaVersion, operation, prompt and
output, and optionally `settings` (default `{model,aspectRatio,resolution,quality}`),
`references` (`{"$input":"<image input>"}`), `negativePrompt` (≤2,000 characters)
and `seed` (0–2147483647). Keep network/filesystem/secrets empty; existing scoped
project-context read permissions may be declared when needed.
Models: nano-banana-2, nano-banana-2-lite, nano-banana-pro, gpt-image-2,
gpt-image-1.5, kling-image-v3, seedream-v4.5 — the same catalog the app uses
(`packages/block-sdk/generation-catalog.js` lists each model's aspect ratios,
resolutions, qualities and reference support; an axis a model lacks is
"default"). Only the Nano Banana family and GPT Image 2 accept references, up to
8 saved images. Users pay with StillMade credits at StillMade's price; never put
payment, keys, endpoints or prices in the package. The user may change model
and settings before running.
The actual output is
`{kind:"image",assetId,versionId,url,mimeType:"image/png",width,height,bytes}`.
The host decodes and measures the actual image, saves one PNG and verifies the
saved hash: up to 8192 pixels a side, 40 megapixels and 64 MiB. Each fixture
expectation has kind image, format png, `minWidth`, `maxWidth`, `minHeight`,
`maxHeight`, `minBytes` and `maxBytes` as ordered integer bounds.
These checks establish dimensions/format/size, not whether an image depicts the
requested subject; the user reviews the real picture.
Upload/schema validation/offline packaging performs no generation and grants
no installation. Offline test/preview cannot execute this capability. In
StillMade the user reviews model/quality/cost for every fixture plus a separate
sample, confirms the provider run, previews results, finishes review, then
confirms import. Project execution requires a separate current quote and
**Use this image** acceptance. Do not claim a live provider test passed from
static/offline checks. Uncertain submissions are not automatically repeated.
Accepted image references connect to compatible image inputs through the
host's safe image resolver and appear in the Editor's visual media inventory.
An optional view uses `StillMade.run` and the normal host preview flow; it gains
no provider API or receipt-writing API. Return one complete `.stillmade.json`
file. Its source, fixtures and SDK remain downloadable for outside editing.
## Test the whole Project Type in StillMade
After importing a Project Type, use **Preview connected workflow** in the builder.
Provide sample inputs keyed by Step ID and sample project context for declared
context ports. Actual upstream output overrides downstream fixture inputs.
Hosted Blocks require an account-bound package review and a separately confirmed
model/payment choice for each connected execution. Never hard-code review output
as production output. Generated media is verified before recipe image decoding;
recipe images must stay within one megapixel. Preview context cannot mutate a
real project. Offline CLI checks do not execute hosted providers or certify the
whole hosted workflow. Keep each Block's own fixtures and sample execution tests.
### Optional saved memory
Only JavaScript Blocks can declare `manifest.state = {scope:"step", version:1,
initial:{count:0}}` and include `state.step` in `permissions.project`. Their code
receives a mutable `state` object alongside `input`: mutate its properties and
return ordinary declared outputs. Do not reassign `state`. Each fixture must include
`expectedState:{version:1,value:{count:1}}`; optionally provide an initial fixture
`state` envelope to test continuation. State values must be plain JSON objects up
to 64 KiB. Every production placement has independent memory pinned to its exact
build, saved with accepted outputs. Preview memory is temporary and resets with
Reset preview. Failed/cancelled runs cannot advance project memory. Single-Block batches use provisional sequential memory until the user accepts a
result. Connected batches carry independent memory per placement in selection order; accepting the completed results validates and saves that memory together. Single-Block background automation supports saved memory when shared Canvas editing is enabled: outputs and memory commit atomically, with conflicts retried from current memory. Connected background workflows also support stateful sandboxed Blocks with shared Canvas enabled. Starting memory is pinned; all memory changes commit together when the complete job succeeds. A failed prefix retains provisional results without advancing shared memory. Do not store API keys or media
bytes in this state. External hosts pass `{state:{version:1,value:{count:0}}}` to
`runPackageAsync` and validate/accept the returned `result.state` with outputs.
Offline Project Type packaging also supports ComfyUI-only and mixed ComfyUI,
hosted-capability, and local packages. Use `node packages/block-cli/cli.js pack
workflow.json workflow.stillmade.json`. This statically checks every embedded
package but executes no fixtures or providers; its report must stay unverified
until StillMade runs the import review and the user confirms installation.
### Editor proposals
To edit existing timeline clips, use output type `timeline-edit` and request
`context.timeline.read` plus `timeline.propose`. Return
`{schemaVersion:1,title,timelineId:input.timeline.activeTimelineId,commands}`.
Each command is `{collection:"clips"|"audioItems",operation,before,values}`.
`before` must be the exact unmodified target object from the scoped timeline
input. Operations: move `{start}`, trim `{trimStart,duration}`, volume `{volume}`
(0–200 percent), mute `{muted}`, remove `{}`. Maximum 100 commands, one per
item. Split uses `operation:"split",values:{at,newId}` with an unused clip ID and a cut at least 0.05 seconds inside the clip. Users review and apply the proposal in the Editor; mismatched targets,
locked tracks and invalid timings are rejected. Do not mutate the input or
claim the proposal has already changed the project. Declare realistic fixtures
with complete target snapshots and an activeTimelineId.
Caption proposals use `collection:"captions"`. Add with `operation:"caption.add"`,
`before:null`, and `values:{id,trackId,text,start,end}`. Update with
`operation:"caption.update"`, the complete original caption as `before`, and
`values:{text,start,end}`; remove uses `{}`. Use an unused ID, an unlocked caption
track from context (or `captions` to create the standard track if absent), nonempty
text up to 5,000 characters, and valid seconds with end greater than start.
New captions use standard Editor styling; updates preserve existing styles.
## On-demand desktop capabilities
Block UIs may call `StillMade.desktop(operation, args)` through the trusted host. Declare explicit `permissions.desktop` scopes: `screen.capture`, `cursor.track`, `camera.capture`, `microphone.capture`, `clipboard.read`, `clipboard.write`, `media.pick`, `notifications.show`, or `power.keep-awake`. Import/install never grants access; the host prompts on use, and access is revoked when the workspace closes. Use `record.start` with `{kind:"screen",cursor:true}`, then `record.stop` to receive `{asset,cursor,displays,coordinates}`; `record.cancel` discards capture and `session.close` revokes access. Records are bounded to 30 minutes/128 MiB and return device-local media references. System audio is Windows-only in this implementation. These APIs are unavailable in the browser and cannot be used from pure runtime code. See [desktop capability reference](/docs/reference/desktop-capabilities) for all operations, limits, and a recording example.
SDK folders can also be imported directly from public GitHub repositories. In StillMade choose SDK folder, enter its relative path (or `.` for the repository root), and select a branch, tag or commit. Keep the standard manifest, runtime, fixture, and optional split interface files in that folder. Files are pinned to one commit and checked before the ordinary sandbox review and confirmation. Keep combined source below 1 MB, or use folder/ZIP upload for larger packages.
## Complete dynamic Editor interface example
The SDK includes `examples/timeline-mixer.stillmade.json` and its source in
`packages/block-sdk/timeline-mixer.js`. Use it as a working reference for dynamic
lists of project items: individual clip level controls, reset, empty states,
input revision handling, isolated execution, and typed `timeline-edit` output.
Its `levels` input is a JSON array of `{key, volume}` entries; keys identify a
collection and clip ID. The host supplies the timeline through scoped context.
The interface never applies edits itself: users review the resulting proposal
before the Editor checks current clip versions and records normal undo history.
## Project Type onboarding
A Project Type can declare project defaults and up to eight setup questions. These are optional additions to the schema-1 Project Type contract; they do not replace protected Chat, configure provider credentials or start production work.
{ "defaults": {"aspectRatio": "9:16", "brief": "Create a short product launch video."}, "onboarding": [ {"id": "product", "label": "What is the product?", "type": "text", "required": true}, {"id": "audience", "label": "Who is it for?", "type": "textarea"}, {"id": "tone", "label": "Tone", "type": "select", "options": ["Clear and direct", "Playful"], "default": "Clear and direct"} ] }
Merge these fields into an existing valid Project Type with `schemaVersion`, `id`, `version`, `name`, `description` and its enabled `stages`. This fragment is not a standalone package.
`defaults.aspectRatio` accepts `16:9`, `9:16` or `1:1`; `defaults.brief` is at most 4,000 characters. Question IDs are unique lowercase identifiers up to 40 characters. Labels are at most 120 characters. Text and textarea answers are at most 1,000 characters. A select question declares 2–12 unique options of at most 120 characters; its default must match an option. `required` is optional and boolean.
The Project Type Builder exposes these fields under **Project defaults & starting questions**. Using the Project Type opens one project setup dialog. The host records the chosen project name and format, and retains the brief plus labeled answers in the existing project's production context (`bible.extraInfo`). Script Writer receives this brief. Answers are project data, not edits to a marketplace release or its version identity; they are not published or sent to acquisition analytics. Read Blueprint/project context only through the permissions already documented by the SDK.
The empty Project Type starter is an editable draft. Add an enabled Block before validation, runtime execution, or publication; empty drafts are never installable releases.
## Publishing and release management
Use **Publish version** from the existing builder. One review dialog covers Community, Marketplace review, or private access where the account permits it, plus runtime, license, preview and redistribution consent. Marketplace selection publishes a Community release and requests review; it does not grant curated status. If review submission fails, the successful Community release is kept and can be submitted again.
The same publishing panel controls public cover media, full description, category, tags, use cases and version notes. These change presentation only. The released title, short description, typed contracts, source and license remain tied to the immutable package version.
My builds includes original and forked drafts, imports, installed versions, every own release and private releases. A version can be marked deprecated or delisted, with a public notice and an optional replacement version. It stops appearing in discovery and becomes ineligible for indexing; direct version URLs, source downloads and existing pinned projects remain available unless a separate security revocation applies.
## Project Type production completion
An optional `outcome` field declares the finished result required by this exact Project Type version:
{"outcome":{"type":"export","dedupe":"material_project_state","minimumDurationSec":3}}
`type` is `export`, `delivery`, or `project_outcome`. `dedupe`, when present, must be `material_project_state`; that is also its default. `minimumDurationSec` is optional, finite, between 0 and 86,400 seconds. A positive minimum requires host-measured media duration. Omit it for text or editable project artifacts.
Configure this in **Project defaults & starting questions → Production completion**. Omitting `outcome` preserves the existing host completion behavior. These settings never launch exports, create revenue or mark a client action as successful. Version the Project Type when changing its production rule.
Only a trusted completion adapter can supply `materialStateHash`, measured duration, success, retained-source lineage and the actual outcome kind. The accounting qualifier reads the rule from the exact released Project Type; browser-provided contributors and success flags are not accepted. Matching material states deduplicate under the payer-period policy. Verified editable-project deliveries can satisfy `project_outcome`; a local MP4 upload alone does not prove an executed media export.
## Hosted image analysis
For image analysis, use runtime `capability`, permission `image.describe`, exactly one `image` input (or one `image[]` input of one to four images to compare together), and exactly one `text` output. The descriptor is `{schemaVersion:1,operation:"image.describe",image:{$input:"imagePort"},instruction:"bounded package-owned instructions",maxTokens:1..4096,output:"textPort"}`. StillMade loads only owned media and controls the provider, model, payment, approval, and execution receipt. Do not add URLs, credentials, network permission, or executable code. Tests use bounded RGBA pixels and nondeterministic text expectations; they never claim a fabricated description is a live provider result. See `IMAGE_DESCRIPTION.md` and `examples/describe-image.stillmade.json` in the SDK.
## Hosted timeline proposals
For LLM-directed editing, use runtime `capability`, permission `timeline.propose`, a current `timeline` input, up to three additional typed context inputs, and exactly one `timeline-edit` output. The descriptor is `{schemaVersion:1,operation:"timeline.propose",context:{inputName:{$input:"inputName"}},instruction:"bounded package-owned editing policy",maxTokens:1..4096,output:"editPort"}`. Bind every input exactly once. Declare `context.timeline.read` and `timeline.propose`. StillMade controls the provider, payment review, strict JSON request, schema validation, stale-before checks, preview, and Apply. Fixtures use `{kind:"timeline-edit",minCommands:1,maxCommands:100}` expectations. See `TIMELINE_PROPOSALS.md` and `examples/timeline-polish.stillmade.json` in the SDK.
## Generate media from JavaScript
For a JavaScript Block that needs generated images, videos, voice recordings,
music or sound effects (several at once, or decided by code), declare
`permissions.actions: ["media.generate"]` and
return `{schemaVersion:1,kind:"stillmade.media-action",operation:"media.generate",resultPort:"<asset[] output>",request:{items:[...]}}`
from one json output. Items (1–50, unique `key`) are
`{key,operation:"image"|"video",prompt,settings?,references?,firstFrame?,lastFrame?,negativePrompt?,seed?}`
with catalog settings from `packages/block-sdk/generation-catalog.js`
(references only for image models that edit from references; frames only for
video), or `{key,operation:"speech",prompt,settings?:{voice,speed}}` (prompt is
the text to speak, up to 3,800 characters; a catalog voice id such as `adam`;
speed 0.25–4), `{key,operation:"music",prompt,settings?:{durationSec}}` (5–240
whole seconds) or `{key,operation:"sfx",prompt,settings?:{durationSec}}`
(0.5–22 seconds in tenths). The user approves one StillMade credit maximum for
the batch; never include prices, providers or keys. The result port receives
`[{key,kind,assetId,versionId,url}]` with `kind` `image`, `video` or `audio`.
## Hosted video generation
Use `runtime: "capability"` and `permissions.capabilities: ["video.generate"]`. Declare one text, script or brief prompt input, up to two `image` inputs for the first and last frame, and one video output. The capability file is:
{"schemaVersion":1,"operation":"video.generate","prompt":{"$input":"prompt"},"firstFrame":{"$input":"start"},"settings":{"model":"kling-v3-turbo","duration":5,"aspectRatio":"16:9"},"output":"video"}
Optional fields: `settings` (default `{model,duration,resolution,aspectRatio,generateAudio}`), `firstFrame`, `lastFrame` (needs firstFrame and a model with last-frame support), `negativePrompt`, `seed`. Models: seedance-2.0, seedance-2.0-fast, seedance-2.0-mini, kling-v3-turbo, wan-2.6-flash, veo-3.1-lite, vidu-q3-turbo, wan-2.7 (needs a first frame) — see `packages/block-sdk/generation-catalog.js` for each model's durations, resolutions and aspect ratios. Never include endpoints, keys, prices or payment. Users pay with StillMade credits at StillMade's per-second price. Public trials use their separately reviewed acquisition policy and never account BYOK.
The canonical output has `kind:"video"`, bounded `assetId`/`versionId`, a stored HTTPS or host media `url`, `mimeType:"video/mp4"`, measured `width`, `height`, `duration` in seconds and `bytes`. SDK limits are 60 seconds, 256 MiB, at most 3,840 pixels on either axis and 8,294,400 pixels per frame. Validating this shape does not verify ownership or real video bytes; the host must inspect, store and verify them.
Fixtures use `kind:"video"`, `format:"mp4"` and explicit `minDuration`, `maxDuration`, `minBytes`, `maxBytes`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight` bounds. See `packages/block-sdk/video-generation-example.js`. Provider fixtures require approval; they are not deterministic equality tests. `validateGeneratedVideoReference`, `defaultCapabilityExpectations` and `validateCapabilityResult` are available through the SDK. Media receipts bind the owner, run, invocation, storage key and SHA-256 digest to the complete output reference. Guest code cannot issue those receipts.
A host advertises only enabled operations. A valid package does not imply every deployment has video generation configured; unavailable hosts must reject it before billing or dispatch. Public video results use the same private media/claim lifecycle as other trial outputs and are retained when continuing in an account.
## SDK compatibility declarations
The runtime remains SDK `0.1.0`; exact declarations remain valid. A Block may also declare a bounded stable range such as `^0.1.0`, `~0.1.0`, or `>=0.1.0 <0.2.0`. `sdkCompatibility(declared)` reports `supported`, `unsupported` or `invalid` against that actual runtime version. `validateManifest` enforces the same result. A supported range never bypasses permissions, runtime availability, source admission or fixtures.
Supported grammar is an exact stable three-part version, caret, tilde, or up to four space-separated comparator intersections (`=`, `>`, `>=`, `<`, `<=`). Numeric components are bounded to 999999. Tags, prereleases, wildcards, alternatives and hyphen ranges are unsupported. Major-zero caret rules apply: `^0.1.0` excludes `0.2.0`; `^0.0.3` excludes `0.0.4`. A declaration is preserved verbatim in its package identity; changing it requires a new immutable release. Do not widen it merely to silence an incompatibility.
This is compatibility checking, not automatic code adaptation or a promise of future SDK support. Explicit reviewed Step-state migration routes are documented separately; changing an SDK range never runs a migration or changes project pins.
### Authored state migration routes
A new JavaScript Block release can optionally declare `manifest.state.migrations`
for explicit upgrades of its Step memory. A route's `fromVersion` is an older
positive state schema version; its destination is the enclosing `state.version`.
For example, a declaration for state version 2 can contain:
"migrations": [{ "fromVersion": 1, "operations": [ { "op": "rename", "from": "count", "to": "completed" }, { "op": "default", "key": "labels", "value": [] }, { "op": "remove", "key": "temporary" } ] }]
Operations run in order on safe ASCII top-level field names. Rename requires an
existing source and absent destination; default writes only if absent; remove
requires the field to exist. Nested values remain intact. There are at most 16
distinct older-version routes and 64 operations per route. Combined declarations
and every intermediate state each fit within 64 KiB. An empty operation list
explicitly advances only the schema version. There is no implicit chaining,
downgrade, deep-path transformation, expression evaluation or code execution.
The portable SDK exports `validateStateMigrations(stateDeclaration)`,
`stateMigrationRoute(manifest, fromVersion)`, and
`migrateBlockState(previousManifest, nextManifest, savedState)`. The last returns
`{state, operations, fromVersion, toVersion}` without modifying its inputs. Both
manifests must describe the same JavaScript Block identity, with valid state
contracts. Matching state versions use an identity transfer; changed versions
require an exact direct route. Runtime execution does not invoke migrations.
Migration declarations change package source: publish a new immutable Block
release and keep its fixtures on the target schema. Do not guess existing state
fields or reset user memory. In StillMade, owners review exact archived/current
source pins, operations and before/after memory, then explicitly apply. The host
rechecks account/project/source/state, retains rollback history and invalidates
downstream results. External hosts must provide equivalent ownership, source
verification, approval and rollback boundaries before accepting the result.
## Async SDK result and progress types
`runPackage` is synchronous and recipe-only. `runPackageAsync` dispatches the supported runtime and resolves `RunPackageResult`, containing final `outputs` and runtime-appropriate optional metadata/media receipts or JavaScript `state`. External hosts must validate and accept Step state atomically with outputs; the SDK does not persist it. A capability executor returns `CapabilityResult`, which cannot contain Step state: `validateCapabilityResult` rejects that field. Stateful JavaScript output is a different runtime result, not a provider response.
Both recipe entrypoints accept `onProgress({stepId, complete, total})`; asynchronous dispatch forwards this callback when executing a recipe. It is not a provider percentage or a new queued-job protocol. An `AbortSignal` requests cancellation through the existing runtime/host adapter. Provider job polling, progress UI, retries, costs and retained-result adoption remain host responsibilities. Do not return an invented `{pending: ...}` envelope as declared Block outputs or assume a canceled network wait undoes provider work.
## Connected failure recovery
The shared Step failure control supports last-valid recovery for eligible stateless sandbox Blocks and bounded hosted text, speech or image generation with immutable receipts for media. Recovery applies only to interactive connected runs. It is not automatic retry or an SDK pending-result envelope. A successful run must first be retained with this policy, bound to the same account, project, workflow, source and inputs.
Sandbox image recovery requires exact immutable PNG receipts and ordered pixel input evidence, with up to 16 images and 32 MiB of saved PNGs. Hosted text recovery requires only the text.generate capability permission, no other scopes or state, and text/structured ports. After a definite eligible output, expectation or provider-response failure, the host offers an explicit reuse action. It verifies both the failed request and the original successful project receipt, requires the same provider/model/payment selection, and repeats current source, context and access checks before adoption. Pending, uncertain, canceled and unclassified requests are not eligible.
Reused results preserve the original output/receipt identity and are marked skipped; they do not create another generation, reservation or qualifying production execution. Recovery does not change billing for the failed attempt. The original retained successful record must remain available. External hosts must supply equivalent authoritative verification and attribution handling; copying a saved output or setting skipped is not proof. Legacy speech/image receipts, video, ComfyUI, background and standalone recovery remain unsupported; eligible hosted speech/image recovery requires immutable version-2 receipts and the same explicit original-run verification.
## Hosted media receipt versions
Speech and generated-image host receipts accept `schemaVersion: 1 | 2` (generated images also 3: a measured catalog image; generated video receipts are 1 or 2). Version 1 remains readable for existing runs. New StillMade-hosted speech/image output uses version 2: the exact SHA-256 digest is part of a reserved storage key, writes use conditional creation with a checksum, and generic uploads, mirroring and trash restoration cannot replace that object. The host still verifies current owner/project access, original run/source/input/selection and actual stored bytes. SDK shape validation alone never grants media access or establishes immutability.
Only version-2 hosted media receipts can participate in explicit connected last-valid recovery. The host offers reuse after a definite eligible failed run, independently verifies both original run receipts, preserves the original output receipt, and marks reuse skipped for new-execution attribution. No provider call or additional credit reservation occurs during reuse. Legacy media, video and ComfyUI outputs are not made eligible by changing a receipt field or URL. External hosts must supply equivalent authenticated immutable-storage and exact-byte checks before supporting this behavior.
### Authored text choices
`manifest.ui` supports `{port: "tone", control: "select", options: ["Calm", "Energetic"]}` for a `text` input and `{port: "tags", control: "multiselect", options: ["News", "Tutorial"]}` for a `text[]` input. Supply 1–100 unique strings, each at most 1,000 characters, and exactly one control for that input. Empty text is allowed; whitespace and order are significant. These are form choices, not a runtime enum: ordinary text/text[] validation still applies to defaults, upstream connections and custom views.
The shared Create authoring panel edits options locally with explicit Apply. Playground shows existing values outside the choices without silently replacing them. Multiselect preserves unlisted items until the user explicitly removes them and can deliberately supply an empty list. Context-bound and upstream-bound inputs retain their existing read-only presentation. Custom sandbox views continue to own their own controls.
### Authored image output presentation
Add `{control: "gallery", port: "images"}` to `manifest.ui` for an `image[]` output, or `{control: "before-after", port: "result", before: "source"}` for a scalar `image` output compared with a declared scalar `image` input. Each output has at most one presentation; an input and output can share a name without sharing their control. These declarations only affect presentation and cannot fetch external media or change outputs.
The shared Files editor exposes output presentation with explicit Apply for recipe, JavaScript, ComfyUI and capability packages. A completed preview captures the actual decoded comparison input before invocation. Before/After/Side by side buttons compare it with the validated result; gallery navigation uses bounded host result paging. Existing media URL/receipt checks still apply. Authored result controls are also available alongside a custom sandbox view; the package view itself is not rewritten.
### Host project item pickers
Shared Create provides searchable project choices for `shot`, `scene`, `character` and their array inputs. Declare the corresponding `context.shots.read`, `context.scenes.read` or `context.characters.read` permission to make scoped host snapshot items available; this does not add a context binding to the input. Selection explicitly copies the chosen snapshot into the ordinary input. Context-bound and upstream-connected ports stay locked. Shots omit selected media unless `context.assets.read` is also declared, matching the existing context resolver.
Array pickers allow explicit add, remove, reorder and empty-list actions, with paginated choices and selected values. The picker adds up to 100 items without truncating larger existing inputs or changing runtime validation. Missing permissions or scoped items are explained in the UI. No project document is edited, no global library is read, and custom sandbox views receive the same host controls for unlocked compatible ports.
### Shared preview tabs and sections
Optional `manifest.uiLayout` groups existing host controls: `{kind:"tabs",groups:[{id:"source",label:"Source",inputs:["prompt"],outputs:[]},{id:"result",label:"Result",inputs:[],outputs:["text"]}]}`. Use `kind:"sections"` and optional group `collapsed:true` for initially collapsed sections. Supply 1–8 groups with unique lowercase identifiers (up to 40 characters), labels up to 80 characters, and explicit input/output name lists. Assign each port at most once per direction. Empty groups are allowed for drafting and omitted in views where they contain no ports; unassigned ports stay visible.
The shared Files layout editor supports labels, assignments, ordering and initial collapse with explicit Apply. Input and output group navigation are independent host buttons; normal keyboard Tab/Enter/Space work. Existing control values survive tab switches. Package code, permissions, validation, Run, progress and approval UI remain unchanged. Custom sandbox views retain their own internal layout; their host result presentation can use output groups.
### Timeline range and workflow decisions
Shared Create offers explicit range and decision controls for scalar `timeline-range` and `approval` inputs. Standard version 2 range data is exactly `{"schemaVersion":2,"startMs":1000,"endMs":2500}`, with finite millisecond values satisfying `0 <= startMs <= endMs <= Number.MAX_SAFE_INTEGER`. Standard decision data is exactly `{"schemaVersion":2,"decision":"accepted"}`; decisions may be `accepted`, `rejected` or `deferred`. Missing input is not approval. These values are workflow data and never authorize spending, permissions, publishing or project edits.
Existing version 1 application-defined documents remain valid. The host preserves them until explicit conversion and Apply; the consuming Block must support version 2. Upstream/context locks remain in force. The SDK exports `isTimelineRange`, `isApprovalDecision`, their TypeScript interfaces and the range bound. The downloadable pack includes `INTERACTION_VALUES.md` with the full compatibility and authority contract.
### Cooperative pending jobs (JavaScript)
Opt in with `manifest.jobs = {kind:"cooperative",version:1}`. This reserves the `input`, `state`, and `job` function arguments; existing packages without opt-in keep their existing function signature. `job.continuation` is null on the first turn and a JSON copy of the previous pending payload thereafter. Its nested data is modifiable local input, not deeply immutable. Return `job.pending(nextContinuation, {complete, total})` to yield, then return the ordinary declared output object when finished. Progress is optional; supplied units must increase with a fixed positive integer total, at most 1,000,000. Omitted progress reports the continuation turn without inventing a percentage.
// text input/output example; requires manifest.jobs opt-in. if (input.text.length > 4096) throw new Error("Use at most 4096 characters");
const previous = job.continuation || {offset:0,text:""}; const end = Math.min(previous.offset + 256, input.text.length); const next = {offset:end,text:previous.text + input.text.slice(previous.offset,end).toUpperCase()}; if (end < input.text.length) return job.pending(next,{complete:end,total:input.text.length}); return {text:next.text};
The host invokes the same code and original input in the same disposable QuickJS runtime for at most 32 turns. All turns share the existing 500 ms active execution budget, 16 MiB heap / 32 MiB fixed WASM memory, and a cumulative 4 MiB serialized continuation/state/final-output budget. Node keeps its existing 5-second worker wall deadline; browser hosts keep their existing deadlines (at most 15 seconds for sandbox work). Yielding never resets those budgets. Persistent Step state is carried privately between turns and returned for host review only with the final valid outputs; cancellation, failure or limits cannot commit intermediate state.
`job.pending` returns a reserved strict JSON envelope with `$stillmadeJob:1`, `continuation`, and `progress`. It is a request for another bounded sandbox turn, not a trusted capability or permission. No Promise, poll URL, cancel URL, network request, host function, provider job identifier or new timer API is accepted. There is no durable/reload job guarantee. The shared authoring editor exposes opt-in; preview, connected Steps and import sample UI use the existing job handle and cancellation controls. These cooperative jobs do not claim support for external asynchronous services.
## Native tool execution is host-owned
A native `desktopTools` package cannot prove its transformation by returning its
input from `src/run.js`. Generic execution requires a declared `src/desktop.json`
argument/output mapping and a trusted desktop executor. The custom interface
submits the original connected input plus settings to `StillMade.run`; only the
host invokes the approved native action and registers the actual result. Preserve
old immutable releases; introduce a new version for a new execution mapping.
Offline native admission/package checks execute zero fixtures and remain
`execution: "not-run"`, `liveVerified: false`, `reviewRequired: true`. Retain native
receipts with successful connected results; conditional/failure skips stay
explicitly skipped and cannot claim native completion. See DESKTOP_EXECUTION.md.
Acceptance example
The public examples include examples/grayscale.stillmade.json, a versioned update of a separate docs-only authoring exercise that passes current import admission. The unchanged first submission is retained as examples/docs-only-grayscale-original.stillmade.json for inspection.