Formats
Five writers, one schema. They all carry the same information; they differ in how well they nest and how easily the receiving system reads them.
| Format | Output | Good for |
|---|---|---|
| JSON | data/archive.json | The canonical, lossless master file |
| NDJSON | data/archive.ndjson | Huge sites — one object per line, nothing to hold in memory |
| XML | data/archive.xml | Systems that speak XML; rich text is preserved in CDATA |
| YAML | data/archive.yaml | Reading or hand-editing the model before an import |
| CSV | data/csv/*.csv + schema/*.csv | Spreadsheets and simple importers |
JSON
One document, and the format everything else is measured against. Read it and you have the whole bundle:
{
"meta": { … generator, source, format … },
"schema": { … the site's structure … },
"records": {
"entries": [ … ],
"categories": [ … ]
}
}
Pick JSON unless you have a reason not to.
NDJSON
Newline-delimited JSON: one self-describing object per line, so a reader never has to hold the document in memory.
{"_type":"meta","meta":{…}}
{"_type":"schema","schema":{…}}
{"_type":"record","recordType":"entries","record":{…}}
{"_type":"record","recordType":"entries","record":{…}}
Every line names its own type, so an importer can stream the file and dispatch line by line. This is the format for a site large enough that json_decode on the whole document is a bad idea.
Reading one back? Don’t split on newlines naively — rich text values contain them. Use a JSON-aware line reader.
XML
Built with DOM, not string concatenation, so it’s well-formed by construction:
<archive formatVersion="1.0">
<meta>…</meta>
<schema>…</schema>
<records>
<record type="entries">…</record>
</records>
</archive>
The conventions that make arbitrary Craft field handles safe to emit:
- An object becomes one child element per key; a list becomes repeated
<item>elements. - A key that isn’t a legal XML name becomes
<item key="…">. - Null is
<foo nil="true"/>; booleans are the stringstrueandfalse. - Values containing markup or newlines are wrapped in CDATA, so rich text survives intact.
YAML
The JSON document, in YAML, and structurally identical to it — same keys, same nesting. Worth choosing when a human is going to read the bundle before a machine does: reviewing what a migration is about to carry, or hand-editing the schema before feeding it to an importer.
CSV
CSV can’t nest, so this format is a set of files rather than one document:
data/csv/<type>.csv one row per record, columns flattened to dotted keys
data/csv/relations.csv one row per relation target, joining records by uid
schema/<key>.csv the site's structure, which has nowhere else to go
The conventions:
- Nested keys flatten to dotted columns:
container.section,author.username,file.filename. - The header is the union of every row’s keys, so a column one record uses and another doesn’t still appears.
- Multi-value cells use
|as the separator. - Each field becomes a single
fields.<handle>column. Relations collapse to a list of target uids — the full targets are inrelations.csv. Anoptioncollapses to its value. - Anything that genuinely can’t flatten — Matrix blocks, table rows,
raw— is JSON-encoded into its cell. - A record type that matched nothing is skipped rather than written as an empty file. The manifest still records the zero.
- Quoting is plain RFC 4180. No backslash escaping.
Cells contain newlines. Parse with a real CSV reader, not by splitting lines.
Adding a format
Writers are registered by event, so a target-specific one — WordPress WXR, an in-house importer’s format — can be added from a module without touching Archive. See Extending.