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/exportproduced bydocument_exporter(or an equivalent that emits the samemanifest.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 shipsdocument_exporter, a management command that
writes the exact tree suchi’s importer reads.
For a docker-compose deployment:
-p— folder-prefixed layout. Producesoriginals/,archive/,thumbnails/,json/under the export root instead of a flat dump.-sm— split manifest. Writes one JSON sidecar per document underjson/instead of one hugemanifest.json. Suchi reads either shape, but the split form is faster on large corpora and lets the export resume incrementally.
--from at the export path
directly.
The resulting bundle looks like:
- 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
Failedwith reason “paperless version drift” so you can decide what to do. - Duration.
document_exporterreads 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.
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: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:suchi-data volume at
/data/import-report.md. Read it without restarting the service:
- Loads every manifest object (top-level
manifest.jsonplus any per-document sidecar files under<root>/or<root>/documents/). - 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.
- For each document, streams the original (and optional archive)
file into the CAS by SHA-256, then inserts one
documentsrow plus tag junctions and custom-field values in a single transaction.legacy_idis preserved for round-tripping and dedup.
Dedup
Documents are keyed within the selected system by ingested-byte SHA-256/owner andlegacy_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:- --flat
- --map-jd
- --auto-jd
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 asFAILED 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 (sourceworkflowtrigger.type):
Action type mapping (source
workflowaction.type):
Trigger filter mapping:
Saved views
Paperless saved views migrate into suchi’ssaved_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:
TABLE → table, SMALL_CARDS and LARGE_CARDS →
card. Icons are dropped (suchi doesn’t render view icons).
JD categorization
Choose at most one category strategy:--flatsends every imported document to the inbox.--map-jd <file>applies the first matching HuML or TOML rule.--auto-jduses the bundled deterministic keyword mapping.
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.
Verify (dry-diff)
The shadow-window primitive: diff a nightly export against the live suchi DB without writing.- 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_idthat isn’t in this bundle. Suggests a source-side deletion since the last export.
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.