Bundle format
Archive Bundle Format v1. A bundle is a ZIP file; everything inside it is plain text or a copied asset file, and nothing in it needs Craft to read.
my-site-2026-07-26-140233.zip
├── manifest.json always JSON, whatever the chosen format
├── README.txt human-readable description of this bundle's layout
├── data/
│ └── archive.json the master data file, in the chosen format
│ (or archive.ndjson / archive.xml / archive.yaml, or a csv/ directory)
└── assets/
└── <volumeHandle>/<path>/<filename> copied files from local volumes only
The site’s structure lives inside the master data file under schema — sites, sections, entry types, field definitions, category and tag groups, volumes, filesystems, global sets, user groups with their permissions, routes, the installed plugin list, and a curated set of system settings. Formats that can’t nest, like CSV, put it in a schema/ directory instead.
Filesystems are described by name and type only. Their settings are never exported: that’s where an S3 or Spaces filesystem keeps its access key and secret, and a bundle is a file people email each other.
manifest.json
{
"archiveFormatVersion": "1.0",
"generatedAt": "2026-07-26T14:02:33+00:00",
"generator": {
"plugin": "Archive",
"pluginVersion": "1.0.0",
"craftVersion": "5.10.0",
"phpVersion": "8.2.20"
},
"source": {
"systemName": "My Site",
"primarySiteUrl": "https://example.com",
"sites": [
{ "handle": "default", "name": "My Site", "language": "en-US",
"primary": true, "baseUrl": "https://example.com" }
]
},
"format": "json",
"dataFiles": ["data/archive.json"],
"contents": { "entries": 128, "categories": 9 },
"assets": {
"included": 42, "referenced": 7, "skipped": 1, "bytes": 18452113,
"files": ["assets/images/hero.jpg", "…"]
},
"options": { "…the export options used…" },
"warnings": ["…anything Archive could not fully represent…"]
}
contents is the authoritative record count per type. warnings is where a field type with no serializer, an unreadable asset, or a skipped oversize file gets reported — an empty array means the export was complete.
Records
A record is one element in one site. Records from the same element share a uid and differ by site, which is how translations stay linkable without nesting.
{
"uid": "3f2b9a1c-…",
"id": 412,
"type": "entry",
"sourceClass": "craft\elements\Entry",
"site": "default",
"siteId": 1,
"language": "en-US",
"title": "Hello world",
"slug": "hello-world",
"uri": "news/hello-world",
"url": "https://example.com/news/hello-world",
"status": "live",
"enabled": true,
"container": {
"section": "news",
"sectionName": "News",
"sectionType": "channel",
"entryType": "article",
"entryTypeName": "Article"
},
"author": { "uid": "…", "username": "jholt", "name": "Justin Holt" },
"parent": { "uid": "…", "slug": "…", "title": "…" },
"level": 1,
"order": 3,
"dateCreated": "2026-01-04T09:11:00+00:00",
"dateUpdated": "2026-07-02T16:40:12+00:00",
"postDate": "2026-01-04T09:00:00+00:00",
"expiryDate": null,
"fields": { … },
"relations": [ … ]
}
Keys that don’t apply to a type are omitted rather than set to null — a category has no author or postDate, so those keys simply aren’t there.
Per type
container says where a record lived, and differs by type:
type | container | Also carries |
|---|---|---|
entry | section, sectionName, sectionType, entryType, entryTypeName | author, authors, postDate, expiryDate; level + parent in structures |
category | group, groupName | level, parent |
tag | group, groupName | — |
globalSet | handle, name | — |
asset | volume, volumeName, folderPath | file |
user | — | username, email, fullName, admin, pending, locked, suspended, lastLoginDate, groups, preferences, photo |
address | — | label, fullName, organization, countryCode, administrativeArea, locality, postalCode, addressLine1–3, latitude, longitude, owner |
user and address records carry no site, siteId or language, because those element types have no per-site content in Craft. Every other type has one record per site.
Field values
Every entry under fields has four keys:
kind— what the value is, in Archive’s own vocabulary. This is what an importer should switch on, and it’s stable across whichever plugin produced the field.type— the originating Craft field class, kept for provenance.typeName— that field type’s display name.value— plain, JSON-representable data.
kind | Craft fields | value |
|---|---|---|
text | Plain Text, Email, URL, Country | string |
richText | CKEditor, Redactor, other HTML fields | HTML string, reference tags already resolved |
number | Number, Range | number |
boolean | Lightswitch | boolean |
date | Date, Time | ISO 8601 string |
option | Dropdown, Radio Buttons, Button Group | value + label, or null when nothing is selected |
options | Checkboxes, Multi-select | array of the above |
relation | Entries, Categories, Tags, Users, Assets | array of element refs |
blocks | Matrix, Content Block | array of uid, type, typeName, sortOrder, enabled, fields — recursively |
table | Table | array of row objects keyed by column handle |
link | Link | type, value, url, label |
color | Colour | hex string |
money | Money | amount + currency |
address | Addresses | array of address objects |
seo | SEOMatic | the meta bundle, flattened to plain keys |
raw | anything Archive doesn’t recognise | whatever the field’s own serializeValue() produces |
raw is the honest fallback: the value still travels, it just arrives as opaque data whose shape is the originating plugin’s business. Field types from third-party plugins land here until Archive learns them — or until you teach it.
Rich text is exported with parseRefs() applied, so {asset:14:url} becomes the real URL rather than a Craft-only token.
Element references
Any pointer to another element — a relation target, an author, a parent — uses the same shape, so an importer only ever has to learn one:
{ "uid": "…", "id": 14, "type": "asset", "title": "hero.jpg",
"url": "https://example.com/uploads/hero.jpg" }
Asset refs carry extra keys, and are the one place a bundle distinguishes local from remote — see Assets.
Relations
"relations": [
{
"field": "relatedEntries",
"fieldType": "entries",
"targets": [
{ "uid": "…", "id": 9, "type": "entry", "title": "Another post",
"url": "https://example.com/news/another-post" }
]
}
]
Relations appear both inside the field value that produced them and in the record’s top-level relations array, so an importer can walk the graph without descending into every field.
Stability
archiveFormatVersion follows semver. Additive keys are a minor bump; anything that would break a reader written against 1.x is a major bump. The full specification also ships with the plugin, at docs/FORMAT.md.