Module Composition
Modules can reference other modules. This is Loom's answer to the question: "how do I reuse automation across teams?"
Example
# root module
spec:
params:
- name: serviceName
required: true
- name: namespace
default: "default"
modules:
- name: base-infra
source: "./modules/base-infra"
params:
namespace: "{{ .namespace }}"
- name: argocd-app
source: "https://github.com/myorg/loom-modules.git"
params:
serviceName: "{{ .serviceName }}"
namespace: "{{ .namespace }}"
operations:
- name: commit-all
commitPush:
message: "feat: full onboard for {{ .serviceName }}"
author: "loom-bot"
email: "loom@example.com"Execution Order
- Child modules run first, in the order they're listed
- Each child module can have its own child modules (recursive)
- Then the parent's operations run
- All modules write into the same target directory
A child (or an individual operation) can be gated with an if predicate — a shell expression that skips the step when it exits non-zero.
Module Sources
Sources can be:
- Local paths (
./relative/pathor/absolute/path) — resolved relative to the parent module - Git URLs — cloned to a temporary directory automatically
- Git URLs with
//separator —repo.git//subdirclones the repo and usessubdir/as the module root
The // convention (borrowed from Terraform) lets a single Git repository host multiple modules in different directories:
modules:
- name: networking
source: https://github.com/myorg/loom-modules.git//networking
- name: monitoring
source: https://github.com/myorg/loom-modules.git//monitoringThe source field is templatable, so the subdirectory can be dynamic:
modules:
- name: env-module
source: "https://github.com/myorg/loom-modules.git//{{ .environment }}"
params:
env: "{{ .environment }}"Sibling modules within the same repo
When a git URL contains //, the entire repository is cloned (not a sparse checkout). Modules within the same repo can reference each other using relative local paths:
# repo layout:
# loom-modules/
# networking/loom.yaml
# monitoring/loom.yaml
#
# inside networking/loom.yaml:
modules:
- name: monitoring
source: ../monitoring # resolves to <cloneDir>/monitoringThis means you can publish reusable modules as Git repositories. A platform team maintains the standard modules; product teams compose them.
Parameter Flow
Parameters are passed from parent to child through the params field. Child params are rendered through the parent's template context, so you can reference any parent parameter:
modules:
- name: child-module
source: "./child"
params:
env: "{{ .env }}"
label: "{{ .serviceName }}-{{ .namespace }}"Each child module resolves its own spec.params independently -- only the values passed in params are available. There is no implicit inheritance of parent parameters.
Conditional Modules
A child reference accepts an optional if predicate that decides whether the module runs. The string is rendered with the parent's params (like name, source, and params), then executed via sh -c: exit 0 runs the child, any non-zero exit skips it — along with all of its operations and sub-modules.
modules:
- name: argocd-app
source: "./modules/argocd-app"
if: "test -d argocd" # skip unless the target has an argocd/ dir
- name: prod-only
source: "./modules/prod-only"
if: '[ {{ .env }} = prod ]' # params are templated before the shell runsThe predicate runs in the child's resolved target directory — its own spec.target clone if it has one, otherwise the parent target it inherits — so it can inspect the very repository the child would change. (A child with its own target is therefore cloned before its if is evaluated.)
The same if field works on individual operations; see the operations reference. Predicates are evaluated in every mode, including --dry-run, so keep them side-effect-free (test, grep, file checks).
Bulk Runs
Repeating the same child module with different params is how you run one module in bulk — and with loom.jsonnet the entries can be generated from a list instead of written by hand. See Bulk Runs for the full patterns, including how to ship a batch as one PR or one PR per item.