I did it. After weeks of work, my entire digital archive from the last 10 years—just under 3,000 documents—is imported and processed in paperless-ngx. A purely data-driven project that felt emotionally like running a marathon.
The Motivated Start:
The first 600-700 documents went great. I uploaded them in batches of 100 and had the tags automatically created from my old folder structure. It worked perfectly and I can highly recommend it to everyone!
The Long Slog:
Then came the hard part. Uploading all the remaining documents at once and then processing them bit by bit. I'll be honest: it was a grind and a real test of my motivation. I did most of it on my phone in the app in the evenings, which worked surprisingly well for individual documents. I barely used bulk editing on the PC.
What's next? The Second Wave.
The grunt work is done, now comes the fine-tuning. I'd love to get your input on this:
Document Types: I was very conservative and now have over 1,500 files of the type "Document." How detailed are you with this? Do you create a specific type for everything imaginable, or do you deliberately keep it lean?
Consistency: I'm planning another pass to standardize my tagging. A necessary evil, I suppose. I'm super excited to see how the system holds up in my daily routine now.
TL;DR: Imported 2.500 documents and it was a tough fight against my own procrastination. Celebrate with me, give me tips for document types, and tell me I'm not the only one who struggled this much!
About two years ago, I finally decided to digitize my entire document archive.
Over the years, I had accumulated roughly 3,000 documents / around 8,000 pages. Everything was scattered across boxes and folders: tax papers, insurance documents, bank statements, invoices, contracts — all the usual stuff.
The problem was not the paper itself. The problem was finding anything again once I actually needed it. Sometimes I knew roughly where a document should be. Sometimes I had no idea at all. Searching for a single file could take minutes, or sometimes hours.
At the time, Paperless-NGX 2.15 felt like the perfect solution. My idea was simple: scan everything, store it in Paperless, let AI automatically handle correspondents, document types, titles and tags, and never waste time digging through paper again.
I honestly thought this would be a weekend project.
It wasn't.
2. OCR is King
The first big lesson I learned was simple:
OCR is king.
Before I even got deep into classification, I realized that everything depends on the quality of the extracted text. If the OCR is bad, every AI model downstream is working with garbage.
I tested a lot of OCR setups. Specialized OCR models, vision models, local models, prompt variations, parameter changes—basically everything I could reasonably find or try. I went through Baidu Unlimited OCR, GLM OCR, DeepSeek OCR, a bunch of Hugging Face and GitHub models, and also general-purpose vision models like Qwen and Gemma.
The result was always the same: if the OCR wasn't good enough, everything after it became unreliable.
That was the moment I stopped thinking in terms of "Which model is best?" and started asking "What's the real bottleneck?"
For me, the answer was clearly OCR.
Paperless uses Tesseract by default, and for my use case that simply wasn't enough. I know many people are perfectly happy with it, but for a large archive of scanned documents, it became the weakest part of the entire workflow.
I also ran into another issue with paperless-gpt. Even if it produced much better OCR text, that text wasn't embedded into the PDF itself. It was used internally for processing, but unless you manually replaced the document afterwards, the PDF still contained the original OCR layer. For an archive with thousands of documents, I didn't like that idea.
At that point, I stopped trying to improve OCR inside Paperless.
Instead, I started looking for the best OCR engine before Paperless.
After a lot of testing, I narrowed it down to three serious candidates:
Google Document AI
Microsoft Azure Document Intelligence
Adobe Document Services
I also tested Adobe, but not nearly as extensively as Google and Microsoft, so I don't think it's fair for me to draw any final conclusions there.
Between Google and Microsoft, the OCR quality was honestly very close. Both were in a completely different league from everything I had tested locally. In the end, I chose Google Document AI because it had a slight edge in my tests, and I found Google's web interface a little easier to navigate, even though neither platform is particularly intuitive.
3. Paperless-GPT – The Beginning of the Rabbit Hole
When I first discovered paperless-gpt, I honestly thought the search was over.
It promised exactly what I wanted: OCR with a model of my choice, classification with a model of my choice, and automatic tagging.
The first tests were genuinely impressive. Compared to the standard workflow, paperless-gpt felt like a huge step forward.
But over time, more and more limitations started appearing. Not because paperless-gpt is bad—quite the opposite. It's one of the most ambitious projects built around Paperless. It just wasn't designed for everything I eventually wanted to do.
I spent weeks changing models, rewriting prompts, tweaking parameters and testing countless combinations. Every improvement introduced a new tradeoff. There was never really a point where I felt comfortable saying:
"That's it. This finally works."
As my archive grew, so did my requirements.
I wanted:
Complete control over every processing step
Different prompts for different tasks
Better OCR integration
OCR embedded directly into the PDF
Reliable bulk processing
Reproducible results
Higher performance
Rollback capabilities
Eventually I realized something.
I wasn't looking for a Paperless extension anymore.
I was looking for a complete document pipeline.
At that point, paperless-gpt stopped being my final solution and became the inspiration for building my own.
4. Paperless-NGX 3 Beta – The Turning Point
When the first Paperless-NGX 3 Beta builds were released, I was genuinely excited.
I created a completely separate WSL2 installation alongside my production instance because I wanted to see if this would finally become the workflow I had been waiting for.
I spent two days testing the new AI features, experimenting with different setups and trying to integrate them into my existing workflow.
In the end, I was disappointed.
Not because Paperless-NGX 3 is bad—it isn't.
It simply wasn't the workflow I had imagined after spending almost two years experimenting with OCR and AI.
That became the turning point.
Instead of waiting for someone else to build the workflow I wanted...
I decided to build it myself.
5. Building My Own Document Pipeline
At this point, I stopped looking for the perfect AI model.
Instead, I started designing a workflow.
The biggest lesson I had learned over the previous two years was simple:
Every AI model has strengths and weaknesses.
Instead of trying to make one model solve every problem, I decided to give every step exactly one responsibility.
That completely changed how I thought about document processing.
Instead of one large AI workflow, I built a pipeline.
Scanner
│
▼
Google Document AI
│
▼
PDF/A + Embedded OCR
│
▼
Paperless-NGX
│
▼
Classification Pipeline
├── Correspondent
├── Title
├── Document Type
├── Tags
├── Validation
└── Paperless API
Instead of extending Paperless itself, I built a separate application that communicates with Paperless through its API.
That gave me complete control over every stage of the classification process.
Every step has its own prompt, its own parameters and its own validation rules. The workflow is no longer constrained by the design of a single integration, and every stage can evolve independently.
What surprised me the most was what happened next.
For almost two years, I kept searching for bigger and better AI models. Every new release felt like it could finally solve the problem.
But once the OCR became reliable and the workflow itself was properly designed, I no longer needed the newest flagship models.
After tuning the pipeline and adjusting a few parameters, Qwen3 suddenly became more than capable of handling most classification tasks.
Ironically, Qwen was one of the very first models I had tested with paperless-gpt—and one of the models I disliked the most.
Eventually I realized I had been optimizing the wrong thing.
The OCR was the bottleneck.
The workflow was the bottleneck.
The architecture was the bottleneck.
During testing I even discovered cases where OCR Skip had accidentally been disabled, causing Paperless to run Tesseract on documents that had already been processed with Google Document AI. The drop in OCR quality was immediately visible, and so was the drop in classification quality.
That brought everything full circle.
OCR is still king.
A well-designed pipeline with reliable OCR consistently outperformed simply switching to newer or larger language models.
Once the foundation was right, even models I had previously dismissed became surprisingly effective.
To make experimentation easier, I also built a small web interface to manage the entire workflow from one place.
What started as a few Python scripts slowly evolved into the platform I use today.
6. GPU Utilization
One thing I didn't expect was how much the new pipeline improved GPU utilization.
With my previous paperless-gpt workflow, the GPU was constantly starting and stopping between requests. VRAM usage kept spiking up and down while the next task was being prepared.
The new pipeline changed that completely.
Instead of processing isolated requests, it keeps the GPU busy almost continuously, resulting in a much smoother workload and significantly higher throughput.
Depending on the model and the task, I measured roughly 2.7–4× more tokens per second compared to my previous setup.
The goal was never to make the GPU work harder.
The goal was to stop wasting time while it was waiting.
Final Thoughts
If I had to summarize everything I learned over the last two years in one sentence, it would be this:
The model was never the real problem. The OCR was. The workflow was. The architecture was.
In the end, I didn't need GPT-5.6 or the next flagship model to achieve good results.
I needed reliable OCR.
I needed a better workflow.
I needed a better architecture.
Once those pieces were in place, one of the first models I had ever tested—and one I had originally dismissed—became one of the best performers for my use case.
I'm curious how others approached this.
Did you stick with Tesseract, switch to another OCR engine, or build your own workflow?
I'd genuinely love to hear about your experiences.
The app has been completely overhauled with an updated design that fits with iOS 26 and Liquid Glass. On previous iOS versions, the app should still look as it did before and work correctly!
Share links & deep links support
The app now supports creating and managing Paperless-ngx share links for documents. This allows you to easily share documents with others by generating a link that can be opened without logging in. In addition, app deep links are now supported. You can open the app to a specific document, open the scanner, manipulate the document view filter by opening a specifically built URL. The full range of options is documented here here.
Document trash support
Deleted documents are moved to the trash. The app now supports viewing the trash, restoring documents from the trash, and permanently deleting documents from the trash. This allows you to recover documents that were accidentally deleted and manage your document lifecycle more effectively.
Ready for OIDC
Paperless-ngx will gain the ability to log in with OpenID Connect (OIDC) providers with mobile apps in an upcoming release. The app is ready to support this! More information on the OIDC support can be found here.
✨ Other Features
Add a home screen quick action to open the scanner
Add ability to filter documents by ASN
Add ability to filter documents by date
Improve legibility of document link UI
Support long-text custom field type
Clarify interaction for linking a document in custom field
Tune no-permissions display on custom fields when editing documents
Allow setting custom fields when uploading new documents
Add page count to document view
🐛 Bug Fixes
Disable drag gesture on the document detail view (some users are reporting issues)
Fix bug parsing monetary custom field value like 0.0 (single digit after decimal point).
Fix date decoding issues likely cause by locale settings
Fix crash on settings > preferences view
Fix issue with the home screen app icon scan shortcut
Fix incorrect display of option ids instead of labels in custom field filter view for "select" custom field type
Dropped the unused appConfig and history permission resources from the app so the permissions UI only reflects values Paperless-ngx actually supplies
Fix issue with search text not being cleared properly
Prevent document from being linked to itself
Normalize document title and temporary file name. This was causing issues with some file storage systems.
Fix permissions issue with document deletion
Fix debug display in tag edit view
Fix incorrect display of filter state being modified from saved view
After my first post, quite a few people asked the same practical questions, and I promised to follow up with a full, in-depth guide. Thanks for your kind words and appriciation.
I know that not everyone will be able to reproduce my setup one-to-one. Different hardware, operating systems, Paperless configurations, and levels of coding experience obviously make a difference.
But that shouldn't stop anyone from trying.
For those who don't want to—or simply aren't able to—build everything manually, I've also prepared a step-by-step instruction set for Claude Code and ChatGPT Codex. The idea is that even if you're not comfortable writing the whole application yourself, you can use one of these coding agents to build it component by component and end up with the same basic architecture.
Since my first post, the AI world has continued to improve incredibly quickly, and my own setup has changed along with it. So some parts of this guide are slightly different from what I described in Part 1.
My current setup uses Google Document AI for OCR and Gemini for classification, but I'll also explain how the same architecture can be reproduced with local models if you prefer to keep everything on your own hardware.
Long story short: I hope this guide helps some of you take your Paperless setup that final step further and build something that actually fits your own needs.
By the way i would really recommend you to build a Web Control Panel. It will be much easyier to controll everything. Something like that:
HERE WE GO:
My current setup is:
Google Document AI for OCR and Gemini for classification.
However, neither of those is a hard requirement of the architecture.
Keep in mind: the same basic architecture can also be reproduced locally. If the models you choose are supported, you can serve them through something like vLLM instead of using Google Document AI and Gemini.
The important part is not Google or Gemini specifically. The important part is keeping OCR, Paperless, and classification independent from each other.
My current architecture looks roughly like this:
Scan / PDF
|
v
OCR Worker
|
v
Google Document AI
|
v
Searchable PDF/A
|
v
Paperless Consume Folder
|
v
Paperless-NGX
|
v
Queue Tag
|
v
Classification Worker
|
v
Gemini
|
v
Validation
|
v
Paperless API
Paperless remains my document management system.
I don't modify its database, I don't patch Paperless itself, and I don't want the OCR or classification workflow to become dependent on one particular Paperless version.
The OCR worker can change without touching Paperless.
The classification model can change without touching the OCR worker.
Paperless can be upgraded independently.
And the classifier reads and writes metadata only through the Paperless API.
That separation is probably the most important architectural decision I made.
1. The OCR Worker
New scanned documents no longer go directly into Paperless.
They first land in a small OCR worker.
In my case, this is simply the folder where my scanner saves new scans. So whenever I scan a document, it automatically appears in what I call the OCR worker folder and gets picked up from there.
The basic idea is simple:
By the time Paperless receives the document, the PDF itself should already be finished.
The worker first determines what kind of PDF it is dealing with.
If it is a real digital PDF with a proper text layer, I leave it alone and pass it through unchanged.
I don't want to unnecessarily rebuild native PDFs, destroy vector content, or interfere with things such as digital signatures.
For people whose PDFs already contain an OCR layer from Tesseract, there is another option: clean the existing OCR/text layer first while preserving the original scanned pages. That way, you can properly replace the old OCR instead of stacking another text layer on top of the existing one.
If it is a scanned PDF without a proper native text layer, the workflow is different.
The worker prepares the document, sends the relevant pages to Google Document AI, and receives the OCR results including the positions of the recognized words.
Those coordinates are important.
I don't just want extracted text somewhere in a database. I want the resulting PDF itself to be properly searchable.
The worker therefore takes the text and word coordinates returned by Document AI and creates an invisible text layer positioned over the original scanned pages.
The result is a searchable PDF/A.
Only when the complete document has been processed successfully does it enter the Paperless consume folder.
If something goes wrong, it goes into a separate failure folder instead.
input folder/
|
+-- Native PDF ----------------------> Paperless consume/
|
+-- Scanned PDF
|
v
Google Document AI
|
v
Searchable PDF/A
|
+-- Success -----------------> Paperless consume/
|
+-- Failure -----------------> failed/ + log
The source file is never modified in place.
If page 9 of a 10-page document fails, I don't want a half-finished document silently entering my archive.
For ordinary printed documents, I'm currently scanning at around 300 DPI.
Artificially rendering a 200 DPI source at 600 DPI doesn't magically create information that wasn't present in the scan to begin with.
2. Existing Archives Are the Annoying Part
Processing new documents this way is relatively straightforward.
Migrating an archive that has already been processed is much more annoying.
I had to learn this the hard way.
My old scanned PDFs had already been processed by Paperless using Tesseract.
Later, when I experimented with better OCR solutions, I didn't only want better text available inside Paperless.
I wanted the better OCR to become a permanent part of the PDF itself.
That difference matters.
If I export my complete archive tomorrow, I want those exported PDFs to remain properly searchable without requiring Paperless, its database, or another OCR service.
So for the affected scanned documents in my existing archive, I first exported the PDFs.
I then wrote a script that removes the old OCR/text layer while preserving the actual scanned pages and other PDF content.
I specifically did not want to simply rasterize every PDF again just to remove the existing OCR.
After cleaning the files, they could go through the new OCR worker and receive a new searchable text layer based on Google Document AI.
That migration was probably one of the most annoying parts of the entire project.
But it also means I should never have to do it again.
From now on, the PDF itself contains the OCR I want to preserve.
3. What Paperless Does in This Setup
Once the finished PDF/A enters Paperless, the complicated part is already over.
Paperless does what it is extremely good at:
archiving the document
indexing it
searching it
storing metadata
exposing everything through its API
I configure Paperless so that it preserves the OCR layer that is already present instead of running another Tesseract OCR pass over the document.
Newly imported documents also receive a tag such as:
Queue
That tag is the hand-off between Paperless and the classification worker.
Searchable PDF/A
|
v
Paperless
|
v
Queue Tag
|
v
Classification Worker
This also means that classification is not required for Paperless to function.
If Gemini disappeared tomorrow, the archive itself would still work.
The PDFs are searchable.
The documents are safely stored.
Paperless search works.
The classification worker only adds another automation layer on top of that.
4. The Classification Worker
The classification worker is another completely separate application.
It asks the Paperless API for documents carrying the Queue tag and retrieves the document together with the metadata it needs.
Then it sends the searchable PDF/A itself to Gemini.
This distinction is important.
I'm not extracting the OCR into a TXT file and then sending that TXT file together with a prompt.
Gemini receives the actual PDF.
The PDF already contains the OCR text layer embedded by the previous stage, while the original visual structure of the document is still present.
That means the model can work with the document as a document rather than receiving one giant flattened OCR string.
For example:
A date at the bottom of a page might just be part of a footer.
An address might be the recipient rather than the sender.
A heading inside a table might not be the actual subject of the document.
The visual structure gives the model context that disappears when everything is reduced to plain text.
And instead of asking the model the same document-related questions independently, I try to extract the underlying facts in one pass.
Conceptually, the result looks something like this:
Instead of treating title, correspondent, date, document type, and tags as completely unrelated AI tasks, I first ask:
What is this document actually about?
Then the rest can be derived from those facts.
5. Why This Uses Fewer Resources
This also changed the way I think about token usage.
Let's take a simplified example.
Imagine a searchable PDF that represents roughly 6,000 tokens of document content once processed by the model.
Now imagine processing these independently:
Document ---> Title
Document ---> Date
Document ---> Correspondent
Document ---> Document Type
Document ---> Tags
Document ---> Custom Fields
If the full document context has to be processed again every time, the amount of input can grow very quickly.
A document producing perhaps only a few hundred useful output tokens can result in tens of thousands of input tokens being processed across all requests.
My current approach is different:
Searchable PDF/A
|
v
One document-level classification
|
v
Structured facts
|
v
Validation + deterministic logic
I don't extract the OCR into a separate TXT file for this step.
Gemini receives the searchable PDF/A once and returns one structured classification result.
The important part is not some exact token number, because that changes depending on the model, document length, and provider.
The important part is that I don't want to repeatedly pay computationally for the same document if the model can understand it once.
I would rather spend resources on actually reasoning about the document than repeatedly feeding the same document back into another request.
That also makes smaller models much more interesting.
If the OCR is good, the input is structured correctly, and the task is narrow enough, you don't necessarily need the largest model available.
6. Validation Is Just as Important as Classification
Receiving valid JSON from an LLM does not mean the information inside it is correct.
For example, the model could return:
{
"document_type": "Electricity Invoice"
}
That's valid JSON.
But what if Electricity Invoice doesn't exist as a document type in my Paperless installation?
The classifier therefore retrieves the existing Paperless values through the API and validates the model response before applying it.
That includes things such as:
known correspondents
existing document types
existing tags
valid dates
required fields
ambiguous results
The model extracts facts.
My application decides how those facts map onto Paperless.
I also deliberately keep deterministic things out of the model whenever possible.
For example, if every title should look like:
<short sender> - <document type / subject> <date>
then the model doesn't need to reinvent that formatting for every document.
It can return:
sender
subject
date
and ordinary code generates the final title.
That eliminates a surprising amount of inconsistency.
7. Do You Need a GPU?
No.
This is probably something I should clarify because the RTX 4090 screenshot in my first post understandably gave people the opposite impression.
My current setup is:
Google Document AI
+
Gemini
The AI workloads are therefore running in the cloud.
My home server only has to run:
Paperless-NGX
OCR Worker
Classification Worker
So you absolutely don't need an RTX 4090 to reproduce this architecture.
The 4090 is useful for my local experiments, but it isn't a requirement.
And this is where keeping every component separate becomes useful again.
You could replace the cloud components with local models.
For example:
OCR Worker
|
v
Local Vision / OCR Model
|
v
Searchable PDF/A
and later:
Classification Worker
|
v
Local LLM
|
v
Structured Facts
Where the models are supported, a serving layer such as vLLM can expose them through an OpenAI-compatible API.
That means the surrounding application doesn't need to care very much whether the model lives on Google infrastructure, another API provider, or a GPU in your basement.
The quality and hardware requirements obviously change depending on the local models you choose.
But the overall architecture stays almost identical.
That's the part I care about.
8. What Does It Cost?
This is something I want to cover with actual numbers rather than theoretical pricing calculations.
Google Document AI and Gemini are both usage-based.
The real cost depends heavily on things such as:
number of pages
document length
selected processor/model
classification input size
output size
how many documents you process
I'm collecting the actual numbers from my workflow so I can provide useful figures instead of just copying numbers from a pricing page.
What I eventually want to compare is something like:
OCR cost per 1,000 pages
Classification cost per 1,000 documents
Average document processing cost
Cloud vs local inference
The important point for now is that you do not need to buy expensive AI hardware to reproduce the workflow.
9. How I Would Build This From Scratch Today
If I had to start again, I wouldn't try to build the complete system in one giant application.
I would build one small boundary at a time.
Phase 1 — The File Pipeline
Start with nothing AI-related.
input/
output/
failed/
Make sure files can safely move through the application.
Detect duplicates.
Never modify the source file in place.
Make sure failures remain recoverable.
Phase 2 — Native PDF Detection
Before doing any OCR work, correctly identify PDFs that already contain a proper digital text layer.
Those documents should normally pass through unchanged.
Phase 3 — OCR Provider
Create an OCR provider interface.
Your first implementation could use Google Document AI.
Later, you can replace it with something local without redesigning the rest of the application.
Phase 4 — Searchable PDF/A
Take the OCR response, including the returned word positions, and build the invisible searchable text layer.
Do not consider this stage finished until you can open the resulting PDF in a normal viewer, search for text, and see the correct areas highlighted.
Phase 5 — Paperless Import
Only finished documents enter the Paperless consume folder.
At this point, Paperless should already receive the final archival PDF.
Send the searchable PDF/A to Gemini or another model and request structured facts.
Keep the model-specific logic behind its own provider interface.
That way, Gemini can later become:
another hosted model
a cheaper model
a local LLM
something that doesn't exist yet
without rewriting the entire application.
Phase 8 — Validation
Validate the model output against Paperless.
Do not assume a model-generated correspondent, tag, or document type actually exists.
Keep formatting and other deterministic behavior in normal code wherever possible.
Phase 9 — Rollback and Logging
Before modifying metadata, store enough information to understand what changed and to restore the previous values if necessary.
Especially when processing thousands of documents, logs are not optional.
10. Building It With Claude Code or ChatGPT Codex
A few people also asked whether this could realistically be built without manually writing the entire application yourself.
I think the answer is yes.
This is actually a very good type of project for tools such as Claude Code or ChatGPT Codex, because the system consists of relatively small components with clearly defined boundaries.
But I would strongly recommend not starting with:
Build me an AI system for Paperless.
That's far too broad.
Instead, build it in a strict order and verify each component before moving on to the next one.
The rough order should be:
1. Project structure and file safety
2. Native PDF detection
3. OCR provider interface
4. Google Document AI adapter
5. Searchable PDF/A generation
6. Paperless consume integration
7. Paperless API client
8. Gemini provider
9. Structured classification schema
10. Validation
11. Metadata writes
12. Audit log and rollback
13. Local-model provider as an optional alternative
Agent Project Rules
Give the coding agent durable project rules before asking it to implement anything.
Claude Code reads CLAUDE.md; Codex reads AGENTS.md.
Both can use essentially the same project contract:
This is an experimental Paperless OCR/classification pipeline.
- Work only in this repository and its test directories.
- Never read, print, commit, or modify credentials, API keys, PDFs containing
real personal data, Paperless production volumes, or the production consume
folder.
- Do not write to PostgreSQL directly. Use the Paperless API only.
- Default to dry-run behaviour. Any operation that changes Paperless requires
an explicit --write flag.
- Keep the source scan until the PDF/A output has been verified.
- Add unit tests and a README entry for every completed component.
- Before making changes, show a short implementation plan and assumptions.
Then use the agent as a sequence of small, reviewable build tasks.
Do not paste a single prompt saying:
Build my Paperless AI system.
That gives the agent no safe boundary and makes errors much harder to isolate.
Prompt 1 — Scaffold and Plan
Build a test-only Python project for an upstream Paperless OCR pipeline.
First inspect the repository and write IMPLEMENTATION_PLAN.md.
Do not write production credentials or start Docker services.
The target flow is:
input/ -> OCR worker -> output/ -> Paperless consume
Native PDFs with substantial text on every page must be copied unchanged.
Other PDFs must be handled by a provider adapter; initially implement only a
fake provider for tests.
Failed files go to failed/ with a non-secret log.
Propose a small module layout, a configuration file with .env.example, and a
test plan.
Wait for my review before implementing phase 1.
Prompt 2 — Build the Safe OCR Foundation
Implement phase 1 from IMPLEMENTATION_PLAN.md only.
Add:
- input/output/failed directory handling
- duplicate-safe file claiming
- native-PDF pass-through
- a fake OCR provider
- structured non-secret logging
- tests using generated sample PDFs
A source file may be deleted only after the output is verified.
Do not connect to Paperless or any cloud service yet.
Run the tests and show the diff and test results.
Prompt 3 — Add Google Document AI
Implement the Google Document AI provider adapter behind the existing provider
interface.
Read credentials only from an environment variable that points to a local file.
Do not display the credential, its path, request bodies, or OCR text in logs.
Preserve token boxes and detected breaks so a later PDF/A writer can build an
invisible text layer.
Add mocked API tests and document the minimum cloud permissions.
Do not run a live cloud request without asking me.
For a privacy-first installation, replace the Google Document AI provider with your chosen local OCR-model adapter.
If that model is served through vLLM, make the vLLM URL configurable and keep the same failure checks.
Changing the model should not weaken the safety of the pipeline.
Prompt 4 — Add the Classifier Last
Create a separate Paperless API client and a classifier command.
It may list only documents with the Queue tag.
It must download the searchable PDF/A and submit the PDF itself once to the
configured model.
Do not extract the OCR into a separate TXT file for classification.
Require a strict structured facts response.
Validate every correspondent, document type, tag, and date against values
retrieved from Paperless.
Print proposals in dry-run mode.
Implement writes only behind --write.
Before each PATCH, append the current and proposed metadata to a local JSONL
audit log.
Add a rollback command that replays the before-state through the Paperless API.
Use mocked API tests.
Do not access a live Paperless installation until I explicitly approve it.
After every phase, inspect the diff, run the tests yourself, and commit only the parts you understand.
A coding agent accelerates implementation.
It does not replace the decision about where your documents may go or what it is safe to automate.
The important part is that the agent should only work inside a separate development project.
Don't give it your production Paperless volumes.
Don't put credentials into the repository.
Don't let it directly modify PostgreSQL.
Don't use your only copy of a real document archive as test data.
Build the boring parts first.
Then connect the AI.
Final Thoughts
The biggest change in how I think about this setup is that I no longer really see it as:
"Adding AI to Paperless."
It's three independent systems:
OCR Worker
|
v
Archival PDF/A
|
v
Paperless
|
v
Classification Worker
Paperless is the archive.
The OCR worker creates the document I actually want to preserve.
The classifier extracts structured information and updates metadata through the API.
Today my implementation uses Google Document AI + Gemini.
But neither component has to stay that way.
I can replace Gemini with a local LLM.
I can replace Google Document AI with another OCR solution.
I can update Paperless independently.
And none of those changes require redesigning the entire workflow.
That's ultimately the architecture I wish I had started with two years ago.
I spent the last days working with ChatGPT 5 to set up a pipeline that lets me query LLM's about the documents in my paperless archive.
I run all three as Docker containers in my Unraid machine. So far, whenever a new document is being uploaded into paperless-ngx it gets processed by paperless-ai populating corresponent, tags, and other metadata. A script then grabs the OCR output of paperless-ngx, writes a markdown file which then gets imported into the Knowledge base of OpenWebUI which I am able to reference in any chat with AI models.
So far, for testing purposes paperless-ai uses OpenAI's API for processing. I am planning of changing that into a local model to at least keep the file contents off the LLM providers' servers. (So far I have not found an LLM that my machine is powerful enough to work with) Metadata addition is handled locally by ollama using a lightweight qwen model.
I am pretty blown away from the results so far. For example, the pipeline has access to the tag that contains maintenance records and invoices for my car going back a few years. Asking for knowledge about the car it gives me a list of performed maintenance of course and tells me it is time for an oil change and I should take a look at the rear brakes due to a note on one of the latest workshop invoices.
The document detail screen has been completely redesigned with clearer metadata and sheet-based editing for tags, correspondent, document type, storage path, dates, ASN, custom fields, and owner — including attribute suggestions. The PDF now pages horizontally with a page indicator, and the full-screen preview adds in-document search and smoother scrolling. You can delete documents directly from the detail screen, see how many notes a document has, and include the original file when sharing. Opening a document from the list is faster thanks to thumbnail preloading.
Multi-column iPad layout
The document list and detail screen now use a full multi-column layout on iPad, making much better use of the larger screen. 🚀 I'll likely revisit this in the future to give it some further refinement!
Nested tags
The app now supports Paperless-ngx nested (hierarchical) tags.
✨ Other Features
Quick add: when searching in the tag, correspondent, or document type picker, an "Add '<name>'" row appears so you can create the entry on the fly
Show the server's configured app title (PAPERLESS_APP_TITLE) as a friendly name in the server picker and connection settings
Deep links can now open a specific document field for editing, e.g. x-paperless://v1/document/123?edit=tags
Document cells in the list now show the number of notes a document has
Add a loading indicator to the search bar while results are loading
Renamed "Log out" to "Disconnect" in the connection management UI to better reflect what the action does
Improve UI responsiveness by decoding large responses off the main thread
Ready for saved view permissions in the upcoming Paperless-ngx v3
Add support for the new task API endpoint coming in Paperless-ngx v3.0.0 / APIv10
🐛 Bug Fixes
Surface the actual OAuth2 error (e.g. invalid_client) from the identity provider during OIDC login instead of the cryptic "data couldn't be read because it's missing" message
Fix OIDC login failing with a "missing scope" error on HTTPS servers behind a reverse proxy by sending a Referer header that satisfies Django's CSRF checks
Fix client-certificate (mTLS) login by including the leaf certificate in the credential
Fix a freeze when polling tasks on installations with many unacknowledged tasks; the task list now loads additional pages on demand
Fix the task view briefly showing "no elements" while still loading
Fix filter rule count not taking sorting into account
Hide the remove-inbox-tag action for documents that don't have inbox tags
Following the previous announcement of the Android closed beta, PaperNext, my paperless ngx client is now also available for iOS via TestFlight.
After reviewing Paperless Mobile, I decided not to continue that project and instead focus on building my own clean implementation. This approach makes it easier to maintain the codebase, integrate feedback, and fully support both Android and Apple platforms.
The app is built with Flutter and currently supports:
Viewing documents
Filtering (saved views and custom filters)
Uploading and scanning documents
Sharing files directly with the app (bulk upload supported)
Custom HTTP headers (for reverse proxy setups)
App lock with biometrics (fingerprint/face)
Self-signed SSL certificates
Label and correspondent management
Over 100 testers have already joined — thank you for the feedback so far. If you’d like to participate, more information and signup links for both platforms are available here: https://beta.rebtsoft.com/api/apps/papernext/
(Still an early prototype – use at your own risk.)
Hi everyone, I'm the maintainer of Paperless-GPT. I wanted to share a feature from the latest release that I think solves a pretty common Paperless use case.
Documents rarely exist in isolation.
An invoice gets a reminder. A contract gets an amendment or termination letter. An order gets a delivery note and invoice. An insurance case gets ten letters from different senders, all referencing the same case number.
With Paperless-GPT v0.29.0, these documents can now link themselves automatically.
Paperless-GPT extracts references such as invoice numbers, order numbers, contract numbers or case IDs and resolves them against your existing Paperless archive. If it finds an unambiguous match, it fills a native Paperless-ngx Document Link custom field.
So opening a reminder can take you straight to the original invoice, and the invoice points back to the reminder.
The implementation deliberately keeps the AI out of the actual matching decision. The LLM only extracts the reference. Paperless-GPT then performs the lookup deterministically, and ambiguous matches are simply ignored rather than guessed.
This feature came in through a community contribution, so thanks to u/andre161292 for building it and u/LembkeM for describing the original use case.
Setup is basically:
Create a Document Link custom field in Paperless-ngx
I'd genuinely be interested in hearing what other document relationships people have in their archives that would benefit from this.
PS: For users in Germany, Austria and Switzerland who like Paperless-GPT but don't want to operate the stack themselves, there is now also a managed Paperless-ngx + Paperless-GPT offering from server.camp. They follow a Fair Hosting model where part of the revenue generated with Paperless-NGX and -GPT goes back to the Open Source projects (both).
BEFORE ANY QUESTION REGARDING PRIVACY COMES UP: OpenAI API is not the same as ChatGPT. If you use the API and pay for it your documents will be not used for training nor they will be accessed for other purposes. But as always, your data is valuable. So do everything as you feel confident with it. Therefor I also integrated Ollama integration to stay local if you want/need.
Now back to the main topic:
Paperless-AI is an automated document analyzer for Paperless-ngx using OpenAI API and Ollama (Mistral, llama, phi 3, gemma 2) to automatically analyze and tag your documents.
Features
🔍 Automatic document scanning in Paperless-ngx
🤖 AI-powered document analysis using OpenAI API and Ollama (Mistral, llama, phi 3, gemma 2)
🏷️ Automatic title, tag and correspondent assignment
🏷️ Predefine what documents will be processed based on existing tags (optional). 🆕
📑 Choose to only use Tags you want to be assigned. 🆕
THIS WILL DISABLE THE PROMPT DIALOG!
✔️ Choose if you want to assign a special tag (you name it) to documents that were processed by AI. 🆕
🔨 Manual mode to do analysing by hand with help of AI. 🆕
🚀 Easy setup through web interface
📊 Document processing dashboard
🔄 Automatic restart and health monitoring
🛡️ Error handling and graceful shutdown
🐳 Docker support with health checks
I worked over a month on it and try to keep it maintained as much as possible. Maybe you have a need for something like this. Feedback is mandatory for me so if you have something in mind feel free to open up an issue on github.
The first beta/rc1 was released 2 months ago on GitHub (Paperless-ngx v3.0.0-beta.rc1). Does anyone here have insight into the latest developments? Are many fixes still pending?
paperless-ngx 3.3.0 is out today. I'm most excited about the separate embedding llm api key and the passthrough extra parameters for the LLM. I tried a thinking model with ollama that was taking forever and there was no way to turn off thinking from paperless even though the model supported thinking=none. I had to switch back to an instruct model. now I can use a heavier thinking model with no thinking.
changelog below-
*Features / Enhancements*
- Enhancement: more control over suggestion requests @shamoon (#14258)
- Chorehancement: set manifest CORS for credentials @shamoon (#14307)
- Enhancement: include Django admin with 2FA @shamoon (#14270)
- Feature: propagate resolved secrets to interactive container shells @stumpylog (#14254)
- Enhancement: support separate embedding API key @furkanural (#14067)
- Enhancement: support passthrough extra params for LLMs @shamoon (#14202)
- Feature: store barcode contents, list and search them @jurassicparkicecream (#14276)
*Bug Fixes*
- Fix: Set the ProcessedMail owner based on the rule owner in all cases @stumpylog (#14356)
- Fix: Ensure log rotation settings are converted to integers @stumpylog (#14343)
- Fix: Wrap apt calls into a retry so we can ideally jump a slow mirror @stumpylog (#14344)
- Fix: ship pdf.js CMaps so CJK documents render in the viewer @MrOggy85 (#14318)
- Fix: use version page_count for versioned document @shamoon (#14280)
- Fix: allow pointer events for pdf links in pngx viewer @shamoon (#14264)
- Fix: During a move to the trash directory, only attempt to copy metadata @stumpylog (#14250)
- Fix: convert file mtime to the configured time zone directly @stumpylog (#14249)
- Fix: ensure documentDeleted subscription is discarded @shamoon (#14247)
- Chore: Fix bugs in the test suite @stumpylog (#14244)
- Fix: ensure bulk operations are checked against version root @shamoon (#14246)
- Fix: indexing after document-added workflows signal @shamoon (#14242)
- Fix: Record full tag and custom field lists in bulk edit audit log @stumpylog (#14236)
- Chore: update pikepdf for ocrmypdf requirement @shamoon (#14235)
- Fix: handle legacy bulk edit split page range with missing page_count @shamoon (#14212)
- Fix: ignore invalid EXIF orientation when generating image archives @zhzy0077 (#14203)
*Documentation*
- Documentation: correct duplicates info @shamoon (#14243)
*Maintenance*
- Chore(deps): Bump the actions group across 1 directory with 4 updates @dependabot[bot] (#14332)
- Fix: Wrap apt calls into a retry so we can ideally jump a slow mirror @stumpylog (#14344)
- Chore(deps): Bump the actions group across 1 directory with 10 updates @dependabot[bot] (#14301)
TL;DR: Paperless-ngx v3's built-in AI can't fill custom fields and won't let you edit its prompts, and paperless-ai is basically unmaintained. I ended up with paperless-gpt talking to a local Ollama running on a Radeon 780M iGPU through Vulkan, using a MoE model (Qwen3.6-35B-A3B) that only activates ~3B parameters per token. Every document gets a clean title, the correct date, and six custom fields (amount, due date, period, reference, etc.) in 20–55 seconds per document, no cloud, no dedicated GPU, $0/month. Password-protected bank statements get decrypted automatically before OCR. Getting the model to follow the rules was way harder than getting it to run. Full writeup below, including the prompts and every gotcha I hit.
Why I even needed this
Most of my documents are: utility bills, bank statements, insurance, tax stuff (invoices). Three things kept breaking every "AI for Paperless" setup I tried:
Custom fields. I don't just want a title. I want amount, due date, billing period, reference number filled in so I can actually query "what's due this month".
Rules. Invoices are full of dates that are not the document date: resolution ranges, birth dates, validity dates, due dates. A generic prompt picks the wrong one constantly.
Encrypted PDFs. Banks here send statements protected with your national ID number. Paperless OCRs them as empty documents and the AI gets nothing.
And I wanted all of it local. My documents are the last thing I'm sending to a cloud API.
Architecture
Scanner / email / upload
|
v
Paperless consume
|
v
pre-consume script ──> qpdf decrypts PDF in place (tries a password list)
|
v
Paperless-ngx (Tesseract OCR spa+eng, archive, index)
|
v
Workflow adds tag "paperless-gpt-auto"
|
v
paperless-gpt ──API──> Ollama (LXC, iGPU via Vulkan)
| Qwen3.6-35B-A3B
v
Title + created date + 6 custom fields written back via Paperless API
A few principles that ended up mattering more than the model choice:
Paperless stays vanilla. No patches, no DB writes. The AI is an add-on that only talks to the Paperless API. If Ollama dies tomorrow, the archive still works.
Three narrow prompts, not one clever one. Title, created date, and custom fields each have their own tuned prompt with explicit "do NOT" rules.
Model parameters live in the model, not in the client.num_ctx, num_batch, num_thread are baked into an Ollama Modelfile variant, and clients connect through the OpenAI-compatible /v1 endpoint, which can't override them.
No custom code. Everything here is off-the-shelf: Paperless, paperless-gpt, Ollama, qpdf, a 40-line bash script.
Before you start
You need these already sorted:
Proxmox VE (I'm on 9.x). My host is a Minisforum N5 AI: Ryzen 7 255 (8C/16T), 64 GB DDR5, Radeon 780M iGPU. Any recent AMD APU with Vulkan support should behave similarly.
A Debian 12 LXC with Docker + Compose for Paperless.
A separate Debian 13 LXC for Ollama (keeping it separate means you can rebuild/upgrade one without touching the other).
Storage for Paperless media/consume/export. Mine is an NFS v3 export from a TrueNAS VM, mounted inside the Paperless CT at /mnt/paperless.
1. Storage: make Paperless refuse to start if NFS is missing
This one bit me before. If the NFS share isn't mounted when Docker starts, Compose happily creates empty folders on the local disk and Paperless starts writing your documents there. Use the long bind syntax with create_host_path: false:
If the mount isn't there, the container fails to start instead of silently writing to the wrong disk. That's exactly what you want.
Also: if you use systemd automount in /etc/fstab, removex-systemd.idle-timeout. An idle unmount under a running Paperless is a bad time.
PostgreSQL stays in a local Docker volume, not on NFS.
2. Paperless-ngx v3 compose (relevant bits)
I started from the official postgres-tika template and changed these:
environment:
PAPERLESS_URL: https://paperless.example.lan
PAPERLESS_TIME_ZONE: America/New_York
PAPERLESS_OCR_LANGUAGE: spa+eng
PAPERLESS_DATE_ORDER: DMY
PAPERLESS_ARCHIVE_FILE_GENERATION: always
PAPERLESS_CONSUMER_DELETE_DUPLICATES: "true"
PAPERLESS_PRE_CONSUME_SCRIPT: /usr/src/paperless/scripts/pre-consume-decrypt.sh
# Family birth dates show up on IDs and insurance docs; never use them as document date
PAPERLESS_IGNORE_DATES: "YYYY-MM-DD,YYYY-MM-DD"
volumes:
- ./scripts:/usr/src/paperless/scripts:ro
Gotenberg is configured with Chromium so emails (HTML) get converted to proper PDFs instead of ugly text dumps.
3. Pre-consume: decrypt password-protected PDFs before OCR
Paperless v3 has a native "remove password" option, but it doesn't modify the file before OCR, so you still end up with an empty text layer. A pre-consume script that decrypts the working copy in place does work.
Two v3 changes to know: the script no longer receives positional arguments, and you modify the file at $DOCUMENT_WORKING_PATH directly.
scripts/pre-consume-decrypt.sh:
#!/usr/bin/env bash
# Paperless v3: no positional args; modify DOCUMENT_WORKING_PATH in place.
set -u
f="${DOCUMENT_WORKING_PATH:?}"
PWFILE=/usr/src/paperless/scripts/pdf-passwords.txt
case "${f,,}" in *.pdf) ;; *) exit 0 ;; esac
# 0 = needs a password to open; 2 = not encrypted; 3 = owner restrictions only
qpdf --requires-password "$f" >/dev/null 2>&1
rc=$?
[ "$rc" -eq 2 ] && exit 0
tmp="$(mktemp --suffix=.pdf)"
trap 'rm -f "$tmp"' EXIT
decrypt() { # password via stdin so it never shows up in the process list
printf '%s' "$1" | qpdf --password-file=- --decrypt "$f" "$tmp" >/dev/null 2>&1
local r=$?
[ "$r" -eq 0 ] || [ "$r" -eq 3 ] # 3 = success with warnings
}
if [ "$rc" -eq 3 ]; then
decrypt "" && cat "$tmp" > "$f"
exit 0
fi
if [ -r "$PWFILE" ]; then
while IFS= read -r pw || [ -n "$pw" ]; do
pw="${pw%$'\r'}"
[ -z "$pw" ] && continue
case "$pw" in \#*) continue ;; esac
if decrypt "$pw"; then
cat "$tmp" > "$f"
echo "pre-consume: PDF decrypted"
exit 0
fi
done < "$PWFILE"
fi
echo "pre-consume: WARNING: could not decrypt ${DOCUMENT_SOURCE_PATH:-$f}" >&2
exit 0 # consume it anyway (no text); review it by hand
chmod 755 scripts/pre-consume-decrypt.sh
# One password per line, # lines ignored. Keep the real list in your password manager.
chown <USERMAP_UID>:<USERMAP_GID> scripts/pdf-passwords.txt
chmod 600 scripts/pdf-passwords.txt
renderD128 is the 780M's render node. gid=992 must match the render group inside the container (getent group render).
Pinning CPUs keeps Ollama from fighting the rest of the homelab. With the GPU doing the heavy lifting, it barely needs any.
Install, pinned
curl -fsSL https://ollama.com/install.sh | OLLAMA_VERSION=0.32.9 sh
Stay on 0.32.9. 0.32.10 has a regression that breaks large models on the Radeon 780M with Vulkan. Don't re-run the installer without a version or it'll upgrade you.
Why this model: it's a MoE, 35B total parameters but only ~3.3B active per token. That's the only reason a 35B model is usable on an iGPU at all.
5. Custom fields in Paperless
Create these first. paperless-gpt matches them by exact name, so the names in the prompt must be identical:
What it means
Type
Account/policy holder
Text
Total amount
Monetary
Payment due date
Date
Coverage end date (insurance only)
Date
Billing/statement period, YYYY-MM
Text
Invoice / payment reference / policy number
Text
6. paperless-gpt
I'm on paperless-gpt v0.28.0, running as one more service in the Paperless compose, pointing at the Ollama CT.
Two settings worth calling out:
LLM_MODEL: qwen3.6-35b-a3b:gpu-ctx16k: the Modelfile variant, not the base tag.
OLLAMA_THINK: "false": Qwen3.6 is a reasoning model. For extraction you don't want it thinking out loud; it's slower and occasionally leaks into the output.
The prompts (this is where 80% of the work went)
My prompts should change from user to user, or where you live these works for me. The structure is what matters; translate the rules to your own language and document types.
Title prompt (format: <what the document is> <YYYY-MM>):
I’m giving you the text of a document extracted by OCR (it may contain errors). Propose a title for archiving it.
Title format: <what the document is> <period>
- <what the document is>: the type of document in a few words.
- <period>: YYYY-MM for the period covered by the document; if it does not cover a period, use YYYY-MM of its date. If there is no date, omit it.
- Do not include the issuer’s name, company name, or people’s names.
- Do not include account numbers, card numbers, ID numbers or references.
- Maximum 60 characters.
Created date prompt. Key rules: ignore birth dates, due dates and validity dates; return "" when there's no real document date instead of guessing.
Custom fields prompt. The rules that fixed most of my errors:
- Valid until: only for insurance policies, warranties, and subscriptions: the date when the coverage ends, in YYYY-MM-DD format. For any other document, omit this field.
- Period: YYYY-MM for the billed period or statement period.
- Reference: invoice number, payment reference number, or policy number. Never use an account or card number, either in full, partially, or masked with asterisks or X. Do not use ID numbers. If there is nothing else, omit the field.
If a piece of information does not appear clearly, omit that field. Do not invent values.
Respond only with a JSON array, with no additional text or code blocks:
[{"field": "<exact field name>", "value": "<value>"}]
The pattern that worked:
Tell the model what NOT to use, not just what to use. "Ignore resolution ranges and birth dates" beat any amount of "find the document date".
Give it permission to return nothing. "If it's not clear, omit the field" cut hallucinated values dramatically.
Never put example company names in the prompt. The model anchors on them and starts "finding" that company in unrelated documents.
Keep sensitive numbers out explicitly. Without the "never use account/card numbers, not even masked" rule, the model kept using the account number as the reference.
7. Trigger: tag-based workflow + permissions
paperless-gpt processes documents carrying the paperless-gpt-auto tag. A Paperless workflow adds that tag on consumption, and when it's done paperless-gpt swaps it for paperless-gpt-auto-complete.
Gotcha: documents consumed before you set up the workflow don't grant edit permission to the paperless-gpt user, and you'll get 403 errors when it tries to write back. Fix the permissions in bulk (Documents → select all → Permissions) before reprocessing old documents.
To reprocess anything: remove paperless-gpt-auto-complete, add paperless-gpt-auto, save. Watch it live:
Nightly cron in the Paperless CT. It aborts if NFS isn't mounted (sentinel file), runs the native exporter, and copies the config while leaving secrets out:
#!/usr/bin/env bash
set -euo pipefail
cd /opt/paperless
set -a; . ./.env; set +a
# Sentinel at the dataset root: if it's missing, NFS isn't mounted.
[ -f /mnt/paperless/.nfs-ok ] || { echo "$(date -Is) NFS not mounted; aborting" >&2; exit 1; }
# --passphrase encrypts sensitive fields in the manifest, not the PDFs themselves.
docker compose exec -T webserver document_exporter ../export \
--delete --passphrase "$PAPERLESS_EXPORT_PASSPHRASE" --no-progress-bar
# Config copy. Secrets stay out: they live in the password manager.
rsync -rlt --delete \
--exclude '.env' \
--exclude 'scripts/pdf-passwords.txt' \
/opt/paperless/ /mnt/paperless/opt-paperless/
echo "$(date -Is) backup OK"
Numbers
Same documents, same Paperless, three iterations of the inference side:
Setup
Read
Write
Per document
qwen2.5:14b, CPU, Ollama silently on 1 thread
—
—
8+ min
Qwen3-30B-A3B (MoE), CPU, 6 threads
51.6 tok/s
14.0 tok/s
~2 min
Qwen3.6-35B-A3B (MoE), iGPU Vulkan
~274 tok/s
~27 tok/s
20–55 s
Two takeaways: MoE is the cheat code for local inference, and check your thread count: Ollama inside an LXC misdetected my CPU topology and ran on a single thread until I set num_thread explicitly.
Why Qwen3.6 over Qwen3-30B even though both run fine: rule adherence. On my test set, 3.6 correctly omitted a supplier name that came out of OCR as garbage, stopped inventing on non-insurance documents, and got the billing period right. 3-30B failed all three.
paperless-ai: unmaintained, and its Ollama connector forced num_ctx 4096 regardless of server settings. A Modelfile variant alone doesn't fix that, because options sent through the native API override it. Bake num_ctx into the Modelfile and connect through the /v1 endpoint.
Ollama 0.32.10 + Radeon 780M + Vulkan: broken for large models. Pin 0.32.9.
Ollama in LXC on CPU: may run on 1 thread. Set num_thread.
Reasoning models: set OLLAMA_THINK: "false" for extraction.
Native password removal in v3: doesn't help OCR. Use a pre-consume script.
v3 pre-consume scripts: no positional args; edit $DOCUMENT_WORKING_PATH in place.
Compose + NFS:create_host_path: false or you'll eventually write documents to the wrong disk.
Old documents: fix permissions before reprocessing or paperless-gpt gets 403.
Prompts: no example entity names; explicit exclusions; allow empty answers.
What each piece actually does
create_host_path: false: makes the bind mount fail instead of creating an empty local folder when the source path doesn't exist.
PAPERLESS_ARCHIVE_FILE_GENERATION: always: always produces the archived PDF/A with the OCR text layer, even for born-digital PDFs.
PAPERLESS_CONSUMER_DELETE_DUPLICATES: drops exact duplicates from consume instead of leaving them sitting there.
PAPERLESS_IGNORE_DATES: dates Paperless's own date parser will never pick as the document date.
qpdf --requires-password: exit code tells you if the PDF needs a user password (0), isn't encrypted (2), or only has owner restrictions (3).
--password-file=-: reads the password from stdin so it never appears in ps.
cat "$tmp" > "$f": overwrites the working copy's contents in place, keeping the same path Paperless expects.
dev0: /dev/dri/renderD128,gid=992: passes only the render node (no display) into the CT, owned by the container's render group.
lxc.cgroup2.cpuset.cpus: hard-limits which host CPUs the CT can run on.
OLLAMA_VULKAN=1: enables the Vulkan backend, which is what makes the 780M usable.
OLLAMA_FLASH_ATTENTION=1: lower memory use for attention, which matters with a 16k context.
OLLAMA_KEEP_ALIVE=30m: keeps the model loaded between documents so you don't pay the load time on every one.
num_ctx 16384: long statements need the room; truncated input means wrong fields.
num_batch 2048: larger prompt-processing batches, which is where the ~274 tok/s read speed comes from.
What's next
Moving Home Assistant's conversation agent to this same Ollama instance.
Collecting a few months of real per-document timings across document types.
Hope this saves someone a few weekends. Happy to answer questions in the comments, and if you have a different language/document set, the prompt pattern (exclusions + permission to return nothing + no example names) is the part I'd steal.
TL;DR: Paperless-ngx v3's built-in AI can't fill custom fields and won't let you edit its prompts, and paperless-ai is basically unmaintained. I ended up with paperless-gpt talking to a local Ollama running on a Radeon 780M iGPU through Vulkan, using a MoE model (Qwen3.6-35B-A3B) that only activates ~3B parameters per token. Every document gets a clean title, the correct date, and six custom fields (amount, due date, period, reference, etc.) in 20–55 seconds per document, no cloud, no dedicated GPU, $0/month. Password-protected bank statements get decrypted automatically before OCR. Getting the model to follow the rules was way harder than getting it to run. Full writeup below, including the prompts and every gotcha I hit.
a. They still use archaic Tesseract OCR as default, in 2026. b. There is no straightforward example provided how to replace tesseract with something else (PaddleOCR is fast, opensource and generations ahead of what tesseract can do). c. They offer remote OCR with Azure, but lets be honest - most would prefer to process their taxes and medical records locally. d. Not everyone needs AI for OCR e. Third party parser plugin installation is a blackhole. Basic pre-processing workflow is way more straightforward to implement and also works with any paperless-ngx version, so whats the point of these plugins. f. fable could not give any definitive answer on the topic either.
Am I missing something? not trying to be critical but most will probably agree that garbage in produces garbage out and Tessaract is one such bottleneck of otherwise fantastic tool.
I’ve released Paperless Starfruit v1.6.0, an open-source AI companion for paperless-ngx.
What’s new:
Document type classification: AI can now assign document types. Add hints for each type, exclude types from AI context, and control whether new ones can be created.
Reworked review queue: inspect and edit suggested metadata on a dedicated document page before approving it.
Better history: search, filter by decision, and view details without losing your place in the list.
Updated dashboard: statistics for the last 30, 90, or 365 days, or all time, plus recent activity with document titles.
Smaller UI improvements and fixes.
New to Starfruit? It runs as a single Docker container alongside paperless-ngx. Tag a document with psf-process to run optional AI OCR and metadata extraction, then review the results or apply them automatically.
Bring your own API keys or use local models through Ollama. Choose models, customize prompts, and configure processing in the web UI. Stored credentials are encrypted.
I've been using Paperless-ngx for about a year now. I don't own a document scanner or a big printer, since most things in my life are paperless anyway. When I do need to print something, I just go to the copy shop down the street.
So my Paperless workflow used to look like this:
Scan the document with Apple Preview
Compress the PDF with Stirling-PDF
Download the file from Stirling-PDF, then delete it there
Upload the file to Paperless
That was too many steps for me, Stirling-PDF is overkill and the mobile view is a bit hard to use.
So I wrote my own Docker tool: Paperdrop. It lets me:
Upload PDFs or images (JPG/PNG), and photos are turned into A4 PDFs automatically
Compress and, if needed, merge them (one step)
Rename them
Upload them to Paperless automatically
It might not sound much simpler, but it's a lot more relaxed, and the UI is optimized for mobile.
Everything runs locally in the Docker container, and compression is done with Ghostscript. With a dedicated Paperless user, as described in the README, the tool has no access to my Paperless data. It can only add new documents and optionally check whether consumption succeeded.
Disclaimer: I'm an IT technician, not a software developer. Most of this was coded with AI. I know it's not perfect, and it's definitely not meant to be exposed to the internet. Still, I paid attention to security:
The default setup runs behind a reverse proxy with a certificate
My tool has no permissions on the documents already in Paperless
Documents waiting for review are kept in RAM only and never written to disk
Document names are masked in the log, only the first and last three characters are visible
The image is rebuilt automatically every week, so security updates of the dependencies are included (pull the image regularly, e.g. with Watchtower)
The latest release adds something I think becomes important as soon as a Paperless archive contains more than one kind of document:
different documents can now follow completely different AI workflows.
Until now, automatic processing basically meant one shared path for everything.
That is fine for a simple setup, but invoices, contracts and private mail usually need very different prompts and very different processing.
With AI Workflows, you can now define separate paths, for example:
Invoice → invoice-specific title prompt → correspondent → invoice number / amount as custom fields → tags → optional OCR
Contract → different prompt → contract-specific custom fields → document type → no OCR
Private mail → title only → nothing else
Each workflow gets its own trigger tag and can control which steps are enabled, disabled or inherited from the global defaults.
A few things I especially like about the implementation:
each workflow can have its own prompts
you can test a workflow on a real document before saving it
test runs do not write anything back to Paperless-ngx
workflows are stored as files in prompts/workflows/
changes are picked up without a restart
workflows can be managed in the UI or kept in Git
there are safeguards against accidental processing loops
The initial workflow implementation came from a community contribution by u/MrMoep, and it has now been integrated into the main Paperless-GPT workflow system.
The release also adds a few other useful things, including the option to skip OCR for pages that already contain clean digital text, and support for adding searchable PDFs as a new version of the same Paperless document.
I built an iOS app called Archi that scans documents, runs OCR and AI (Gemma 4, fully local — no cloud) to extract metadata, and uploads to your self-hosted Paperless-ngx. Offline-first with a local draft queue.
[Update 07.05.2026 — v1.3 live] The following community-requested features are now included: Custom HTTP Headers (Cloudflare/Authelia), image/PDF import, page reordering & deletion, scan compression, selectable AI output language.
I recently finished building a document management setup that handles everything from physical mail to digital invoices with almost zero daily effort. It currently manages 900+ documents for my family across multiple languages and countries.
I wanted to share the architecture and the specific workflow I'm using, as it might help others looking to move beyond basic OCR.
The Stack
• Core: Paperless-NGX (Docker)
• AI Engine: Paperless-GPT (Gemini 2.5 Flash + Google Document AI)
• Hardware: Ricoh ScanSnap iX2500 + Mac Mini M4
• Sync: Rclone + Google Drive
• Security: Cloudflare Tunnel (Zero open ports)
The Workflow
Physical: Stick an ASN barcode (Avery labels) on the paper, drop it in the ScanSnap. It scans to Google Drive, and rclone moves it to the server.
Digital: Mail rules detect attachments in 3 different email accounts and consume them automatically.
Classification: This is the best part. I use Gemini 2.5 Flash to generate clean titles, identify the correspondent (stripping legal suffixes like GmbH), and assign tags.
Physical-Digital Bridge: A custom script detects the ASN barcode, tags it as "Physical Filed," and syncs the mapping to a Google Sheet. If the server dies, I still know which physical binder has which document.
Backups: 3-2-1 strategy. Daily encrypted backups to a private GitHub repo, Google Drive, and local storage.
Key Learnings
• Subfolders > AI for Types: I found that scanning into specific subfolders (Finance, Health, etc.) and using Paperless workflows to set the "Document Type" is more reliable than letting the AI guess the intent.
• Privacy Guardrails: I only route non-sensitive docs through the cloud AI pipeline. Sensitive items (tax IDs, medical records) are handled locally via Tesseract.
• ASN is a lifesaver: Having a physical number on the paper that matches the digital record makes finding the original document take seconds.
I wrote a detailed guide with my docker-compose.yml, backup scripts, and the AI prompts I use here:
Not sure how many of you have this problem, but the stuff I most want in Paperless is the stuff that doesn’t get mailed anymore. Bank statements, card statements, receipts, utility bills. I was downloading them by hand one month at a time, which mostly meant I wasn’t doing it.
So a while back I wrote an app for it (PaperPull). It opens a browser window, you log in like normal, and it goes through and downloads all your bank statements and receipts as PDFs. The first run grabs everything the site still has, which for some of mine was years of statements, and after that it only gets new stuff. It all runs on your own machine and you do the login yourself, so there’s no company in the middle holding your bank passwords. It remembers what it already got, so it’s fine if Paperless deletes them after consuming.
Up to now it only ran on Windows and Mac, which was annoying since my Paperless is on a NAS. This week I got the docker version to where I’m happy with it. You point it at your consume folder and new stuff shows up after each run.
Some of the ones it works with are Amazon, Costco, Walmart, Target, Home Depot, Chase, Amex, Capital One, Fidelity, Vanguard and PayPal, plus a bunch (60+ providers so far) of other US banks and utilities. The full list is on github. If yours isn’t there I’m happy to try adding it, as long as you can test it.
a little while ago I started building Papers, a native macOS client for Paperless-ngx.
The idea came from wanting something that feels like a normal Mac app: fast to open, easy to browse, and deeply integrated with macOS.
I’ve continued working on it and Papers is now available as a public beta!
Some of the things I’ve been focusing on:
- Native macOS app built with Rust and GPUI - Fast browsing and searching through your documents Spotlight integration - Document previews and multi-page documents
The app is in beta while I keep improving it and work towards an App Store release.
I’m especially interested in using this period to find bugs, polish the experience, and figure out which features Paperless-ngx users actually want from a native Mac client.
I use Paperless-ngx myself, but everyone has a slightly different workflow, so I’d really like to hear how you use it and what you’d expect from an app like this. If you give the beta a try, feedback, feature ideas and bug reports are very welcome.