ZuloOne
English
Sign in
Menu

Document references and labels

Updated

A saved document can appear in places that were not modelled as a typed document reference: register sources, report group keys, settings payloads, custom form widgets, or a generic Guid column. Those values read like documents when they really are document row ids, without adding a new field kind to the metadata system.

← Developer wiki

Why there is no new field kind#

The storage invariant did not change: a reference-like value in ZuloOne is still a bare Guid. A document row lives in the table of its document type, and the key alone does not name that table. Adding a DocumentReference EDT would have solved only future hand-authored fields and would have left the important cases unfixed: report cells, register sources and old custom payloads that already carry document ids.

Instead the renderer treats every non-empty Guid as a candidate. If the server can prove that the id belongs to a readable document, the UI shows a document label and can open the card. If it cannot, the original value remains visible. That fallback is important: a wrong label is worse than a raw id.

How a label is built#

POST /api/documents/resolve-labels accepts a batch of ids and returns only the ones it can name for the caller — documents first, then dictionary records and metadata ids:

{ "ids": ["7d8b4b7a-7f6b-4d2b-9a9e-1b0d5b84e6aa"] }

Each result includes the document row id, the owning document type id, the localized document type caption, the human number and the document date. The frontend DocumentReference renders that compactly as:

Sales realization № 100045 · 2026-10-01     a document row
Zhytomyr Corner Shop                    a dictionary record (its display format)
Sales realization                           a metadata id (the object type's caption)

The candidates are every GUID-valued field on the card — Guid fields and also String/textarea fields whose value is a GUID. The Tax model is the case that forced the widening: TaxDocument.SourceDocumentId (a document), SourceDocumentType (a document TYPE id, stored as text), TaxSubmission.SourceId (a tax return) and TaxMapping.SourceId (a customer) would otherwise show raw ids.

Editable cards use the same resolved text as the field value. They do not show a second raw GUID input next to a successful label: the raw id is behind the small Edit raw id affordance. Press it when you need to paste or repair the stored id; Esc or leaving the raw input returns to the label view. If the value does not resolve — unknown id, unreadable object or a non-GUID string — the renderer keeps the ordinary text input so the stored value stays visible and editable. Document labels remain links and open the referenced document card; dictionary record and metadata labels are plain text.

The shared DocumentLabelResolver also serves report code. Its full formed name has more context because grouped reports need it: document type caption, localized subtype caption when present, ID (falling back to Number) and DocumentDate. That format stays server-side so reports and labels do not grow separate rules for the same document identity.

Permission stance#

The endpoint is not a privileged id oracle. For every candidate document type it checks the same Read permission as opening the document card. Unknown ids, ids from unreadable document types and non-document ids are omitted from the response, not reported as a distinguishable error. A mixed batch therefore gives usable labels for allowed documents without teaching the browser which forbidden document types exist.

Batching and ownership lookup#

The client deduplicates and sorts ids before calling the endpoint, and caches the answer briefly. The server has no central "document row id → document type" index, so it walks readable document types. The important performance rule is one query per type over the still-unresolved ids, chunked at 500 ids, not one round trip per grid cell. The walk stops as soon as every id has an owner.

What a metadata author should do#

Nothing. Do not add a new EDT, do not mark a form field, and do not wrap values in a { type, id } object. Any Guid value that is the MetaId of a document row can be labelled by the UI. A form layout author only decides where the field or widget appears; the label resolver is platform behaviour.

Traps#

  • Do not infer type from the field. Generic report cells and register source ids often have no field metadata at all. The resolver must discover ownership from the stored row id.
  • Do not turn an omitted result into "not found" UI. Omitted also means "not allowed" or "not a document". Keep the raw value.
  • Do not add per-cell calls from a grid. The endpoint exists because a report page can carry hundreds of document ids.
  • A GUID-looking string is still just a string. External ids, UUIDs from a tax authority or a package id stay raw because the resolver finds no owner — never because the UI guessed. Do not add "looks like a GUID" heuristics beyond asking the resolver.

What's not there yet#

  • There is no metadata-declared DocumentReference field kind.
  • The endpoint does not expose subtype captions to the frontend compact label; report labels still have subtype context through DocumentLabelResolver.
  • Typed dictionary references keep their own picker controls; the resolver only labels GUIDs that are not already a typed reference.
  • Ids that point to something other than a document, a dictionary record or a metadata object (for example a register row) stay raw.

Where next#

Was this page helpful?