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:

typecontainerAlso carries
entrysection, sectionName, sectionType, entryType, entryTypeNameauthor, authors, postDate, expiryDate; level + parent in structures
categorygroup, groupNamelevel, parent
taggroup, groupName
globalSethandle, name
assetvolume, volumeName, folderPathfile
userusername, email, fullName, admin, pending, locked, suspended, lastLoginDate, groups, preferences, photo
addresslabel, fullName, organization, countryCode, administrativeArea, locality, postalCode, addressLine13, 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.
kindCraft fieldsvalue
textPlain Text, Email, URL, Countrystring
richTextCKEditor, Redactor, other HTML fieldsHTML string, reference tags already resolved
numberNumber, Rangenumber
booleanLightswitchboolean
dateDate, TimeISO 8601 string
optionDropdown, Radio Buttons, Button Groupvalue + label, or null when nothing is selected
optionsCheckboxes, Multi-selectarray of the above
relationEntries, Categories, Tags, Users, Assetsarray of element refs
blocksMatrix, Content Blockarray of uid, type, typeName, sortOrder, enabled, fields — recursively
tableTablearray of row objects keyed by column handle
linkLinktype, value, url, label
colorColourhex string
moneyMoneyamount + currency
addressAddressesarray of address objects
seoSEOMaticthe meta bundle, flattened to plain keys
rawanything Archive doesn’t recognisewhatever 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.