ZKRINET Docs
Building blocks

Import/export — your data out and in, as CSV or JSON

On Zkrinet: add the block under Building blocks in the panel — one click, your own code is untouched and a snapshot is taken first. The installation steps below apply if you run PocketBase yourself, for example after an export.

The freedom principle in a plugin: a superuser can take out a whole collection as CSV (opens in Excel/Numbers/Google Sheets) or JSON — and read it back in, or read in data from a spreadsheet. The import is all or nothing: if one row is wrong, nothing is saved.

Validation and saving are PocketBase's own ($app.save in a transaction) — the same checks as when you save in the admin panel.

Tested against PocketBase 0.40.4.

Installation

Copy pb_hooks/ to your PocketBase and restart. The plugin has no collections of its own.

Export

GET /api/zk/export/{samling}?format=csv|json     (superuser)
ParameterDefault
formatcsvcsv or json
avgransarekommasemikolon gives ; — what Excel with Swedish settings expects
doldaoff1 = include fields that are marked as hidden. Password hashes and tokenKey are never included, not even then.
// From the app (superuser logged in) — e.g. a "Download" button in an admin view
const csv = await fetch(`${pb.baseURL}/api/zk/export/kunder?format=csv&avgransare=semikolon`, {
  headers: { Authorization: pb.authStore.token },
}).then((r) => r.blob())

Import

POST /api/zk/import/{samling}?lage=skapa|uppsert&prova=1     (superuser)
Content-Type: text/csv          → the body is CSV
Content-Type: application/json  → the body is [ {...}, ... ]  or  { "lage": "...", "prova": true, "poster": [ ... ] }
const fil = input.files[0]                       // a .csv from Excel
const svar = await fetch(`${pb.baseURL}/api/zk/import/kunder?lage=uppsert&prova=1`, {
  method: "POST",
  headers: { Authorization: pb.authStore.token, "Content-Type": "text/csv" },
  body: await fil.text(),
}).then((r) => r.json())
// { ok: true, lage: "uppsert", prova: true, rader: 120, skapade: 100, uppdaterade: 20, fel: [] }
// or { ok: false, ..., fel: [ { rad: 7, fel: "epost: Must be a valid email address." } ] }
lage=skapa (default)Each row becomes a new record. A row with an id that already exists is an error.
lage=uppsertRow with an id that exists → the record is updated (only the columns in the file are changed). Otherwise it is created.
prova=1Dry run: everything is validated and test-saved in the transaction — including unique keys — and then always rolled back. Saves nothing.

Security

Caps

Import body10 MiB. The server reads at most that much and responds 413 (much larger bodies may get the connection closed instead).
Rows per import10,000 (413 above that)
Records per export100,000 (413 — export in pages via PocketBase's regular API instead)
Reported row errors100, then validation is aborted (fler_fel: true)

The export and the import are built in memory. Larger moves: split the file.

Limitations — read before you promise a customer anything

Testing

./bank.py import-export

13 tests: round trip JSON and CSV (tricky values, Swedish characters, emoji, relations, multi-value, JSON, geo, dates, hidden fields) gives identical data; Python's own CSV reader reads the export correctly; semicolon and Excel values (decimal comma, ja/nej); all or nothing (both validation errors and a unique key in the database); dry run; create/upsert; unknown columns and broken CSV/JSON; guests and users rejected; system collections (name and id), logs and views rejected; hidden fields and passwords; import of users; file fields; the caps. The protections are mutant-verified: 25 mutants, 24 caught. The one that survives is our view block — PocketBase itself refuses to save in a view, so the import gets 400 anyway.