Skip to main content

Includes and templates

Split large workflows into reusable pieces. Includes import template files into your workflow at parse time. Templates (extends) let jobs inherit configuration from hidden job definitions, reducing duplication across similar jobs.

include:
- local: .loom/templates/common.yml

.node-base:
image: node:20
variables:
NODE_ENV: production

lint:
extends: .node-base
stage: ci
target: linux
script:
- npm run lint

Resolution pipeline

Loom resolves includes and templates in a fixed order before validation:

1. Parse main workflow YAML
2. Resolve includes (recursive, depth-first)
3. Merge included definitions into the root
4. Apply default configuration to all jobs
5. Resolve extends chains (template inheritance)
6. Validate the fully-resolved workflow

This means included files can define templates that jobs in the main file extend, and default values are applied before template merging so both parent and child jobs see defaults.

Includes (include.local)

Use include to import local YAML template files into your workflow.

Syntax

include:
- local: .loom/templates/common.yml
- local: .loom/templates/languages/node.yml

Rules

ConstraintEnforcement
include must be a YAML sequenceSchema error if not a sequence
Each entry must be a mapping with only the key localUnknown keys are rejected
Path must start with .loom/templates/Rejected otherwise
Path must end with .yml or .yamlRejected otherwise
Path must not contain ..Path traversal is rejected
Cycles are detectedError shows the full include chain

Nested includes

Included files can themselves contain include entries. Loom resolves them recursively. Cycles are detected and rejected with a descriptive error showing the full chain:

resolve include cycle by removing one include edge:
.loom/workflow.yml -> .loom/templates/a.yml -> .loom/templates/b.yml -> .loom/templates/a.yml

Merge order

Included files are merged in declaration order: earlier includes first, then later includes, then the main workflow file. When the same key appears in multiple files:

  • Scalars and sequences (strings, numbers, lists): the last definition wins.
  • Mappings (like variables): keys merge recursively — later files override specific keys while preserving others.

Keep includes small, composable, and organized by intent:

.loom/
templates/
common.yml # shared defaults
languages/
node.yml # Node.js toolchain setup
go.yml # Go toolchain setup
jobs/
lint.yml # reusable lint job templates
test.yml # reusable test job templates

Name templates by what they do (languages/node.yml, jobs/lint.yml) rather than by team or project names. Prefer several small files over one large include — this makes templates easier to find, review, and compose.

Why path restrictions exist

include.local is restricted to .loom/templates/ and rejects .. path traversal to keep includes:

  • Local and reviewable — all included files are checked into the repo under a known directory.
  • Harder to smuggle — files from unrelated directories cannot be pulled in accidentally.
  • Auditable — reviewers can inspect exactly what a workflow includes by looking at one directory.

To use a template from outside the repo, vendor it into .loom/templates/ and review it like any other code change.

Templates (extends)

A job whose name starts with . is a template job. Template jobs are not executed directly — they provide reusable configuration via extends.

Syntax

.base:
image: loom:nix-local
variables:
PNPM_STORE_DIR: .pnpm-store

check:
extends: .base
stage: ci
target: linux
script:
- pnpm i --frozen-lockfile
- pnpm run check

Rules

ConstraintEnforcement
extends must be a single template job nameLists/multiple inheritance not supported
The parent name must start with .Rejected otherwise
The parent must exist in the workflowError if template is not found
Cycles are detectedError shows the full extends chain
Templates can chain (.a extends .b extends .c)Resolved recursively

Merge semantics

When a child job extends a parent template, configuration is merged with these rules:

Value typeBehaviorExample
Scalars (strings, numbers, booleans)Child replaces parenttarget: linux in child overrides parent
Sequences (lists)Child replaces parent entirelyscript: ["echo child"] replaces parent's script
Mappings (like variables)Keys merge recursively — child overrides, parent provides defaultsChild adds BAR; parent's FOO is preserved
CacheSpecial merge — named caches merge by name, unnamed caches follow mapping/sequence rulesSee Cache

Example showing merge behavior:

.base:
variables:
FOO: "from-base"
BAR: "from-base"
script:
- echo "base"

child:
extends: .base
stage: ci
target: linux
variables:
BAR: "from-child"
NEW: "child-only"
script:
- echo "child"

Effective resolved configuration for child:

KeyResolved valueWhy
variables.FOO"from-base"Inherited from parent (mapping merge)
variables.BAR"from-child"Child overrides parent (mapping merge)
variables.NEW"child-only"Added by child
script["echo child"]Child replaces parent (sequence replacement)

Template requirements

Template jobs (names starting with .) must include at least one of:

  • script — so they provide runnable commands.
  • extends — so they chain to another template.

A template with only image and variables but neither script nor extends produces a schema error. Add a script to fix it.

Debugging resolution

Step 1: Validate structure

Run loom check to catch YAML and schema issues before resolution:

loom check

Common errors at this stage:

  • Invalid include.local path (wrong prefix, missing extension, path traversal).
  • Missing template reference (extends points to a name that doesn't exist).
  • Include cycle or extends cycle.

Step 2: Inspect the resolved workflow

Run loom compile to see the fully-resolved workflow after includes are merged, defaults are applied, and extends chains are resolved:

loom compile --workflow .loom/workflow.yml

This shows the effective configuration each job will run with. Use it to verify that template merging produced the expected result — especially for variables (mapping merge) and script (sequence replacement).

Step 3: Diagnose runtime failures

If the workflow resolves and validates but a job fails at runtime, the issue is in execution, not resolution. Use the diagnostics ladder:

Planned

  • Remote includes — load templates from catalogs or registries outside the local repo.
  • Multi-template composition — extend from multiple templates with explicit conflict rules.
  • Template parameters — pass arguments to templates for more flexible reuse.
  • Template versioning — pin included templates to specific versions for reproducibility.

Next steps