Skip to main content

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:

  1. 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,
  2. optionally embeds metadata into each distinct exported original/archive PDF before any derived copies are created,
  3. validates every manifest path inside the export root, then builds _Steuer/<YYYY>/ — one original-based symlink (or copy) per document tagged Steuer-YYYY — plus a greppable _Steuer/INDEX.csv,
  4. publishes the derived view only after every required source and staged output validates; a failed replacement leaves the prior complete _Steuer current.
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-dir is 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_FILE is a path-only alias for --passphrase-file. - reads the passphrase once from standard input. Protected files must be regular, not symlinks, and mode 0600 on POSIX.
  • PAPERLESS_EXPORT_LOG_FILE is the environment alias for --log-file. Without it, a bounded rotating paperless-export.log is written beside the export directory for unattended-job diagnostics.
  • Passphrase transport is supported for the default docker compose exec -T webserver document_exporter adapter. A custom exporter command is rejected when a passphrase is configured unless this project gains a separately reviewed stdin adapter for it.
  • --embed-tags rewrites each distinct original PDF and Paperless PDF/A archive. Non-PDF originals are skipped. Embedding happens before _Steuer copies, so copy mode receives the updated bytes. Rewrites change checksums, so Paperless can re-export those files on the next --compare-checksums run.

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 password and refresh_token;
  • social-token token and token_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 tagged Steuer-2025; INDEX.csv matches 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 silentdocument_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

paperless_export-1.1.0.tar.gz (93.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

paperless_export-1.1.0-py3-none-any.whl (29.3 kB view details)

Uploaded Python 3

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

Hashes for paperless_export-1.1.0.tar.gz
Algorithm Hash digest
SHA256 5d7a30f4eb6a7d4c361939df043dce6adc8f9dda7ee6a748b61d776280e81f84
MD5 81790b5c53a2d5c99aba2fee6165d8b1
BLAKE2b-256 e4515ee2034439605784f3a903601dd52d802d68a754c5c22078d48ac047b5f4

See more details on using hashes here.

Provenance

The following attestation bundles were made for paperless_export-1.1.0.tar.gz:

Publisher: release.yml on fileworks/paperless-export

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file paperless_export-1.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for paperless_export-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6fe5878f1e28b223a926226b0a3171cd87ec177b8c484d0a87a33d98fdee7dc0
MD5 99d5b546a6bb208b65a7127f1ed10f15
BLAKE2b-256 dc371746e358bbc913a24503f74d5abc66ed51c74dfcbada408cace0f20cc811

See more details on using hashes here.

Provenance

The following attestation bundles were made for paperless_export-1.1.0-py3-none-any.whl:

Publisher: release.yml on fileworks/paperless-export

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

2.0.1

2 files

2.0.0

2 files

1.2.2

2 files

1.2.0

2 files

This release

1.1.0 This release

2 files

1.0.0

2 files

0.1.0

2 files

0.0.3

2 files

0.0.2

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page