Query Result Property

What It Is

A Query result property (pQueryResult, in the Special property family) turns a saved query into a live field on a record. Instead of storing a value a user types, it stores a reference to a Query and, at display time, runs that query in the context of the record it lives on. The output can be a count, a list of matched record IDs, a list of titles, or a list of clickable links — and the mask decides which.

Use it when a record should show something about other records that relate to it: "how many orders does this customer have", "list the open tasks for this project", "sum of invoice lines on this order".

Why It Matters

Without this property you would model the same information as a calculation, an automation that writes a counter, or a custom canvas. Those all duplicate logic and can drift out of date. A Query result property has three advantages:

  • It is always current, because it runs the query when the value is read.
  • It reuses an existing query, so the selection logic lives in one place.
  • It can be counted, listed, or linked from the same definition just by changing the render mode in the mask.

It is the lightweight way to surface related data on a record without building a view or a canvas.

Mental Model

Keep three things separate:

  1. The bound querywhich records are matched. This is the property's value: a reference to a Query.
  2. The context — the query runs scoped to the record the property is on. Inside the query's filters, {$this} refers to that owning record, so a filter like {$this.id} matches "rows that point back to me". This is what makes "count my related records" work.
  3. The render modehow the matched records are turned into the displayed value. This is controlled entirely by the mask.

The first two are query design (see Queries and Query Parameters). The third — the mask — is what the rest of this page is about.

Binding The Query

The property's stored value is the query to run. You have two ways to set it:

  • Fixed for every record — set default_value to the target query. Every record runs the same query. This is the common case.
  • Chosen per record — leave the value editable so users pick which query a given record should run. The field renders as a select of available queries.

If no value and no default_value resolve to a query, the property renders nothing.

The Mask: Render Modes And Formatting

For most property types mask is just a display-formatting expression. For a Query result property the mask does double duty: its first token selects a render mode, and anything after it is an optional formatting expression. This is the part that is easy to get wrong, so the rules below are exact.

Mask Grammar

[render:MODE] [| formatting-expression]
  • render:MODE is an optional keyword that must be at the very start of the mask.
  • A pipe | separates the keyword from an optional formatting expression.
  • A mask with no render: keyword (including an empty mask) is treated as count mode, and the whole mask is used as the formatting expression.

The Four Render Modes

KeywordWhat it produces (no formatting expression)Iterates
render:count (default)A single number: the count of matched records, or the query's calculation result (see below).once
render:rawThe id of each matched record.per record
render:plainThe title of each matched record, as plain text.per record
render:fullThe title of each matched record, as a clickable link to that record.per record

If you omit the keyword entirely, you get render:count.

Rule 1 — The keyword must come first

The render mode is recognized only at the start of the mask string. render:full works; count of {$name}: render:full does not — the keyword in the middle is ignored and the whole string is treated as a count-mode formatting expression. Only the four keywords above are recognized.

Rule 2 — self is the value being formatted

Inside the formatting expression, self is bound to the value this property is currently producing:

  • In count mode, self is the number (count or calculation result).
  • In raw / plain / full mode, self is the matched record, and the expression runs once per record.

Everything else in the expression resolves against the owning record (the record the property lives on), because that record is in scope. So use self to reach the matched record or count, and {$...} / {{...}} to reach the owning record's own properties.

Rule 3 — A formatting expression replaces the mode's default output

This is the subtle one. The built-in output of each mode (the number, the ID, the title, the link) is used only when you do not supply a formatting expression. The moment you add | expression, your expression's result becomes the output and the built-in rendering is discarded.

A practical consequence: once you add an expression, render:raw, render:plain, and render:full behave identically — they all just run your expression once per matched record with self set to that record. Pick whichever reads clearly; the difference between them only matters when the mask is just the keyword with no expression. If you want a link, build it in the expression yourself (the matched record is available as self).

Rule 4 — self is tag-stripped

The value bound to self has its HTML tags stripped and escaped before the expression runs. You cannot smuggle markup in through self. If you need HTML output (a badge, a link, an icon), build it in the expression body, not via self.

Rule 5 — Multiple records are joined

In raw / plain / full mode the per-record results are concatenated using the property's multiple_separator. If that is empty, the tenant's default item separator is used. Count mode produces a single value and is not joined.

Rule 6 — Count mode respects the query's calculation

If the bound query defines a calculation (for example a sum or a distinct count), render:count returns that calculation's result, not a plain row count. If the query has no calculation, you get the number of matched records. This lets the same property show "number of invoice lines" or "total amount" purely by how the query is built.

Examples

All examples assume the bound query selects records related to the owning record (for example, orders whose customer is {$this.id}).

Plain count — leave the mask empty, or:

render:count

Count with a label — count mode, formatting expression applied to the number:

{= self } open item(s)

(No keyword needed — bare expressions are count mode. self is the number.)

Count, but show the query's total instead — give the bound query a sum calculation and keep:

render:count

List titles — comma-separated record titles (set multiple_separator to , ):

render:plain

List as links — clickable links to each matched record:

render:full

Custom per-record output — list a different property of each matched record:

render:plain|{$self.invoice_number}

Here self is the matched record, so {$self.invoice_number} reads that record's field. The mode keyword could be raw, plain, or full interchangeably because the expression supplies the output.

Performance Note

Count mode over the common "count my related records" pattern is optimized: when a list page shows many records each carrying the same count-style Query result property, the platform computes those counts in one grouped query for the whole page rather than running the full query per row. The list-friendly path covers render:count (and the empty/default mask). The raw, plain, and full modes, and count masks with formatting expressions, evaluate per record. Prefer render:count for columns shown across large lists, and reserve the per-record modes for detail views or smaller result sets.

Exports (for example XLSX) always render the matched records as plain text titles, regardless of the configured mode.

Best Practices

  • Build the relationship logic in the query, not the mask. The mask should only decide presentation.
  • Use render:count for list columns and dashboards; use render:full on detail views where users will want to navigate to the related records.
  • Keep formatting expressions short. If a mask expression grows complex, reconsider whether a dedicated view or canvas is the better surface.
  • Remember that a formatting expression overrides the mode's default rendering — do not expect render:full|... to still produce a link unless your expression builds one.
  • Set multiple_separator deliberately for the per-record modes so the joined output reads well on the target surface.