Migration
Everything a project holds, in one file you keep. Move it into another Koolbase, keep it as your own backup, or take it somewhere else entirely. Leaving is meant to be easy — that is what makes arriving safe.
What an export contains
One .tar.gz, from Backups → Your own copy in the dashboard, or from the API:
GET /v1/projects/{project_id}/export
Authorization: Bearer <dashboard session>snapshot.json schema: collections, rules, append-only, environments, buckets
references.json declared references
constraints.json unique constraints
collections/grants.jsonl one record per line
collections/disbursements.jsonl
storage.json every object's bucket, path, size and type — a listing
manifest.json format version, source project, per-collection countsEach record line carries the record's id, created_at, updated_at and revision beside its data, so an import can put it back exactly. Collections stream a thousand records at a time, so a million-row collection exports without loading it all at once.
{"id":"8a1c…","data":{"grant_id":"3f2b…","amount":50},"created_at":"2026-09-01T10:00:00Z","updated_at":"2026-09-01T10:00:00Z","revision":1}What is never exported
The manifest is written last. A download that was cut off has no manifest, and an import refuses it rather than loading half a project.
Importing
POST /v1/projects/{project_id}/import
Authorization: Bearer <dashboard session>
Content-Type: application/gzip
<the archive as the body>Two rules make an import safe, and both are deliberate:
- Only into an empty project. If the target already has collections, the import is refused. Merging into existing data means deciding, record by record, which side wins — that is what seed files are for. An import is one predictable operation.
- All or nothing. Every collection, record and declaration lands in one transaction. A record that does not parse, a reference to a record the archive does not contain, a duplicate under a unique constraint — any of them, and nothing is written.
References and unique constraints are declared after the records, so they validate the real data rather than an empty collection. A reference in the archive that points at a record the archive lacks fails the whole import — the same check that protects a live project.
Ids
Records keep their ids, so every reference between them survives. The one exception: an export from a project on this same Koolbase, imported back into it — a clone. Those ids are already in use, so the colliding records get new ones, and every declared reference that pointed at them is rewritten to match. The result reports how many were remapped. A migration from another Koolbase remaps none.
What an import does not restore
Environment flags, remote config and version policies; functions, crons and triggers; bucket definitions. Those already have a restore path — the snapshot inside the archive. After the import, apply the snapshot to bring them across. Storage objects are listed in storage.json; move the bytes between buckets with any S3 tool.
created_by is cleared on import: the source project's users do not exist in the target. Timestamps and revisions are kept.
Leaving Koolbase
The archive is plain: gzip, tar, JSON and JSON Lines. Nothing in it needs Koolbase to read. Each .jsonl file loads into any database that takes JSON — Postgres with a jsonb column, MongoDB, SQLite, a spreadsheet — and snapshot.json tells you the rules each collection had, so you can rebuild them wherever you go.
tar xzf koolbase-export.tar.gz
psql -c "CREATE TABLE grants (id uuid PRIMARY KEY, data jsonb, created_at timestamptz)"
jq -c '[.id, .data, .created_at]' collections/grants.jsonl \
| psql -c "COPY grants FROM STDIN" # or your loader of choiceArriving from somewhere else
The same archive is how data comes in. Write one — the format above is the whole specification — and import it. Anything that can produce a tar of JSON Lines can produce a Koolbase import: a script over a Firebase export, a pg_dump, a CSV.
Keep your existing ids as the record ids where you can — they must be UUIDs — and put the references between records in references.json so the database enforces them from the first day.