Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Import, export and backups

Every path that moves data in or out reads everything first, writes in one transaction, and keeps a copy before anything is replaced.

Import

Loci imports Anki .apkg (decks) and .colpkg (a whole collection) in any package layout, and its own full-backup .zip.

Two destinations:

  • Merge (Add to my collection) adds the package to what you have. Notes are matched by their Anki guid. A note that is new is added; a note you already have is updated or skipped according to the conflict rule, which by default is newest edit wins: the incoming note replaces yours only if its modification time is newer. The other rules are Keep mine and Use the imported version. Importing the same deck twice does not duplicate it.
  • Replace (Replace my collection) swaps the whole collection for the package. The current collection is saved as a pre-restore backup first.

All or nothing. The whole package is read and validated before anything is written. If any part cannot be read (a truncated zip, a damaged database, a media file whose checksum does not match, a package from a newer Anki than Loci understands), the import stops with an error that says why, and the collection is untouched. The write itself is one transaction: media files first, then notes, cards and review history together.

What the merge does with scheduling. Review due days are shifted by the difference between the two collections’ creation days, so every card falls due on the same calendar day. FSRS state in cards.data is kept verbatim. Review-log rows of imported cards are added. Cards in filtered decks go back to their home decks. Media keep Anki’s names; a name already taken by a different file gets Anki’s -<sha1> suffix and the imported notes are rewritten to match. With Keep scheduling off, imported cards start as new, as with Anki’s “without scheduling” export.

Undo. An import of up to 20,000 rows is one undo entry. A larger import into a non-empty collection writes a pre-restore backup instead.

Export

Export writes an Anki package of one deck (with its subdecks) or of the whole collection, with or without scheduling and with or without media. The file is the legacy .apkg layout that Anki 2.1 through 26.x, AnkiDroid and AnkiMobile import; see package layouts. Without scheduling, cards go out as new.

Download full backup writes Loci’s own .zip (below); .apkg is for Anki.

Automatic backups

A backup is collection.sqlite serialised from the open database, written under a temporary name and renamed. Backups live in backups/ inside the data directory, named by their UTC time and kind:

loci-20261011-041106-auto.sqlite
loci-20261011-093000-manual.sqlite
loci-20261011-101500-pre-restore.sqlite

When they run.

  • On leaving the app (window closed or interface hidden), if anything changed since the last backup.
  • Back up now (a manual backup).
  • Before anything destructive: restoring a backup, Reset collection, an import in Replace mode, restoring a full backup, and a large import into a non-empty collection all write a pre-restore backup first.

The engine’s autoBackup method (also part of maintenance) backs up when something changed and the last backup is at least 30 minutes old; the app does not call it by itself.

Rotation. Loci keeps every automatic backup from today, then the newest of each previous day up to 12 days, then one per week for 10 more weeks, then one per month for 9 more months: 12 daily, 10 weekly and 9 monthly. Manual and pre-restore backups are never deleted automatically.

Restore

Settings › Backups lists every backup, newest first, with its date, kind, size and card count. Restoring one first verifies it with PRAGMA quick_check; a damaged backup is refused and nothing changes. A healthy one replaces the collection, after the current collection has been saved as a pre-restore backup, so a restore can be undone. Media files are kept; a restore deletes none of them.

Restore from a file takes a full-backup .zip, and works the same way.

The integrity check

Opening runs PRAGMA integrity_check and reports problems. Settings › Check integrity (or loci integrity) goes further: it verifies the full-text index and the collection’s consistency, and repairs what is safe to repair. It rebuilds indexes and the search index, recreates the Default deck and preset and any missing parent decks, removes cards whose note or template is gone, moves cards from deleted decks to Default, pads notes with missing fields, generates cards for notes that have none, re-registers tags, and fixes the new-card position counter. It never deletes notes.

The Loci full-backup format

A full backup is an ordinary zip archive. backup::export_full_backup in crates/loci-core/src/backup/backup.rs writes these entries:

collection.sqlite      the database, serialised from the open collection (deflated)
media/<name>           every media file, one entry each, under its own name
manifest.json          what this file is (deflated)

Media files whose content is already compressed (images such as .png, .jpg, .webp; audio and video such as .mp3, .ogg, .mp4; archives, fonts and PDFs) are stored without deflate; everything else is deflated. Only files directly inside media/ are included.

manifest.json is pretty-printed JSON with eight keys:

KeyValue
formatalways "loci-backup"
version1, the version of this zip format
schemaVersionthe collection schema version (currently 1)
collectionIdthe collection’s id from col_meta
createdAtwhen the backup was made, in Unix seconds
notesnumber of notes
cardsnumber of cards
medianumber of media files

Its shape, with placeholders for the values:

{
  "format": "loci-backup",
  "version": 1,
  "schemaVersion": 1,
  "collectionId": "<collection id>",
  "createdAt": <unix seconds>,
  "notes": <count>,
  "cards": <count>,
  "media": <count>
}

Reading it back (collection/full_backup.rs). A file is treated as a Loci full backup when it is a zip that contains both manifest.json and collection.sqlite and the manifest’s format is "loci-backup"; anything else is handed to the Anki package reader. Before anything is replaced, the database image must start with the SQLite header, pass PRAGMA quick_check, and contain a schema_version in col_meta; otherwise the file is refused as corrupt. Media entry names must already be in Anki’s normalised form and contain no further /. A full backup can only replace a collection, never merge into one. On restore, media files that are missing or differ (by size and SHA-1) are written; identical ones are left alone.

The data-safety document

docs/DATA-SAFETY.md, included as it stands:

