Query Language🔗

Docspell uses a query language to provide a powerful way to search for your documents. It currently needs to be enabled explicitely in your user settings to be used on the search page.

This changes the search bar on the items list page to expect a query as described below.

The search menu works as before, the query coming from the search menu is combined with a query from the search bar.

For taking a quick look, head over to the examples.


The overall query is an expression that evaluates to true or false when applied to an item and so selects whether to include it in the results or not. It consists of smaller expressions that can be combined via the common ways: and, or and not.

Simple expressions check some property of an item. The form is:


For example: tag=invoice – where tag is the field, = the operator and invoice the value. It would evaluate to true if the item has a tag with name invoice and to false if the item doesn't have a tag with name invoice.

Multiple expressions are separated by whitespace and are combined via AND by default. To explicitely combine them, wrap a list of expressions into one of these:

  • (& … ) to combine them via AND
  • (| … ) to combine them via OR

It is also possible to negate an expression, by prefixing it with a !; for example !tag=invoice.

The Parts🔗


There are 7 operators:

  • = for equals
  • > for greater-than
  • >= for greater-equals
  • ~= for "in" (a shorter way to say "a or b or c or d")
  • : for "like", this is used in a context-sensitive way
  • < for lower than
  • <= for lower-equal
  • != for not-equals

Not all operators work with every field.


Fields are used to identify a property of an item. They also define what operators are allowed. There are fields where an item can have at most one value (like name or notes) and there are fields where an item can have multiple values (like tag). At last there are special fields that are either implemented directly using custom sql or that are only shortcuts to a longer form.

Here is the list of all available fields.

These fields map to at most one value:

  • name the item name
  • source the source used for uploading
  • notes the item notes
  • id the item id
  • date the item date
  • due the due date of the item
  • created the date when the item was created
  • corr.org.id the id of the correspondent organization
  • corr.org.name the name of the correspondent organization
  • corr.pers.name name of correspondent person
  • corr.pers.id id of correspondent person
  • conc.pers.name name of concerning person
  • conc.pers.id id of concerning person
  • conc.equip.name name of equipment
  • conc.equip.id id of equipment
  • folder.id id of a folder
  • folder name of a folder
  • inbox whether to return "new" items (boolean)
  • incoming whether to return incoming items (boolean), true to show only incoming, false to show only outgoing.

These fields support all operators, except incoming and inbox which expect boolean values and for those some operators don't make sense.

Fields that map to more than one value:

  • tag the tag name
  • tag.id the tag id
  • cat name of the tag category

The tag and category fields use two operators: : and =.

Other special fields:

  • attach.id references the id of an attachment
  • checksum references the sha256 checksum of a file
  • content for fulltext search
  • f for referencing custom fields by name
  • f.id for referencing custom fields by their id
  • dateIn a shortcut for a range search
  • dueIn a shortcut for a range search
  • createdIn a shortcut for a range search
  • exist check if some porperty exists
  • names a shortcut to search in several names via :
  • year a shortcut for a year range
  • conc a shortcut for concerning person and equipment names
  • corr a shortcut for correspondent org and person names

These fields are often using the : operator to simply separate field and value. They are often backed by a custom implementation, or they are shortcuts for a longer query.


Values are the data you want to search for. There are different kinds of that, too: there are text values, numbers, boolean and dates. When multiple values are allowed, they must be separated by comma ,.

Text Values🔗

