All books

Publishing a node-pack

~3 min read · updated 2026-09-10 · markdown

Once your .nodepack.json validates locally (every kind shows in the picker + compiles cleanly), publish to the Hub for others to install. Two paths — local-test then publish, or smoke-test in a fresh project first.

Local test loop

The fastest iteration loop bypasses the Hub entirely:

1. packs.node.install { manifest: <your full .nodepack.json object> }
2. packs.list         { kind: "node" }                → confirm packId, isBundled:false
3. documents.add        { type: "builder", title: "Pack test" }        → module_id
4. builder.graph.node.add       { module_id, kind: "<your_kind_id>" }          → node id
5. builder.graph.node.add       { module_id, kind: "output" }                  → out id
6. builder.graph.connect        { module_id, source_node_id: <yours>, target_node_id: <out>, target_port: "in_l" }
7. dsp.compile        { module_id }
8. builder.source.get     { module_id }   → verify your toBox emits what you expect

(Not a click-chain — steps 1, 4 and 6 need YOUR pack's manifest + kind ids; ask the assistant to run the loop against your file.)

If compile fails, the error string includes the libfaust diagnostic — usually a missing param ref, an arity mismatch, or a typo in a lib() call. Fix the .nodepack.json, packs.node.install again (writes overwrite atomic), retry.

Publishing to the Hub

When the local loop is happy:

hub.publish {
  kind: "node-pack",
  file_path: "asset:assets/my-pack.nodepack.json",
  slug: "my-pack",
  title: "My Pack — short tagline",
  description: "Longer description for the Hub item page."
}

The Hub backend's NodePackInspector validates the manifest server-side + extracts derived metadata (kindCount, packId, ownerHandle). On success you get back the item's id — record it for follow-up version uploads.

Subsequent versions

hub.version.upload {
  kind: "node-pack",
  item_id: "<id from the publish response>",
  file_path: "asset:assets/my-pack-v2.nodepack.json"
}

The Hub computes the new version number automatically (existing-version + 1). The local pack store refuses to downgrade (incoming version must be ≥ installed) — bump the version field in the manifest on every upload.

Forking + iterating

Hub items can be forked: hub.fork { item_id } creates a copy in your namespace. Useful for "I want to base my pack on faust-vocal but add a new vocal effect" — fork, edit, publish under your handle.

Kb-pack-style packs as a doc surface

If your node-pack covers a meaningful domain (a vocal effects suite, a granular synth toolkit, a vintage gear bundle), pair it with a book documenting the kinds. The book lives separately on the Hub (hub.publish kind:"book") but Users find both when searching for the domain.

The Book Reader module's tutorial blocks can link to your kinds with companionPatches references + assistant hints. See FaustWave — Hub §3 for the publish flow.

Bundling for FaustWave team packs

If you're shipping a FaustWave-team pack that should auto-restore on launch:

  1. Drop the .nodepack.json in packages/app/build-resources/bundled-node-packs/.
  2. Add the packId to BUNDLED_PACK_IDS in packages/app/electron/node-pack-store.ts.
  3. Bump the version field — restoreBundledPacks compares against installed and only re-writes when the bundled file is newer.

Community packs DON'T go in BUNDLED_PACK_IDS. They stay Hub-only — users install them on demand.

Common rejection paths

The validator + adapter at install time check for the most common authoring traps. If any fail, the install rejects + emits a per-line error in Logs:

  • Unknown port id referenced from input('xyz') — the input port xyz doesn't exist in inputs[].
  • Unknown param id referenced from param('xyz') — same for params.
  • Forward def ref — def('A') referenced before defs.A is declared (defs resolve in declaration order).
  • Arity mismatch in seq(a, b) — a's output arity != b's input arity.
  • Missing toBox.outputs.<port> — every output port needs a key. Sinks (zero outputs) use the reserved __process key.
  • Bare-name kind not in scope — prim('XYZ', …) where XYZ isn't in stdfaust + the kind doesn't list the relevant library in imports. Add the lib to the kind's imports field.
  • imports entry not ending in .lib — the validator rejects entries that don't end in the .lib suffix.

Where to read more

  • Source of truth — packages/extension-dsp-builder/src/lib/packKindData.ts carries the NodeKindData validator + the validateNodeKindData() function that runs at install time. The validator's error strings are precise; read it when in doubt.
  • Adapter — packKindAdapter.ts walks toBox and produces the runtime NodeKindDef. Read it to understand the evaluation order + the substitution env.
  • Examples — every file under packages/app/build-resources/bundled-node-packs/ is real production code. Look at the simpler ones first (faust-physical-models, faust-spatial) for clean templates.
  • Architecture — docs/NODE-PACK-ARCHITECTURE-ZIEL.md for the bundled-vs-Hub classification and the long-term direction.
Try it yourself — the IDE runs in your browser. Open the IDE → Get the desktop app

Text licensed under CC BY 4.0 — Mani Weber / FaustWave. For language models: llms.txt · llms-full.txt

AUDIO · 48k · 48.0ms FAUST · 3 KB · 1171 docs CPU · 8.4% BPM · 120.0 UTF-8 BETA· v0.90.0