New in 0.4.0. Export and import live in the Content Library’s own menus — there is no separate transfer screen, because a transfer is an action on content somebody is already looking at.
| Where | What is there |
|---|---|
| The entry editor’s ⋯ menu | Export…, for the record you have open |
| The selection bar’s ⋯ menu | Export, for what you have ticked |
| The collection’s ⋯ menu | Import… |
Import belongs to the collection rather than the selection because there is nothing selected when you import — what arrives is whatever the file holds. It is hidden in the trash view, where the rows are on their way out.
What an export carries
The records you chose, plus one hop: the records they point at. An article’s author comes along so the link can be made again on the other side.
Depth-1 records still get resolvable references, filled from a key-only projection over the next layer out. A reference carrying only a foreign row id would be unresolvable anywhere else, which is the whole problem natural keys exist to solve.
The export dialog’s counts come from a preview that runs the same graph walk the export will. “Include related records” is the difference between 40 records and 4,000, and the count is the only warning you get.
Formats
| Format | Carries files | Lossless | Good for |
|---|---|---|---|
| JSON | no | yes | One file, several types, human-readable |
| NDJSON | no | yes | Streaming, and diffing line by line |
| ZIP | yes | yes | Anything with media attached |
| CSV | no | no | A spreadsheet somebody will edit |
Only ZIP carries asset bytes. Pick anything else and the “include files” toggle disables itself rather than accepting a promise the server will not keep.
CSV is lossy on purpose, and predictably
Media cells are deliberately not reconstructed on the way back in. A filename in a spreadsheet is not a file, and inventing an asset reference from one relinks records to whatever happened to share a name.
Use CSV to get content out to somebody who wants a spreadsheet. Use JSON or ZIP to move content between installations.
How a record is matched on the way in
Not by row id. A row id names one database, so everything an import can do rests on a natural key — the field that identifies a record in a way that means something somewhere else.
A localized field is a candidate. A transfer record is one row, not one
record across languages, so a per-locale slug identifies exactly the row it
belongs to; the locale is folded into the key so en/hello and de/hello
stay apart.
Matching runs in two passes, in this order:
- The natural key, which is the identity that means something on another installation.
- The source row id, and only when the key found nothing. A
$idthat names a live row of this type in this workspace is not a guess — it is the row the document was written from.
That second pass is what lets a type with no derivable identity field be linked to rather than copied on every import.
Name your identity fields
Left unconfigured, a type falls back to a derived guess: a field named like
an identifier — slug, sku, email — then the first required text field.
Usually right, reported in every export’s manifest, and still a guess.
TransferPlugin({ identity: { product: ['sku'] } }) is the setting that
decides whether importing the same file twice updates records or duplicates
them. See the transfer plugin.
Two questions, not one
The import dialog asks about the records in the file and the records they point at, separately.
| Question | Governs | Default |
|---|---|---|
| If a record is already here | The records the file is about | — |
| Records this file links to | The depth-1 records they point at | Link |
A single answer cannot serve both. An export carries an article’s author so the link can be made again, and “add a second copy of this article” must not quietly mean “and a second author”.
Link writes nothing to a related record that already exists. It exists so the link can be made, not so the row can be rewritten.
Verdicts before anything is written
Import is two-phase: a preview pass reports what each record in the file would do — created, updated, linked, skipped — and only then does the Import button do it.
Verdicts go stale deliberately. Changing the file, or either policy, retires the table: showing one file’s verdicts above another file’s Import button would be the worst bug this dialog could have.
After a run, every type the import touched is refreshed — the verdicts name them, so the set is exact. A run that creates an article and the author it points at refreshes both lists.
Limits
An export is bounded so a careless filter cannot ask one request to stream a whole library into memory. An import is bounded because the file is untrusted: a small archive can describe an enormous one, and the compression ratio is the signal that arrives before the bytes do.
The full table of ceilings, and how to override them, is on the transfer plugin.
What it guarantees
Every write goes through the same writer service the editor uses — field validation, the workspace scope, relation-target checks, the same-locale rule, the media-asset check, revisions, the audit log and the localization extension all run.
Nothing in an uploaded file names a workspace. The manifest carries the source workspace and the importer never reads it: the request’s workspace is stamped on every write.
Permissions
content:export and content:import, both granted to contributor as well
as admin. Downloading an import template needs only content:read.