A file organizer and Paperless-ngx ingestion tool. Watches a directory for new files, sorts them into structured subdirectories by type and date, and optionally ingests them into Paperless-ngx via local directory or API.
Single binary. Cross-platform (Linux and macOS). No race conditions. Batched notifications.
~/Documents/
invoice.pdf <-- you drop a file here
receipt.jpg
|
v paperflow sorts by type + file date
~/Documents/
pdf/2026/04/invoice.pdf
images/2026/04/receipt.jpg
|
v paperflow ingests to Paperless (if enabled)
~/paperless-ingest/invoice.pdf (directory mode)
-- or --
POST /api/documents/post_document/ (API mode)
- File watching -- monitors a directory for new or moved files using OS-native events (inotify on Linux, FSEvents on macOS)
- Configurable type-based sorting -- files are moved into subdirectories based on extension, fully customizable via config
- Date-based structure -- sorted files are placed in
<type>/<year>/<month>/based on the file's modification time - Paperless ingestion -- ingestible file types are forwarded to Paperless-ngx via one of:
directory-- copies to a local ingest directory that Paperless watchesapi-- uploads directly to the Paperless-ngx REST APInone-- sorting only, no ingestion
- Collision handling -- if a file with the same name already exists at the destination, a timestamp suffix is appended (e.g.
invoice_1775660096.pdf) - Batched notifications -- multiple files processed in quick succession produce a single summary notification instead of one per file
- Startup notification -- confirms the watch directory and ingest mode on startup
- API auth check -- verifies Paperless API connectivity and token validity before starting the watcher (fails fast on bad credentials)
- Event deduplication -- suppresses duplicate fsnotify events for the same file within 500ms (common on macOS)
- Exclude patterns -- glob-based patterns to ignore files (e.g.
*.tmp,~$*) - Interactive setup --
paperflow initwalks you through configuration - Service installer --
paperflow service installsets up systemd (Linux) or launchd (macOS) automatically - XDG-compliant -- config stored in
~/.config/paperflow/
brew install alcxyz/tap/paperflowyay -S paperflow-bin# flake.nix
{
inputs.paperflow.url = "github:alcxyz/paperflow";
# Use as: inputs.paperflow.packages.${system}.default
}go install github.com/alcxyz/paperflow@latestgit clone https://github.com/alcxyz/paperflow.git
cd paperflow
go build -o paperflow ./cmd/paperflowPre-built binaries for Linux and macOS (amd64/arm64) are available on the releases page.
# Interactive setup -- creates config and stores secrets
paperflow init
# Start watching
paperflow watchpaperflow uses XDG base directories. Config lives at ~/.config/paperflow/config.toml (or $XDG_CONFIG_HOME/paperflow/config.toml).
Run paperflow init for an interactive setup wizard, or create the config manually:
# ~/.config/paperflow/config.toml
# Directory to watch for new files
watch_dir = "~/Documents"
# Ingestion method: "directory", "api", or "none"
ingest = "directory"
# Local ingest directory (when ingest = "directory")
ingest_dir = "~/paperless-ingest"
# Archive ingested files to prevent re-ingestion on Paperless restart (optional)
# ingest_archive_dir = "~/paperflow-archive"
# ingest_archive_after = "5m"
# Paperless API settings (when ingest = "api")
# paperless_url = "https://paperless.example.com"
# Token is stored separately in ~/.config/paperflow/token
[notifications]
# Enable/disable desktop notifications
enabled = true
# Duration to wait for more files before sending a batched notification
batch_window = "3s"
# App name shown in notifications
app_name = "Paperflow"
[buckets]
# Map bucket names to file extensions
# Files matching these extensions are sorted into <bucket>/<year>/<month>/
pdf = ["pdf"]
images = ["jpg", "jpeg", "png", "gif", "webp", "tiff", "tif"]
docx = ["docx", "doc", "odt", "rtf"]
xlsx = ["xlsx", "xls", "ods"]
# Files not matching any bucket go to "misc/"
[ingest_types]
# Which file types are eligible for Paperless ingestion
# Must be a subset of extensions defined in [buckets]
# Files in misc/ are never ingested regardless of this setting
types = ["pdf", "jpg", "jpeg", "png", "gif", "webp", "tiff", "tif", "docx", "odt", "xlsx"]
[exclude]
# Glob patterns for files to ignore
patterns = [
"*.tmp",
"*.part",
"~$*",
".~lock.*",
]The Paperless API token is stored separately at ~/.config/paperflow/token with 0600 permissions. paperflow init handles this automatically when you choose API ingestion.
paperflow warns on startup if the token file has overly permissive permissions.
Flags override config values for a single run:
| Flag | Description |
|---|---|
--watch |
Override watch directory |
--ingest |
Override ingestion method |
--ingest-dir |
Override ingest directory |
--ingest-archive-dir |
Archive directory for ingested files |
--ingest-archive-after |
Delay before archiving (default: 5m) |
--paperless-url |
Paperless-ngx base URL (for API ingestion) |
--paperless-token-file |
Path to file containing Paperless API token |
--config |
Path to config file (default: $XDG_CONFIG_HOME/paperflow/config.toml) |
--no-notify |
Disable notifications for this run |
--dry-run |
Log what would happen without moving or ingesting files |
Environment variables with the PAPERFLOW_ prefix override config file values (but are overridden by CLI flags):
| Variable | Description |
|---|---|
PAPERFLOW_WATCH_DIR |
Override watch directory |
PAPERFLOW_INGEST |
Override ingestion method |
PAPERFLOW_INGEST_DIR |
Override ingest directory |
PAPERFLOW_PAPERLESS_URL |
Paperless-ngx base URL |
PAPERFLOW_INGEST_ARCHIVE_DIR |
Archive directory for ingested files |
PAPERFLOW_INGEST_ARCHIVE_AFTER |
Delay before archiving (e.g. 5m) |
PAPERFLOW_NO_NOTIFY |
Set to 1 or true to disable notifications |
Config resolution order: defaults -> config file -> environment variables -> CLI flags.
Interactive setup wizard:
- Asks for the directory to watch
- Asks for the ingestion method (directory, api, or none)
- If directory: asks for the ingest directory path
- If api: asks for Paperless URL and API token
- Writes config to
~/.config/paperflow/config.toml - Stores token (if applicable) at
~/.config/paperflow/tokenwith0600permissions
Re-running paperflow init offers to update the existing config.
Starts watching the configured directory. This is the main runtime command. Runs in the foreground, intended to be managed by a service manager (systemd, launchd) or run in a terminal.
On startup, paperflow sends a desktop notification confirming the watch directory and ingest mode. When using API ingestion, it verifies the Paperless API connection and token before starting the watcher.
Manages the system service:
install-- generates and installs a systemd user service (Linux) or launchd agent (macOS), then enables and starts ituninstall-- stops and removes the servicestatus-- shows current service status
Checks the config file for errors and verifies that:
- Watch directory exists
- Ingest directory exists (if using directory ingestion)
- Paperless URL is reachable (if using API ingestion)
- Token file exists and has correct permissions
When using directory-mode ingestion, Paperless-ngx may re-ingest files from its consume directory after a restart. To prevent this, paperflow can automatically move files from the consume directory to a separate archive after a configurable delay:
ingest_archive_dir = "~/paperflow-archive"
ingest_archive_after = "5m"After copying a file to the consume directory, paperflow waits for the configured delay (giving Paperless time to pick it up), then moves the file to the archive directory with a timestamp prefix (e.g. 20260421-164532_invoice.pdf). If Paperless already consumed and deleted the file, the archiver silently skips it.
This is disabled by default. The archive also serves as an audit trail of what was sent to Paperless — files can be manually re-ingested from the archive if needed.
This does not apply to API mode, which uploads directly and has no residual files.
By default, the following file types are forwarded to Paperless when ingestion is enabled:
pdf, jpg, jpeg, png, gif, webp, tiff, tif, docx, odt, xlsx
This is configurable via [ingest_types] in the config. Files sorted into misc/ are never ingested regardless of configuration.
paperflow sends desktop notifications via notify-send on Linux and osascript on macOS.
When multiple files arrive within the batch window (default 3 seconds), a single summary notification is sent:
- 1 file:
Sorted: invoice.pdf -> pdf/2026/04/ - 5 files:
Sorted 5 files: invoice.pdf -> pdf/2026/04/, receipt.jpg -> images/2026/04/, ...
Ingestion notifications follow the same batching pattern.
# Install and start the service for your platform
paperflow service install
# Check status
paperflow service status
# Remove the service
paperflow service uninstallThis generates and installs a systemd user service (Linux) or launchd agent (macOS). Any flags passed alongside service install are baked into the service definition (e.g. paperflow --config /path/to/config.toml service install).
# ~/.config/systemd/user/paperflow.service
[Unit]
Description=Paperflow document organizer
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/paperflow watch
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=default.targetsystemctl --user enable --now paperflow<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.alcxyz.paperflow</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/paperflow</string>
<string>watch</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>systemd.user.services.paperflow = {
Unit = {
Description = "Paperflow document organizer";
After = [ "network-online.target" ];
Wants = [ "network-online.target" ];
};
Service = {
Type = "simple";
ExecStart = "${pkgs.paperflow}/bin/paperflow watch";
Restart = "on-failure";
RestartSec = "5s";
};
Install.WantedBy = [ "default.target" ];
};paperflow/
cmd/paperflow/
main.go # Entry point, CLI parsing, flags
init_cmd.go # Interactive setup wizard
validate_cmd.go # Config validation command
service_cmd.go # Service install/uninstall/status
internal/
bucket/
bucket.go # File type -> bucket mapping
config/
config.go # Config loading, XDG paths, defaults
ingest/
api.go # Paperless API ingestion + auth check
directory.go # Directory-based ingestion
collision.go # Filename collision handling
notify/
notify.go # Batched notification logic
notify_linux.go # Linux notification (notify-send)
notify_darwin.go # macOS notification (osascript)
organizer/
organizer.go # File sorting logic
watcher/
watcher.go # File system watching (fsnotify)
docs/adr/ # Architecture decision records
VERSION # Single source of truth for release version
flake.nix
default.nix
.goreleaser.yml
CONTRIBUTING.md
README.md
LICENSE
MIT
Support
- BTC:
bc1pzdt3rjhnme90ev577n0cnxvlwvclf4ys84t2kfeu9rd3rqpaaafsgmxrfa - ETH / ERC-20:
0x2122c7817381B74762318b506c19600fF8B8372c