WARNING

🚧 This page is under construction 🚧

I have been using Paperless-ngx for a few years now and it is by far my most used self-hosted service that I now use for managing all of my digital documents. It has helped me almost completely avoid the need to keep paper and I am able me to quickly access my documents from anywhere, even remotely via my Tailscale VPN.

My Current Setup

Currently, I use the Quickscan mobile app on iOS for 99% of my scanning purposes. Mobile phone cameras have become so good in recent years that the quality of the scan is more than enough for my needs. Because we tend to always have our phones with us wherever we go these days, I like the convenience of being able to scan a receipt wherever I am and it helps prevent documents from piling up at my desk waiting to be run through a scanner.

Installing Paperless-ngx using Docker in Unraid

Coming soon...

Dependencies

Advanced Configuration Settings

Coming soon...

Email Consumption

Coming soon...

Workflows

Coming soon...

Custom Fields

Coming soon... Custom Fields

Storage Paths

More recently, I began experimenting with using Filename Templates following the release of Paperless-ngx v2.13.0. This update introduced a new feature (#7836) that allows you to use Jinja templates to apply complex logic in order to create custom filenames and storage paths based on the document variables (called Placeholders). However, you can also create your own Paperless-ngx_Custom-Fields

The possibilities here are really endless, but one of the templates that I have found to be actually useful is related to how I name and organize my receipts that I scan by taking advantage of the ability to use custom field values. When I scan in my receipts, I always include the correspondent, and 2 custom fields I created: 1 named credit_card that is the last 4 digits of the card the transaction was charged too and another named amount where I enter the total dollar amount of the transaction. By doing that, I am able to use those values to have Paperless automatically rename and sort my files into folders with the template below.

receipts/
  {{ created_year }}/                                                                # Year of creation
  {{ custom_fields | get_cf_value("card_number") | default("unknown_card") }}/       # Pulls the value in the `credit card` custom field or defaults to "unknown_card"
  {{ created | replace("-", "") }}_                                                  # Removes hyphens from created date (I did this simply to shorten the filename a bit)
  {{ correspondent | slugify | default("unknown_correspondent") }}_                   # Name of the Correspondent, slugified or defaults to "unknown_correspondent"
  {{ custom_fields | get_cf_value("amount") | replace("USD", "") | default("0") }}   # Pulls the value in the `amount` custom field (with "USD" removed) or defaults to "0"

Let’s break down what this template is doing line by line:

receipts/
  • This line ensures that all of the documents I apply this template to are filed under the main folder named β€œreceipts”.
{{ created_year }}/
  • This line organizes receipts by created year, which is helpful for tax purposes.
{{ custom_fields|get_cf_value("card_number") | default("unknown_card") }}/
  • This line pulls the value from my custom field named card_number and if empty it will default to unknown_card
{{ created | replace("-", "") }}_
  • This line simply removes the hyphens from the full created date that I have formatted as YYYY-MM-DD by default. The only reason I chose to do this is because it slightly shortens the filename while still retaining the usefulness of being able to easily sort chronologically.
{{ correspondent | slugify | default("unknown_correspondent") }}_ 
  • This line pulls the value in the correspondent field, and if empty if will default to nknown_correspondent. I chose to slugify the name of the correspondent to avoid having spaces in filenames because I do include spaces in the correspondent names I have created, but this isn’t strictly necessary.
{{ custom_fields | get_cf_value("amount") | replace("USD", "") | default("0") }}
  • This line pulls the value from my custom field names amount and removes the 3 letter ISO currency code because all of my receipts are in USD. If this field is empty it will default to β€œ0”.

The nice thing about this is that it makes it very easy to locate and backup my original files with the added benefit that they are automatically sorted into an organized folder structure by year and card number. Usually I just access my files from within Paperless-ngx itself whenever I need something, but I also keep multiple backups or my original files and have them accessible on my local network or remotely via VPN. This gives me extra peace of mind that if my server goes down for any reason, I can always access one of my backups and it will have a very easy to navigate folder structure should I ever need to access my files that way.

Here is an example of what that looks like (shout out to this cool little website that helped me create this folder tree https://tree.nathanfriend.com/)

.
└── receipts/
    β”œβ”€β”€ card1/
    β”‚   β”œβ”€β”€ 20250504_home-depot_117.07.pdf
    β”‚   β”œβ”€β”€ 20250329_napa_28.09.pdf
    β”‚   └── etc...
    β”œβ”€β”€ card2/
    β”‚   β”œβ”€β”€ 20250502_costco_134.24.pdf
    β”‚   β”œβ”€β”€ 20250428_aldi_72.34.pdf
    β”‚   └── etc...
    └── hsa_card/
        β”œβ”€β”€ 20250312_providername_25.00.pdf
        β”œβ”€β”€ 20250313_walgreens_32.34.pdf
        └── etc...

This folder structure makes it very easy to reconcile receipts with bank statements because you can simply navigate to the folder for whatever card was charged and quickly scroll to find the date it occurred on. I felt that it was unnecessary to break it down further by month or correspondent because then it would just over-complicate things and create too many layers that would only make it slower to find what you are looking for, if anything. Worst case I have a single day with multiple receipts, but the filename itself contains enough information to know what it is without even opening it, so I feel that this is a good compromise between accessibility and simplicity.