Skip to main content

paperless-export

Overview

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.

This is a readable escape copy, not a replacement for tested backups of Paperless and its database.

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

The latest verified public release is 2.0.0 across GitHub Releases, PyPI, and the Fileworks Homebrew tap.

Install

pipx install paperless-export          # + 'paperless-export[pdf]' for --embed-tags
# or
brew install fileworks/tap/paperless-export

Released 2.0.0 on PyPI, GitHub, and fileworks/tap (verified 2026-08-12).

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.

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.
  • PAPERLESS_EXPORT_TIMEOUT_SECONDS is the environment alias for --exporter-timeout. The reviewed default is six hours; expiry terminates the child cleanly, then force-kills only if it will not exit.
  • 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.

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

immich-export, paperless-export and unpacksort share one exit-code vocabulary, so a script can branch on the code without knowing which tool it ran. The class of outcome is the same everywhere; the specific condition is this tool's, and the table below is what it means here.

Code Name Meaning
0 SUCCESS everything asked for was done
1 PARTIAL the exporter succeeded but requested post-processing is incomplete
2 USAGE bad flags, a path that is missing or malformed, or credentials the server rejected — nothing was attempted
3 CONFLICT the Paperless API, or its Docker/Compose service or container, is unavailable
4 FATAL unexpected failure, or a write that failed after work had begun (re-run with --verbose)
130 INTERRUPTED cancelled by the operator

The line between 2 and 4 is was anything attempted. A --export-dir that is missing when you type the command is 2 in every tool: nothing ran, and nothing was written. A write that fails part-way through leaves the export in a state nobody can characterise, and that is 4.

document_exporter's own child code stays in the diagnostics line.

Scheduling on a Synology (DSM Task Scheduler)

Use the checked-in POSIX/BusyBox-compatible wrapper so Task Scheduler does not depend on an interactive shell profile:

PAPERLESS_COMPOSE_DIR=/volume1/docker/paperless \
PAPERLESS_EXPORT_DIR=/volume1/paperless/export \
/bin/sh /volume1/docker/paperless-export/scripts/synology-task.sh

Nightly, after the Paperless backup window; the export target should live on a share covered by your backup chain. Run the task as a dedicated account that can read the Compose project and write only the export directory. The wrapper sets umask 077, validates absolute/readable locations before launch, and is syntax- checked with a strict POSIX shell in CI.

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
--exporter-timeout Stop a stuck child after 21600 seconds by default

--help is authoritative.

Troubleshooting

The export appears to hang. Child output is streamed and a five-second heartbeat reaches both the terminal and rotating logfile during silent work. The configured exporter timeout stops a genuinely stuck child.

Exit code 2 for a directory you expected to work. tax-view rebuilds the views of an export it did not produce, so --export-dir has to exist already; run paperless-export run first. run will create the directory itself, but only its last segment, and only when the parent is already there — so a mistyped path fails loudly instead of quietly becoming a new empty tree.

Exit code 1 or 4. One means document_exporter succeeded but requested post-processing (PDF metadata embedding, the _Steuer view) is incomplete; the report names each part that is missing. Four means an unexpected failure, or output that could not be written once the run was under way — including a manifest path pointing outside the export root. Both leave Paperless originals untouched, and a failed tax-view publication retains the previous complete _Steuer.

Symlinks fail on the target. Use --copy; the tax view is then built from copies.

Development

uv sync --locked --all-extras --all-groups
uv run ruff check . && uv run ruff format --check .   # lint
uv run mypy                                           # strict types
uv run pytest                                         # tests
uv build                                              # sdist + wheel

Pull requests run linting, strict types, tests, and builds on Python 3.12, 3.13, and 3.14, plus Linux/macOS/Windows portability, the Synology contract, documentation links, dependency auditing, and an installed-wheel smoke test. Renovate batches routine dependency changes into one Monday PR and automerge is enabled; a per-repository concurrency limit of one prevents stacked dependency branches or PRs.

A separate Sunday workflow indexes production-format manifests at 50,000, 100,000, and 500,000 entries, keeping schedule-only work out of pull-request and main CI checks.

Conventional Commits drive python-semantic-release. After CI passes on a main push, the release workflow pins the tested SHA, verifies the staged source, lock, tag, distributions, and installed CLI, then atomically pushes the release commit and tag. Publication proceeds through the protected github-release, pypi, and homebrew environments in that order; PyPI uses trusted publishing and GitHub Releases carry the verified wheel and sdist.

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.

Security

Report vulnerabilities privately as described in SECURITY.md. Registered secrets — the Paperless token and the passphrase — are redacted from the logfile and from captured exporter output, including values split across chunk boundaries. A passphrase is accepted only from a protected file or standard input, never from argv or the environment.

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-2.0.1.tar.gz (155.1 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-2.0.1-py3-none-any.whl (36.3 kB view details)

Uploaded Python 3

File details

Details for the file paperless_export-2.0.1.tar.gz.

File metadata

  • Download URL: paperless_export-2.0.1.tar.gz
  • Upload date:
  • Size: 155.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for paperless_export-2.0.1.tar.gz
Algorithm Hash digest
SHA256 7a325df7edc38f61e3577d5e7093d4f615d36601469837ef28fd574cc4e97d18
MD5 4b801d757d2b6eaae53bde45de20c952
BLAKE2b-256 6c77798f2f1e4e9a4576e5a8bb3d41541f5de1d842d114063930a3481755d769

See more details on using hashes here.

Provenance

The following attestation bundles were made for paperless_export-2.0.1.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-2.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for paperless_export-2.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 262fffae1c88476ed925b745af4ac6d5eb54e1fbf0da2d426bd56872a40de5e2
MD5 dc962f84b96d8e7368c5120d3cc9fe4e
BLAKE2b-256 ff9ce24ab38584a9c332bca922b950d85d4efd12593d67aad4ccbbe8f8b24572

See more details on using hashes here.

Provenance

The following attestation bundles were made for paperless_export-2.0.1-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

This release

2.0.1 This release

2 files

2.0.0

2 files

1.2.2

2 files

1.2.0

2 files

1.1.0

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