Generate a Hundred Files from One Template

Introduction

Onboarding letters, per-service config files, a reference table that must match the schema — all of it gets hand-written, and all of it drifts the moment the data changes. This post keeps the facts in one data file and generates the files from a template: a personalized file per record, or a single doc rolled up from structured data. Edit the data, re-run, done.

Install the packages

aux4 aux4 pkger install aux4/template aux4/json

Step 1: a template and one render

aux4/template renders Handlebars. Any --key value flag becomes a template variable. Create welcome.hbs:

Hi {{name}},

Welcome aboard as our new {{role}}! Your first day is {{start}}.

— The Team

Render one:

aux4 template --file welcome.hbs --name Sally --role Engineer --start 2026-08-01
Hi Sally,

Welcome aboard as our new Engineer! Your first day is 2026-08-01.

— The Team

Step 2: one file per record — the hundred files

Put the data in a file — people.json, a list of records:

[
  { "name": "Sally", "role": "Engineer", "start": "2026-08-01" },
  { "name": "Marco", "role": "Designer", "start": "2026-08-04" },
  { "name": "Priya", "role": "PM",       "start": "2026-08-06" }
]

Now run the template once per record with aux4 json exec, writing each result to its own file. ${item.field} is the current record's field, and --output names the file:

cat people.json | aux4 json exec 'aux4 template --file welcome.hbs --name "${item.name}" --role "${item.role}" --start "${item.start}" --output "${item.name}.txt"'

Three records in, three files out — no loop, no scripting:

ls *.txt
Marco.txt
Priya.txt
Sally.txt
cat Sally.txt
Hi Sally,

Welcome aboard as our new Engineer! Your first day is 2026-08-01.

— The Team

Ten records make ten files; ten thousand make ten thousand. The template never changes — only the data does.

Step 3: a single doc from structured data

The same tool rolls many records into one file. For a document like an API reference, put the records under a key and iterate them with {{#each}}. Create api.json:

{
  "endpoints": [
    { "name": "List users",   "method": "GET",  "path": "/users",  "description": "Return all users.", "auth": true },
    { "name": "Create user",  "method": "POST", "path": "/users",  "description": "Create a new user.", "auth": true },
    { "name": "Health check", "method": "GET",  "path": "/health", "description": "Liveness probe.",    "auth": false }
  ]
}

And reference.hbs — note {{#if auth}} reads the real JSON boolean:

# API Reference

| Name | Method | Path | Auth |
| --- | --- | --- | --- |
{{#each endpoints}}
| {{name}} | `{{method}}` | `{{path}}` | {{#if auth}}Yes{{else}}No{{/if}} |
{{/each}}

Pass the whole file as the context with --data and get a finished Markdown table:

aux4 template --file reference.hbs --data api.json
# API Reference

| Name | Method | Path | Auth |
| --- | --- | --- | --- |
| List users | `GET` | `/users` | Yes |
| Create user | `POST` | `/users` | Yes |
| Health check | `GET` | `/health` | No |

Add --output REFERENCE.md to write it to a file, wire that into an .aux4 command (aux4 docs generate), and your reference is regenerated from the source of truth every time — it can't go stale, because nobody edits the Markdown by hand.

Conclusion

aux4/template turns data into files: aux4 json exec fans a template out to one file per record, and --data with {{#each}} rolls records up into a single document. The facts live in one place; the files are always a re-run away. Onboarding letters, config files, invoices, docs — write the template once and generate the rest.

See Also