paperless-export
A thin scheduled wrapper around Paperless-ngx's
built-in document_exporter, plus the one thing it doesn't do: a materialized
_Steuer/YYYY/ tax view built from your Steuer-YYYY tags.
Paperless's exporter already produces the full no-lock-in export — every
document laid out by your storage-path template, originals and PDF/A archive
versions, and a complete manifest.json (tags, correspondents, types, custom
fields). This tool deliberately does not rebuild any of that. It:
- runs
document_exporter <target> --use-filename-format --compare-checksums --delete(each flag toggleable), streams progress with bounded diagnostics, and falls back to a flat export with a clear warning when a path exceeds the OS limit, - optionally embeds metadata into each distinct exported original/archive PDF before any derived copies are created,
- validates every manifest path inside the export root, then builds
_Steuer/<YYYY>/— one original-based symlink (or copy) per document taggedSteuer-YYYY— plus a greppable_Steuer/INDEX.csv, - publishes the derived view only after every required source and staged output
validates; a failed replacement leaves the prior complete
_Steuercurrent.
export/
Bescheid/Finanzamt/2024-05-01 Steuerbescheid.pdf # ← document_exporter
manifest.json # ← document_exporter
_Steuer/
2024/2024-05-01 Steuerbescheid.pdf → ../../Bescheid/Finanzamt/…
INDEX.csv # year,title,correspondent,created,original_path
Status
Released 1.0.0 — verified on PyPI, as a GitHub Release, and through
fileworks/tap on 2026-07-26. Development after that tag is unreleased
until the release workflow runs.
Overview
paperless-export wraps Paperless-ngx's own document_exporter and adds what
it leaves out: a scheduled, verified run, streamed progress instead of silence,
and an optional tax view (_Steuer) built from your existing tags.
It is an escape hatch, not a backup — it produces a readable copy of what Paperless holds, so that Paperless never becomes the only place your documents exist.
Install
pipx install paperless-export # + 'paperless-export[pdf]' for --embed-tags
# or
brew install fileworks/tap/paperless-export
Version 1.0.0 is published on
PyPI, as a
GitHub Release,
and through fileworks/tap. Development after that tag remains unreleased
until the normal release workflow runs.
Usage
# the nightly job (run from the directory containing your compose file):
paperless-export run --export-dir /volume1/paperless/export
# protected secret-file path only; the passphrase value never enters argv/env:
paperless-export run \
--export-dir /volume1/paperless/export \
--passphrase-file /run/secrets/paperless-export-passphrase
# rebuild only the tax view from an existing export:
paperless-export tax-view --export-dir /volume1/paperless/export
# FAT/exFAT or cloud targets that don't preserve symlinks:
paperless-export run --export-dir ./export --copy
# embed originals and archive PDFs without creating _Steuer:
paperless-export run --export-dir ./export --no-tax-view --embed-tags
Notes:
--exporter-target(default../export) is the path as the exporter process sees it inside the container;--export-diris the same directory on this host. With the standard compose setup they're the same bind mount.PAPERLESS_URL+PAPERLESS_TOKEN(env or flags) enable a preflight check so a bad token fails fast with a clear message — they're optional because the exporter itself runs inside the container and needs no API access.PAPERLESS_EXPORT_PASSPHRASE_FILEis a path-only alias for--passphrase-file.-reads the passphrase once from standard input. Protected files must be regular, not symlinks, and mode0600on POSIX.PAPERLESS_EXPORT_LOG_FILEis the environment alias for--log-file. Without it, a bounded rotatingpaperless-export.logis written beside the export directory for unattended-job diagnostics.- Passphrase transport is supported for the default
docker compose exec -T webserver document_exporteradapter. A custom exporter command is rejected when a passphrase is configured unless this project gains a separately reviewed stdin adapter for it. --embed-tagsrewrites each distinct original PDF and Paperless PDF/A archive. Non-PDF originals are skipped. Embedding happens before_Steuercopies, so copy mode receives the updated bytes. Rewrites change checksums, so Paperless can re-export those files on the next--compare-checksumsrun.
Quick start
# from the directory holding your Paperless compose file
paperless-export run --export-dir ~/paperless-export
The first run performs a full export. Later runs reuse what is already there and rebuild only what changed.
Passphrase and export security
Without a passphrase, the command warns before launching Paperless because Paperless-ngx 2.20.x may store supported account secrets in plaintext. Paperless's native passphrase protects these fields:
- mail-account
passwordandrefresh_token; - social-token
tokenandtoken_secret.
The passphrase does not encrypt the entire manifest.json, exported
documents, or other metadata. Paperless records the cryptographic parameters
in metadata.json, and a later import needs the same passphrase. Keep the
passphrase in a secret-manager-mounted file (or provide it over stdin), make
the export directory accessible only to the backup account, and protect backup
copies with storage-level encryption. Do not put the secret value in a shell
argument or environment variable.
Every original and archive path read from manifest.json is treated as
untrusted. Absolute paths, Windows drive/UNC forms, empty/malformed components,
parent traversal, and symlink escapes are rejected before _Steuer is cleared
or any PDF is opened. The same confinement is repeated immediately before
file operations. Do not modify the export tree concurrently with
post-processing.
Behavior guarantees
- Read-only against Paperless — writes only into the export directory.
- Atomic derived view —
_Steuer/is built in a confined same-filesystem sibling stage and journalled through publication. Missing sources, copy/link failures, and interruptions preserve the last complete view. - Verifiable — after a run,
_Steuer/2025/contains exactly the documents taggedSteuer-2025;INDEX.csvmatches a manifest query. - Honest failures — exporter, unavailable infrastructure, unsafe output, and incomplete PDF metadata have separate stable exit categories. A missing tax-view source is a fatal output error because publishing a partial view is forbidden.
- Never silent —
document_exporter's output is relayed live rather than buffered until the end. Final errors repeat only the last 64 KiB diagnostic tail; path-too-long detection covers the entire stream independently.
Exit codes
| Code | Meaning |
|---|---|
| 0 | complete requested output, including successful advisory fallbacks |
| 1 | unexpected wrapper failure |
| 2 | bad configuration / authentication failure |
| 3 | Paperless API or Docker/Compose/service/container unavailable |
| 4 | malformed, unsafe, missing, or fatally unwritable export output |
| 5 | exporter succeeded but requested post-processing is incomplete |
| 6 | document_exporter ran but failed (its child code remains in diagnostics) |
Scheduling on a Synology (DSM Task Scheduler)
cd /volume1/docker/paperless && \
/usr/local/bin/paperless-export run --export-dir /volume1/paperless/export
Nightly, after the Paperless backup window; the export target should live on a share covered by your backup chain.
Configuration
Configuration is by flag and environment variable; nothing is stored between runs.
| Setting | Purpose |
|---|---|
--exporter-cmd |
Exporter invocation; defaults to Docker Compose in the working directory |
--exporter-target |
Export path as seen inside the exporter container |
--passphrase-file |
File holding the export passphrase. The value never enters argv or the environment |
--copy |
Use verified copies in _Steuer instead of symlinks |
--no-tax-view |
Skip building _Steuer |
--log-file |
Bounded rotating logfile path |
--help is authoritative.
Troubleshooting
The export appears to hang. It does not — document_exporter used to buffer
its output. Progress is now streamed; a long silence means a genuinely long step.
Exit code 4, 5, or 6. Four means exported output is missing, unsafe, or
cannot be published; five means non-fatal requested post-processing (currently
PDF metadata embedding) is incomplete; six means document_exporter itself
failed. All three leave Paperless originals untouched, and tax-view publication
failures retain the previous complete _Steuer.
Symlinks fail on the target. Use --copy; the tax view is then built
from copies.
Development
uv lock --check
uv sync --locked --all-extras --dev
uv run ruff check . && uv run ruff format --check . # lint
uv run mypy # strict types
uv run pytest # tests
uv build
Conventional Commits drive releases (python-semantic-release): merge to
main → version bump + changelog + GitHub Release + PyPI publish (OIDC) +
Homebrew formula bump.
For per-clone paths, commands, or preferences, create an ignored
CLAUDE.local.md at the repository root. Do not put credentials or other
secrets in it.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file paperless_export-1.1.0.tar.gz.
File metadata
- Download URL: paperless_export-1.1.0.tar.gz
- Upload date:
- Size: 93.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d7a30f4eb6a7d4c361939df043dce6adc8f9dda7ee6a748b61d776280e81f84
|
|
| MD5 |
81790b5c53a2d5c99aba2fee6165d8b1
|
|
| BLAKE2b-256 |
e4515ee2034439605784f3a903601dd52d802d68a754c5c22078d48ac047b5f4
|
Provenance
The following attestation bundles were made for paperless_export-1.1.0.tar.gz:
Publisher:
release.yml on fileworks/paperless-export
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
paperless_export-1.1.0.tar.gz -
Subject digest:
5d7a30f4eb6a7d4c361939df043dce6adc8f9dda7ee6a748b61d776280e81f84 - Sigstore transparency entry: 2291989901
- Sigstore integration time:
-
Permalink:
fileworks/paperless-export@d60ecf317223af9c05204614bc6bf9c5a059e69e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/fileworks
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d60ecf317223af9c05204614bc6bf9c5a059e69e -
Trigger Event:
workflow_run
-
Statement type:
File details
Details for the file paperless_export-1.1.0-py3-none-any.whl.
File metadata
- Download URL: paperless_export-1.1.0-py3-none-any.whl
- Upload date:
- Size: 29.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6fe5878f1e28b223a926226b0a3171cd87ec177b8c484d0a87a33d98fdee7dc0
|
|
| MD5 |
99d5b546a6bb208b65a7127f1ed10f15
|
|
| BLAKE2b-256 |
dc371746e358bbc913a24503f74d5abc66ed51c74dfcbada408cace0f20cc811
|
Provenance
The following attestation bundles were made for paperless_export-1.1.0-py3-none-any.whl:
Publisher:
release.yml on fileworks/paperless-export
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
paperless_export-1.1.0-py3-none-any.whl -
Subject digest:
6fe5878f1e28b223a926226b0a3171cd87ec177b8c484d0a87a33d98fdee7dc0 - Sigstore transparency entry: 2291989972
- Sigstore integration time:
-
Permalink:
fileworks/paperless-export@d60ecf317223af9c05204614bc6bf9c5a059e69e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/fileworks
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d60ecf317223af9c05204614bc6bf9c5a059e69e -
Trigger Event:
workflow_run
-
Statement type: