Actions
Actions are executed in order when all filters and conditions pass. Changes to the document are only written to Paperless-ngx when a save action is reached.
save
Persists all pending document field changes via the Paperless-ngx API. Respects dry-run mode — no changes are written when dry-run is active.
Warning
Changes accumulate in memory until save is called. A rule that modifies fields but never calls save will have no visible effect on the document.
stop
Aborts rule processing immediately. All subsequent actions in the list are not executed. Useful for early exits.
set_field
Sets a document field to a value. Classifier fields (correspondent, document_type, storage_path) are automatically resolved from name to Paperless-ngx ID.
- set_field: title
value: "{{ year }}/{{ month }}"
- set_field: correspondent
value: "ACME GmbH" # auto-resolved to the correspondent's ID
- set_field: document_type
value: "Invoice"
- set_field: storage_path
value: "archive/invoices"
- set_field: archive_serial_number
value: "2024-001"
Allowed fields
title, content, correspondent, document_type, storage_path, archive_serial_number, mime_type, original_file_name, owner, page_count
add_tag / del_tag
Adds or removes one or more tags. Tag names are automatically resolved to their Paperless-ngx IDs. Both operations are idempotent.
# Single tag
- add_tag: Inbox
# Multiple tags at once
- add_tag: [Inbox, ToReview]
# Remove a tag
- del_tag: Inbox
set_cf
Writes a value to a Paperless-ngx Custom Field. The field is referenced by name. Type conversion is automatic.
note
Appends a processing note to the document's notes field. Notes are accumulated under the configured header (SECRETARY_PAPERLESS__NOTE_HEADER) and written when save is called. Respects dry-run mode.
- note: "Title could not be determined automatically."
- note: "Processed by rule {{ rule.id }} on {{ now() }}."
match
Applies a regular expression to a document field or a scope variable. Capture groups are available as m1, m2, … in the replace template. The result is stored under the variable name given by as.
If the pattern does not match, the as variable is left undefined.
- match: 'Invoice No\.\s+(\d+)'
field: content
replace: "{{ m1 }}"
as: invoice_number
- match: 'dated (\d{4})-(\d{2})-(\d{2})'
field: content
replace: "{{ m1 }}/{{ m2 }}"
as: date_string
You can also match against a scope variable:
Note
Either field or variable must be set — not both.
set_variable
Sets an arbitrary Jinja2 scope variable for use in later actions or conditions.
lookup
Searches Paperless-ngx for documents matching a query and stores the result in a scope variable. The result object has two attributes:
.count— total number of matching documents.items— list of document objects (up tolimit)
- lookup:
query:
correspondent: "ACME GmbH"
tags: [Invoice]
created_after: "2024-01-01"
as: found_docs
limit: 25
Query parameters
| Parameter | Description |
|---|---|
correspondent |
Filter by correspondent name or ID |
document_type |
Filter by document type name or ID |
storage_path |
Filter by storage path name or ID |
tags |
List of tag names or IDs (all must match) |
query |
Full-text search query |
title |
Title substring filter |
created_after |
ISO date string — documents created after this date |
created_before |
ISO date string — documents created before this date |
ordering |
Field name to sort by, prefix with - for descending |
After lookup, use the result in conditions or subsequent actions:
- if: "{{ found_docs.count > 0 }}"
actions:
- note: "Found {{ found_docs.count }} related documents."
if / else
Conditional branching. The conditions block follows the same syntax as top-level conditions. The else block is optional.
- if:
conditions:
- variable: invoice_number
exists:
actions:
- set_field: title
value: "Invoice {{ invoice_number }}"
else:
- add_tag: "Problem"
- note: "Invoice number not found."
Shorthand form
When the condition is a single Jinja2 template expression, you can use the shorthand:
- if: "{{ invoice_number is defined }}"
actions:
- set_field: title
value: "Invoice {{ invoice_number }}"
else:
- add_tag: "Problem"
Nesting
if actions can be nested to any depth. Each level has its own actions and optional else block.
Execution order and dry-run
Actions are executed sequentially. If an action raises an error (e.g. a Paperless API lookup fails), execution stops at that point.
In dry-run mode:
saveis a no-op — field changes are recorded in the trace but not sent to Paperless-ngxnoteis a no-op- All other actions (match, set_variable, lookup, if, …) execute normally so you can inspect the full trace