How Loci protects a collection, what to do when something goes wrong, and the tests behind each guarantee.

Where the data lives

The platform app-data directory (app-internal storage on Android) holds collection.sqlite, media/ and backups/. The CLI uses the same layout under --dir. There is no server.

The browser build keeps collection.sqlite and blobs.sqlite (media and backups as rows) in the site’s origin-private file system. It holds the Web Lock loci-rs-collection, so only one tab opens the collection, and asks the browser to keep its data (navigator.storage.persist()) on the first write. Clearing the site’s data deletes the collection: keep a full backup elsewhere.

Guarantees

Every change is one transaction

Each write goes through Db::transaction (BEGIN IMMEDIATE … COMMIT, rolled back on any error; nested calls join the outer transaction). An operation’s undo entry is written in the same transaction. The journal mode is DELETE: after a crash mid-write SQLite rolls the incomplete transaction back on the next open.

Imports read and validate the whole package before writing; media files go first, then notes, cards and review history in one transaction.

Integrity check on open

Opening runs PRAGMA integrity_check and reports problems instead of continuing silently. Check database also verifies the full-text index and repairs what is safe to repair: rebuilds indexes and the search index, recreates the Default deck and preset and missing parent decks, removes cards whose note or template is gone, moves cards from deleted decks to Default, pads notes with missing fields, generates cards for notes without any, re-registers tags and fixes the new-card position counter. It never deletes notes.

Automatic backups

  • When: on closing the window or hiding the app, if anything changed since the last backup. The engine’s autoBackup applies a 30-minute rule for callers that want periodic backups.
  • How: the database is serialised and written to a temporary file, then renamed.
  • Rotation: every automatic backup from today, then the newest of each previous day up to 12 days, one per week for 10 more weeks, one per month for 9 more months. Manual and pre-restore backups are never deleted automatically.
  • Before anything destructive (restoring a backup, Reset collection, a Replace import, restoring a full backup, an import of more than 20,000 rows into a non-empty collection): a pre-restore backup.

Undo

The last 50 operations: answers, edits, deletions, bulk actions, catch-up plans, imports (up to 20,000 rows), the sample deck, extending today’s limits, and an Optimise that adopted new parameters. Automatic optimisation is not put on the stack. Undo restores the exact rows changed.

Sync

Merging another device’s sync file (docs/SYNC.md) is one transaction, preceded by an automatic backup when it will change something not yet backed up. A merge never drops a review; only undo on the device that made it does. A merge that changed anything clears the undo stack. Restoring a backup makes the restored state the newest on every device.

Export

  • .apkg: a deck with its subdecks or the whole collection, with or without scheduling and media, in the legacy layout that Anki 2.1 through 26.x and AnkiDroid import. When the collection began with an Anki import, exports carry that collection’s crt and renumber due days to it.
  • Full backup (.zip): collection.sqlite, every media file under media/, and manifest.json. Restore with Import in Replace mode.

When something goes wrong

SituationWhat happensWhat to do
The app closed or crashed during a writeSQLite rolls back the unfinished transaction on the next open.Nothing. At most the last answer is missing.
A wrong bulk edit, catch-up plan or importIt is one undo entry.Undo. For a large import, restore its pre-restore backup.
Opening reports database problemsThe collection still opens; nothing is repaired silently.Settings › Backups › Check database. If problems remain, restore the newest backup older than the problem.
The database file is unreadableOpening fails with corrupt.Restore a backup (each is checked with PRAGMA quick_check first) or a full backup in Replace mode.
A backup is damagedRestoring it is refused; nothing changes.Pick an older one.
Media files are missingCards show without them; Check media lists missing and unused files.Re-import the original package; identical files are deduplicated.
Browser storage was clearedThe browser build starts empty.Import a full backup in Replace mode.
Loci is open in another tabThe second tab does not open the collection.Close one tab and reload.
A sync merged something unwantedA backup was written before the merge.Restore it; the next sync makes it current everywhere.
A package fails to importNothing is written; the error says why.Re-export from Anki; try .colpkg if .apkg fails.

From the command line:

loci integrity --dir <dir>          # check and repair
loci backup --dir <dir>             # manual backup into <dir>/backups
loci export out.apkg --dir <dir>

Tests

GuaranteeTest
Backup → corrupt → open reports failure → restore → equal countsbackup::backup backup_corrupt_open_restore_counts_equal, tests/integration/stats_export.rs detects_the_damage_and_restores_equal_counts
Full backup restores collection and media; merging one is refusedstats_export.rs full_backup_replaces_the_collection_and_media_refuses_to_merge
Export → import into a second collection is losslessstats_export.rs export_then_re_import_into_a_second_collection_is_lossless
All package layouts and .colpkg import to identical collectionstests/integration/import.rs
Broken packages are rejected before anything is writtenimport.rs rejects_a_broken_package_cleanly, anki::tests_fixtures
Replace keeps a pre-restore backup; imports undoimport.rs
Answers, catch-up plans and bulk actions undo exactlytests/integration/study.rs, tests/integration/backlog_browse.rs
Backups after writes, on demand and on leavetests/integration/rpc.rs backs_up_after_writes_on_demand_and_on_leave
Rotation, listing, restorebackup::rotation, backup::backup
Integrity check and repairsbackup::integrity
Sync: merge order never matters, reviews are never lost, backup before merge, restore winscrates/loci-core/src/sync/tests.rs
Browser build: storage, backups and restorecrates/loci-web/tests/smoke.mjs, apps/loci/tests/web/pwa.spec.ts