Skip to main content
suchi import --from ./export-bundle consumes a Paperless-ngx export in its JSON-manifest + originals/ + archive/ layout. The import is idempotent and safe to re-run against a nightly export during a shadow window. Select the existing destination with --system S02 for both import and --verify; omission uses original system 1. Bundle import does not introduce systems: use taxonomy import first. Category/name resolution, metadata/workflow upserts, legacy resume, ASN and duplicate lookup all stay in the target system. The owner must be able to enter it. This Paperless/Django importer is not a native Suchi takeout reader or a whole-instance backup restore. Imported documents retain a Paperless-ngx import source with their original filename. This provenance is independent from the preserved legacy id and does not create a version or document link.

Prerequisites

  • A completed export bundle at /path/to/export produced by document_exporter (or an equivalent that emits the same manifest.json + originals/ + archive/ + json/ tree).
  • A running suchi instance with at least one user (usually the admin from /setup).
  • Enough free disk for the CAS to absorb every unique document blob (originals + optional archive PDFs).

Getting the dump out of paperless-ngx

Paperless-ngx ships document_exporter, a management command that writes the exact tree suchi’s importer reads. For a docker-compose deployment:
For a bare or systemd install:
Both flags matter:
  • -p — folder-prefixed layout. Produces originals/, archive/, thumbnails/, json/ under the export root instead of a flat dump.
  • -sm — split manifest. Writes one JSON sidecar per document under json/ instead of one huge manifest.json. Suchi reads either shape, but the split form is faster on large corpora and lets the export resume incrementally.
If paperless is in a container, copy the export off so the suchi host can see it:
Bare-metal installs skip the copy — point --from at the export path directly. The resulting bundle looks like:
Two caveats worth knowing before you run the exporter:
  • Version. Suchi’s importer targets paperless-ngx v3.0.5 (see Migration target version). Newer minor versions are not a supported compatibility surface — the report will flag unknown shapes as Failed with reason “paperless version drift” so you can decide what to do.
  • Duration. document_exporter reads every original + every archive PDF end-to-end. On corpora over a few thousand docs, run it during a maintenance window; the exporter is safe to interrupt and re-run.
Once the bundle is on disk, review it before starting the import.

Review, then import

Run a dry review first. It parses the manifest, verifies that each required original exists, and reports what would be imported without writing to Suchi:
Then run the confirmed import:
The running web app sees imported rows immediately; neither command requires a Suchi restart.

Docker Compose

Use a one-shot container so a large export never passes through the browser or reverse proxy. It shares Suchi’s data volume and mounts the export read-only; the running web container stays online:
The report remains in the suchi-data volume at /data/import-report.md. Read it without restarting the service:
The importer:
  1. Loads every manifest object (top-level manifest.json plus any per-document sidecar files under <root>/ or <root>/documents/).
  2. Upserts tags, correspondents, document types, storage paths, and custom-field definitions verbatim within the selected system. Names are local identities — re-running there against a modified export updates that system’s row only.
  3. For each document, streams the original (and optional archive) file into the CAS by SHA-256, then inserts one documents row plus tag junctions and custom-field values in a single transaction. legacy_id is preserved for round-tripping and dedup.

Dedup

Documents are keyed within the selected system by ingested-byte SHA-256/owner and legacy_id. Re-running the same bundle against that system skips already imported documents and reports them as Skipped (dupes).

Category resolution

By default, every imported document lands in the JD inbox — visible, bulk-reassignable, never lost. Three flags override:
Force every doc to the inbox. Never consult a rule. Use this if you want your existing tag/correspondent process untouched — the inbox is your one bucket, JD affordances stay hidden in the UI.
The three flags are mutually exclusive — pick one strategy.

Migration target version

The importer targets paperless-ngx v3.0.5 and is not intended to track that project’s release train. Migration is a one-shot event — picking a stable target once, freezing there, and treating anything newer as “please tell us if you need it” is deliberately simpler than maintaining a compatibility surface across paperless minor versions. Unknown workflow trigger types, action types, or saved-view rule types land as FAILED entries in the migration report with reason “paperless version drift”; the report’s follow-ups section lists each kind so a future maintainer can decide whether the delta is worth a bump.

Workflows → automations

Paperless workflows migrate into suchi’s automations engine. Triggers, actions, and filter fields all map through a fixed table. Trigger type mapping (source workflowtrigger.type): Action type mapping (source workflowaction.type): Trigger filter mapping:

Saved views

Paperless saved views migrate into suchi’s saved_views — with a lossy translation from paperless’s 50-way filter_rules.rule_type enum onto suchi’s allow-listed filter_json keys (q, tags__id__in, correspondents__id__in, document_type__id, jd_category_id, sensitivity, ordering). Suchi still reads these flat imported filters; the importer does not yet rewrite source rules into rich-query equivalents. A view is Full when every rule maps 1:1; Partial when at least one rule degrades (e.g., storage-path rule → text search); Failed when zero rules map — the view row is skipped and reported. Rule-type mapping (source rule_type int → target filter_json): Display mode: TABLEtable, SMALL_CARDS and LARGE_CARDScard. Icons are dropped (suchi doesn’t render view icons).

JD categorization

Choose at most one category strategy:
  • --flat sends every imported document to the inbox.
  • --map-jd <file> applies the first matching HuML or TOML rule.
  • --auto-jd uses the bundled deterministic keyword mapping.
An unmatched rule or a rule targeting a category absent from the active tree falls back to the inbox. Invalid targets are also listed in the import warnings. The importer does not perform fuzzy matching or create approval proposals.

Migration report

Every non-dry-run import writes a markdown report to --report ./import-report.md (default). The report has these sections:
  • Header — timestamp, source bundle root, target datadir, duration, active JD strategy.
  • Summary — rows per model class (Documents / Tags / Correspondents / Document types / Storage paths / Custom fields / Notes / Workflows / Saved views), columns Full / Partial / Failed.
  • Partial migrations — one row per Partial with source, target, reason.
  • Failed migrations — one row per Failed with source, reason.
  • Follow-ups suggested — aggregated feature gaps by blocker count, sorted descending, so unsupported source behavior is explicit.
The report is the migration’s system of record — silent lossy migrations feel like betrayal; every drop appears here with a reason.

Verify (dry-diff)

The shadow-window primitive: diff a nightly export against the live suchi DB without writing.
Report partitions every document in the bundle into:
  • New — would be imported (no matching legacy_id).
  • Match — present locally + compared fields all agree.
  • Differ — present locally, but at least one compared field diverges. The report lists the field names.
  • Orphan — present in suchi with a legacy_id that isn’t in this bundle. Suggests a source-side deletion since the last export.
Compared fields: title, original_size. Deliberately minimal — OCR text drifts trivially across source versions and would false-positive.

Shadow migration recipe

The recommended cutover for cautious operators:
1

Stand suchi up alongside the source system

Point it at the same backup pipeline.
2

Every night, verify

Run your DMS’s exporter on the source side and suchi import --verify on the suchi side.
3

Watch the diffs settle

Any long-standing Differ or Orphan entries are the real migration surface to investigate.
4

Cut over

When diffs are boring, do a full --auto-jd (or --map-jd) import.
5

Repoint clients

Update mobile apps and staging-dir producers to point at suchi.
6

Cool off

Keep the source running for a two-week cool-off, then decommission.