Text values need to be put in quotes (") if they contain one of these characters:

  • whitespace
  • quotes "
  • backslash \
  • comma ,
  • brackets []
  • parens ()

Any quotes inside a quoted string must be escaped with a backslash.

Examples: scan_123, a-b-c, x.y.z, "scan from today", "a \"strange\" name.pdf"

Numeric and Boolean Values🔗

Numeric values can be entered literally; an optional fraction part is separetd by a dot. Examples: 1, 2.15.

A boolean value can be specfied by yes or true and no or false, respectively. Example: inbox:yes


Dates are always treated as local dates and can be entered in multiple ways.

Date Pattern🔗

They can be in the following form: YYYY-MM-DD or YYYY/MM/DD. The month and day part are optional; if they are missing they are filled automatically with a 1. So 2020-01 would be the same as 2020-01-01.

A special pattern is today which marks the current day.

Unix Epoch🔗

Dates can be given in milliseconds from unix epoch. Then it must be prefixed by ms. The time part is ignored. Examples: ms1615209591627.


Dates can be defined by providing a base date via the forms above and a period to add or substract. This is especially useful with the today pattern. The period must be separated from the date by a semi-colon ;. Then write a + or a - to add or substract and at last the number of days (suffix d) or months (suffix m).

Examples: today;-14d, 2020-02;+1m

Simple Expressions🔗

Simple expressions are made up of a field with at most one value, an operator and one or more values. These fields support all operators, except for boolean fields.

The like operator : can be used with all values, but makes only sense for text values. It allows to do a substring search for a field.

For example, this looks for an item with a name of exactly 'invoice_22':


By using :, it is possible to look for items that have 'invoice' somewhere in their name:


The asterisk * can be added at the beginning and/or end of the value, but not in betwee. Furthermore, the like operator is case-insensitive, whereas = is not. This applies to all fields with a text value.

This is another example looking for a correspondent person of with 'marcus' in the name:


Comparisons via </> are done alphanumerically for text based values and numerically for numeric values. For booleans these operators don't make sense and therefore don't work there.

All these fields (except boolean fields) allow to use the in-operator, ~=. This is a more efficient form to specify a list of alternative values for the same field. It is logically the same as combining multiple expressions with OR. For example:


is the same as

(| source=webapp source=mailbox )

The ~= version is nicer to read, safes some key strokes and also runs more efficient when the list grows. It is not possible to use a wildcard * here. If a wildcard is required, you need to write the longer form.

If one value contains whitespace or other characters that require quoting, each value must be quoted, not the whole list. So this is correct:

source~="web app","mail box"

This is not correct: source~="web app,mail box" – it would be treated as one single value and is then essentially the same as using =.

The two fields incoming and inbox expect a boolean value: one of true or false. The synonyms yes and no can also be used to make it better readable.

This finds all items that have not been confirmed:


The incoming can be used to show only incoming or only outgoing documents:


For outgoing, you need to say:



Tags have their own syntax, because they can appear multiple times on an item. Tags only allow for two operators: = and :. Combined with negation (the ! operator), this is quite flexible.

For tags, = means that items must have all specified tags (or more), while : means that items must have at least one of the specified tags. Tags can be identified by their name or id and are given as a comma separated list (just like when using the in-operator).

Some examples: Find all invoices that are todo:


This returns all items that have both tags invoice and todo. Negating this:


… results in an expression that returns all items that don't have both tags. It might return items with tag invoice and also items with tag todo, but no items that have both of them.

Using : is just analog to =. This finds all items that are either waiting or todo (or both):


When negating this:


it finds all items that have none of the tags.

Tag names are always compared case-insensitive. Tags can also be selected using their id, then the field name tag.id must be used instead of tag.

The field cat can be used the same way to search for tag categories.

Custom Fields🔗

Custom fields can be used via the following syntax:


They look almost like a simple expression, only prefixed with a f: to indicate that the following is the name of a custom field.

The type of a custom field is honored. So if you have a money or numeric type, comparsions are done numerically. Otherwise a alphnumeric comparison is performed. Custom fields do not support the in-operator (~=). Date fields are an exception: they are always compared alphanumerically and thus the today special value doesn't work with them.

For example: assuming there is a custom field of type money and name usd, the following selects all items with an amount between 10 and 150:

f:usd>10 f:usd<150

The like-operator can be used, too. For example, to find all items that have a custom field asn (often used for a serial number printed on the document):


If the like operator is used on numeric fields, it falls back to text-comparison.

Instead of using the name, the field-id can be used to select a field. Then the prefix is f.id:


Fulltext Search🔗

The special field content allows to add a fulltext search. Using this is currently restricted: it must occur in the root (AND) query and cannot be nested in other complex expressions.

The form is:

content:<your search query>

The search query is interpreted by the fulltext index (currently it is SOLR). This is usually very powerful and in many cases this value must be quoted.

For example, do a fulltext search for 'red needle':

content:"red needle"

It can be combined in an AND expression:

content:"red needle" tag:todo

But it can't be combined via OR. This is not possible:

tag:todo (| content:"red needle" tag:waiting)

File Checksums🔗

The checksum field can be used to look for items that have a certain file attached. It expects a SHA256 string.

For example, this is the sha256 checksum of some file on the hard disk: 40675c22ab035b8a4ffe760732b65e5f1d452c59b44d3d0a2a08a95d28853497.

To find all items that have (exactly) this file attached:



The exist field can be used with another field, to check whether an item has some value for it. It only works for fields that have at most one value.

For example, it could be used to find items that are in any folder:


When negating, it finds all items that are not in a folder:



The attach.id field is a special field to find items by providing the id of an attachment. This can be helpful in certain situations when you only have the id or part of that of an attachment. It uses equality if no wildcard is present. A wildcard * can be used at beginning or end if only a part of the id is known.



Shortcuts are only a short form of a longer query and are provided for convenience. The following exist:

  • dateIn, dueIn and createdIn
  • year
  • names
  • conc
  • corr

Date Ranges🔗

The first three are all short forms to specify a range search. With dateIn and dueIn have three forms that are translated into a range search:

  • dateIn:2020-01;+15ddate>=2020-01 date<2020-01;+15d
  • dateIn:2020-01;-15ddate>=2020-01;-15d date<2020-01
  • dateIn:2020-01;/15ddate>=2020-01;-15d date<2020-01;+15d

The syntax is the same as defining a date by adding a period to some base date. These two dates are used to expand the form into a range search. There is an additional / character to allow to subtract and add the period.

The year is almost the same thing, only a lot shorter to write. It expands into a range search (only for the item date!) that selects all items with a date in the specified year:

  • year:2020date>=2020-01-01 date<2021-01-01

The last shortcut is names. It allows to search in many "names" of related entities at once:


  • names:tim(| name:tim corr.org.name:tim corr.pers.name:tim conc.pers.name:tim conc.equip.name:tim )

The names field uses the like-operator.

The fields conc and corr are analog to names, only that they look into correspondent names and concerning names.

  • conc:marc*(| conc.pers.name:marc* conc.equip.name:marc* )
  • corr:marc*(| corr.org.name:marc* corr.pers.name:marc* )


Find items with at least one tag invoice or todo that are due next:

tag:invoice,todo due>today

Find items with at least both tags invoice and todo:


Find items with a concerning person of name starting with "Marcus":


Find items with at least a tag "todo" in year 2020:

tag:todo year:2020

Find items within the last 30 days:


Find items with a custom field paid set to any value:


Find items that have been paid with more than $100 (using custom fields paid as a date and usd as money):

f:paid:* f:usd>100