Skip to content

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.

- save

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.

- stop

- stop: "Reason string shown in execution trace"

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.

- set_cf: "Invoice Number"
  value: "{{ invoice_no }}"

- set_cf: "Amount"
  value: "{{ amount }}"

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:

- match: '^(\d{4})/'
  variable: date_string
  replace: "{{ m1 }}"
  as: year

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.

- set_variable: status
  value: "processed"

- set_variable: counter
  value: 0

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 to limit)
- 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:

  • save is a no-op — field changes are recorded in the trace but not sent to Paperless-ngx
  • note is a no-op
  • All other actions (match, set_variable, lookup, if, …) execute normally so you can inspect the full trace