Building Packages (Real Guide)
What a package really is
A package is three things, and only three things:
1) schema.yaml
- The contract.
- Defines the allowed shape and required fields.
2) data.yaml (or any YAML you pass via --data)
- Must match schema.yaml exactly.
- If required fields are missing or types don’t match, compile must fail.
3) templates/
- Plain text files with ARB tokens.
- The compiler replaces tokens and expands repeats.
If it “kind of works” but isn’t strict, you’ll ship broken generators. ARB’s value is that it refuses to do that.
Canonical package layout
This is the layout you should default to:
packages/my-package/
schema.yaml
templates/
output-1.md.arb
output-2.txt.arb
partials/
_header.md.arb
_footer.md.arb
examples/
data.yaml
Notes:
- examples/data.yaml must compile successfully. Always.
- Partial files should start with "_" and live under templates/partials/.
- If a file is meant to be compiled into output, it should NOT start with "_".
Schema rules (what is allowed)
These are the only schema types you should use:
- object
- string
- number
- boolean
- list
Key patterns:
1) Object:
type: object
required: [a, b]
properties:
a: { type: string }
b: { type: number }
2) List of objects:
type: list
items:
type: object
required: [...]
properties: ...
3) Nested objects:
Define objects inside properties to keep structure explicit.
If you try to use JSON-Schema vocabulary (“array”, “oneOf”, etc.), ARB will reject it.
Schema rules (what is NOT allowed / common mistakes)
Common errors that waste time:
- Using "array" instead of "list"
- Forgetting required fields (then templates reference missing paths)
- Designing schema too loose (optional fields everywhere, then outputs drift)
- Stuffing formatting into schema instead of data/templates
Rule of thumb:
If output correctness depends on a field, make it required.
End-to-end example (simple, realistic)
Goal: generate one README.md with a title, tagline, and a repeated list of commands.
1) schema.yaml:
type: object
required: [project, commands]
properties:
project:
type: object
required: [name, tagline]
properties:
name: { type: string }
tagline: { type: string }
commands:
type: list
items:
type: object
required: [cmd, desc]
properties:
cmd: { type: string }
desc: { type: string }
2) examples/data.yaml:
project:
name: My Tool
tagline: Deterministic generation, no surprises.
commands:
- cmd: mytool init
desc: Initialize a workspace
- cmd: mytool build
desc: Generate outputs
3) templates/README.md.arb:
# {var}project.name{/var}
{var}project.tagline{/var}
## Commands
{rep}commands
- `{var}cmd{/var}` — {var}desc{/var}
{/rep}
4) compile:
arb compile --package packages/my-package --data packages/my-package/examples/data.yaml --out out
Output: out/README.md
That’s the complete loop: schema → data → template → output.
Template tokens (reference)
ARB templates support a small set of directives:
1) Variable substitution
{var}path.to.value{/var}
2) Conditional blocks
{if}path.to.value
...rendered if present/truthy...
{/if}
3) Repetition over lists
{rep}list.path
...inside, {var}field{/var} refers to each item...
{/rep}
4) Includes (partials)
{inc}relative/path/to/template.arb{/inc}
Notes:
- Include target must be a literal string and must end with .arb
- Include resolution is relative to the current template
- Include cannot escape templates/
- Files starting with "_" are skipped as outputs (include-only convention)
Template rules (what you should NOT do)
Don’t try to re-create a template engine in ARB.
Avoid:
- Nested “logic” that becomes hard to reason about
- Using {if} everywhere instead of making schema strict
- Making templates depend on missing/optional data paths
- Hiding complexity in partials
Rule:
If you need complex branching, redesign schema + data so the template stays boring.
Lists and repeats (designing for {rep})
If you want repeated output, model it as a list in schema/data.
Good:
sections:
- heading: ...
body: ...
- heading: ...
body: ...
Then template:
{rep}sections
{var}heading{/var}
{var}body{/var}
{/rep} Bad: section_1_title / section_2_title / section_3_title That becomes unmaintainable immediately.Partials (how they actually work in ARB)
ARB supports partials via an explicit include directive in the template engine.
Conventions:
- Partial template files start with "_" (example: _nav.html.arb).
- Files starting with "_" are skipped as output files by the compiler.
- Partials typically live under templates/partials/ for clarity, but this is a convention, not a requirement.
Including a partial:
- Use the include directive:
{inc}partials/_nav.html.arb{/inc}
- The include path must be a literal string and must end in ".arb".
- Include paths are resolved relative to the current template.
- Includes cannot escape the templates/ directory.
- Include cycles are detected and rejected by the compiler.
- Maximum include depth is limited (to prevent runaway expansion).
Why partials exist:
- Reduce duplication (headers, footers, navigation blocks).
- Centralize shared markup or text without re-implementing logic.
- Keep output templates readable and consistent.
What partials are NOT:
- A place to hide control flow or logic.
- A second template language layered on top of ARB.
- A substitute for well-designed schema and data files.
Practical guidance:
- If you need conditionals or repetition inside a partial, drive them with schema/data
and use {if} or {rep} inside the partial itself.
- If partial usage makes it hard to understand final output by reading templates,
the package has probably become too clever.
Debugging failures (fast)
ARB failures are usually one of these:
1) YAML parse error
- Usually indentation or unescaped ":" in a bad context.
- For long bodies and code blocks inside YAML, use "|" not ">".
2) Schema validation error
- Data doesn’t match schema (missing required field, wrong type, wrong structure).
3) Template missing value path
- Template references a path that doesn’t exist in data.
- Fix the template path or fix the schema+data to include it.
Your quickest workflow:
- Fix YAML syntax first.
- Then fix schema/data mismatches.
- Then fix template paths.
Package quality checklist (before you ship it)
Before calling a package "done":
- examples/data.yaml compiles cleanly with a single command
- templates do not reference undefined paths
- schema is strict where output correctness matters
- lists are used for repeated structures
- partials reduce duplication but do not add hidden logic
- output is deterministic (diff stable across runs)