Use @pathmx/generate to turn JSON or CSV records into ordinary Markdown
Sources with Knap templates.
Generated Sources work with PathMX's existing queries, links, layouts, and editing.
bun run pmx plugins add generate
Put your records in data/artists.json:
[
{
"slug": "ada-clay",
"title": "Ada Clay",
"category": "Ceramics",
"booth": "A12"
},
{ "slug": "rowan-paper", "title": "Rowan Paper", "category": "Printmaking" }
]
Create templates/artist.knap.md beside your Paths directory:
---
{{ category | yaml_property:"category" }}
---
# {{ title | escape_md }}
{{ category | escape_md }}
{% if booth %}Visit booth {{ booth | escape_md }}.{% endif %}
Preview the resulting files:
bun run pmx generate \
--template templates/artist.knap.md \
--data data/artists.json \
--output 'paths/artists/{{ slug }}.artist.md' \
--dry-run
Each record supplies the variables for both the template and the output path.
This example creates paths/artists/ada-clay.artist.md and
paths/artists/rowan-paper.artist.md. Ada's page includes the booth line;
Rowan's omits it. Remove --dry-run to create the files.
Quote output patterns so your shell passes them unchanged. Outputs must be
visible .md files inside the selected Paths directory. Templates and input
data may live elsewhere. Keep templates outside Paths when they are authoring
inputs rather than pages you want PathMX to index.
Save the same request in paths/artists.recipe.md:
---
generate:
template: ../templates/artist.knap.md
data: ../data/artists.json
output: "./artists/{{ slug }}.artist.md"
---
# Generate artist pages
Create one artist Source per record in the gallery export.
Review replacements before overwriting hand-edited pages.
Run it with:
bun run pmx generate paths/artists.recipe.md --dry-run
The recipe is a Source in the selected Paths directory. Its type is your choice:
.recipe.md, .import.md, or another filename works. The plugin validates the
generate declaration's shape; pmx lint reports malformed declarations.
template, data, and output are required nonempty strings. Optional
dataFormat is json or csv. Other declaration fields are rejected.
Arguments override the corresponding saved fields. Paths in the declaration resolve beside the recipe Source; paths supplied as arguments resolve from the working directory. The recipe filename itself is also working-directory-relative.
bun run pmx generate paths/artists.recipe.md --data data/updated-artists.csv --dry-run
JSON input accepts one object or an array of objects. CSV uses the first row for column names and retains all values as strings. Quoted commas, embedded newlines, and UTF-8 BOMs are supported. Empty batches, inconsistent CSV rows, and empty or duplicate column names are errors.
The data filename selects CSV for .csv; other filenames default to JSON.
Use --data-format csv or --data-format json to override that choice.
Piped input defaults to JSON:
cat data/artists.json | bun run pmx generate \
--template templates/artist.knap.md --data - \
--output 'paths/artists/{{ slug }}.artist.md' --dry-run
Use --data - --data-format csv for piped CSV. A recipe can also declare
data: '-' and dataFormat: csv.
Templates use Knap syntax. Variables come directly from each
record; PathMX's live $ bindings and implicit source scope are not supplied.
Knap evaluates its tokens throughout the template, including code fences.
Use escape_md for literal text and yaml_property for metadata. Raw values
can deliberately emit Markdown and HTML. Source generation does not automatically
escape every value or sanitize authored markup.
--dry-run renders the entire batch, validates proposed Source changes, and
prints file diffs without committing them. Templates report errors with their
filename, line, column, and input record. --json returns a structured report
with file paths, statuses, warnings, and preview diffs.
Existing files with identical content are unchanged. Replacing different
content requires --overwrite, including during a preview:
bun run pmx generate paths/artists.recipe.md --overwrite --dry-run
bun run pmx generate paths/artists.recipe.md --overwrite
Replacement writes the entire generated Source. Keep manual additions in the template or input data if they should survive later replacements. The command does not merge content, synchronize remote data, or delete stale outputs.
Duplicate destinations, paths outside Paths, symbolic-link destinations, and outputs that replace the recipe, template, or data input are rejected. Rendering and validation finish before any generated Source is committed. Writes use PathMX's existing Source transaction lifecycle, so watching servers see ordinary Source changes.