Skip to main content

Project templates

info

A project template describes a whole project — which blocks it contains, how each is configured, and how they connect. It is unrelated to a workflow template, the Tengo unit of workflow code, and unrelated to the plot template picker in Graph Maker. This page only covers project templates.

What a project template is

A project template is a file listing a project's blocks, each block's parameters, and how they connect. Applying it creates those blocks in a project, configured and wired, in one action instead of by hand. Exporting a project writes the same kind of file back out, so a setup that already works can be reproduced elsewhere.

Each entry names a block by its block kind and a version selector, not by a block package version. A kind is a versioned params contract that changes far more slowly than the block implementing it, so the same file keeps working as new implementing blocks are released.

A project template file

my-pipeline.template.yaml
schema: template-v1
blocks:
- id: samples
kind: "@platforma-open/milaboratories.samples-and-data.kind@1.0.0"
params:
sampleLabelColumnLabel: Sample
- id: mixcr
kind: "@platforma-open/milaboratories.mixcr-clonotyping-2.kind@~1.0.0"
params:
input: { block: samples, name: reads }
species: hsa
chains: [TRB]
preset: { type: name, name: milab-human-dna-xcr-7genes-multiplex }
- id: browser
kind: "@platforma-open/milaboratories.clonotype-browser-3.kind@^1.1.0"

The kind names above are the real kind packages of those three blocks; the versions and the reads output name are illustrative — check a kind's own package.json and the upstream block's outputs for the values to write.

Each entry names a block by kind plus a version selector, sets that block's parameters in domain terms (species, chains, preset), and wires an input by pointing at an earlier entry's output — { block: samples, name: reads }. The third entry omits params entirely, which is legal for a kind whose contract has no required field.

Schema reference

FieldMeaning
schemaMust be exactly the string template-v1. This is the format marker every project template opens with.
blocksA list of entries. Its order is creation order, so every entry must appear after the entries it references.

Entry fields

FieldMeaning
idRequired. Template-local identifier, unique within the file. Names the entry for references from other entries. On export it is the block's project-local UUID, reused verbatim.
kindRequired, always. {name}@{selector}, where name is the kind package's full npm name. Required even when an entry pins its implementation, because it carries the params contract the entry is typed against.
paramsThe block's BlockParams, checked against the kind's own parser before anything is created. May be omitted, which means exactly {} — terseness, not an escape: an entry that omits it still fails for a kind whose contract has a required field.
blockOptional. One exact block package version, {name}@X.Y.Z. Exact only; a range is rejected. Skips kind resolution.
locationOptional. An absolute URI naming where the implementation sits, e.g. file:///abs/path/to/block. For a block that is built but not published. Mutually exclusive with block; an entry carrying both is rejected.

Any other key in an entry, or at the top level, is refused rather than ignored.

warning

location is an absolute path on the machine that wrote it, so a file carrying one applies there and nowhere else. It is a developer affordance, not a way to share a project.

There is no label field — a project template does not name block instances for display.

Version selectors

SelectorIntent
X.Y.ZExact: this kind version and no other.
~X.Y.ZPatch floor: behavior frozen.
^X.Y.ZMinor floor: behavior floats.

The tiers invert conventional semver reading, so read them carefully. For a kind version, a params-incompatible change is a major; a genuine change in behavior or scientific result is a minor; an added optional param with no behavior change is a patch.

So ~ freezes behavior and floats only additive params — the reproducibility-friendly float — while ^ deliberately floats scientific behavior inside the major. Crossing a major always takes a deliberate edit of the file.

warning

A bare X.Y.Z states the author's behavioral intent, not a content hash. The same pin can resolve to different implementing block versions, and nothing here is a lockfile.

Anything outside this grammar is rejected at parse time, including ranges that are legal npm — >=1.0.0, 1.x, latest — with Malformed kind version selector (expected 'X.Y.Z', '~X.Y.Z' or '^X.Y.Z').

References between blocks

A reference to another entry's output has two spellings:

# what an export writes, and what a block holds
sources:
- __isRef: true
blockId: samples
name: numbers

# the same reference, written by hand
sources:
- { block: samples, name: numbers }

Three things follow:

  • Both mean the same reference, and both arrive at the block identically — the readable form is expanded inside the block's own bundle, before the kind's parser runs.
  • block holds a template-local entry id, the same thing a stored reference's blockId holds inside a project-template file.
  • Export always writes the long form, so a hand-edited file and an exported file are not byte-identical even when they are equivalent.

YAML pitfalls

  • Quote any value that must stay a string but reads as something else: "yes", "no", "on", "off", "true", "null", "1:30", "0755", "1.0", "".
  • Quote a value whose first character is * or #"*anchor", "#not-a-comment" — and one containing : , such as "label: value".
  • The file is read as YAML 1.2, so JSON is accepted too: every JSON document is also YAML.
  • A serialized column id arrives as a single quoted JSON string, escapes and all. Leave it as one string; do not try to pretty-print it into nested YAML.

Applying and exporting

The Desktop app's start page offers creating a project from a project-template file. Picking a file parses it, then shows one row per entry with the kind reference and the block version resolved for it, plus a single Allow pre-release block versions option, off by default. Nothing is created until you confirm. Under the hood, apply runs in three stages: every entry is resolved to a concrete block pack; then every block is fetched, checked that it can run against this backend, its workflow template cached, and its params offered to its kind; and only then are all the blocks created, in one transaction. Apply is all or nothing — a reported problem means no block was created. One thing is deliberately not checked beforehand: which entries an entry references. Reading that means reading the params, which only the block can do, and no block exists until stage two has fetched one. So a file whose entry references an entry listed below it does apply, and the block it creates reports itself as missing references — the same way a reference to a deleted block already behaves.

A project can be written back out with Export as Template... on its project card. Each block supplies its own params through its model's templateParams(), and blocks are serialized in dependency order — the project structure is already stored topologically, which is exactly the order a project template needs. Both commands are gated by the app's project-templates setting.

warning

A project template carries a project's setup, not its data. samples-and-data is the worked example: every dataset and metadata column travels as configured, emptied of everything keyed by sample or by group — dataset contents, sample ids, sample labels and metadata values are all stripped. A file handle is portable only when it is a storage reference, so carrying files would make one file mean different things in different installations. The block a project template seeds is the study as it was set up, waiting for its files.

The second limit: what a block exports is what its own params projection returns. UI-only state — table layout, plot configuration — is not carried.

See also