18. Labels
Install LabelPlugin after RestApiPlugin and before adding resources:
import { LabelPlugin } from 'json-rest-api'
await api.use(LabelPlugin, { preferNameFields: ['name', 'title'] })
It adds a computed string label to resources that do not already declare one.
The label is derived during reads and is not a stored column. GET, query, full
write responses and included resources use the same computation in both plain
and JSON:API formats.
Candidates are derived during schema compilation, after attribute and search
enrichment. Canonical addKnexFields rebuilds them from the new configuration,
so a field addition retains the label and an added preferred/global-search
source becomes eligible. Reads and descriptor-only refreshes reuse the compiled
candidates. Ordinary Knex field-addition helpers perform DDL only; changing
their runtime declarations still requires resource initialization.
The plugin considers, in order:
- The first global-search field that resolves to a public stored field on this
resource. A search alias uses its
actualField; a joined search path is not a local label source. - String fields listed in
preferNameFields, in the configured order. - The first public stored string field in the schema.
- The resource’s logical ID.
Null and undefined values fall through to the next candidate. Empty strings, zero and false are retained and converted to strings. Getters run before label computation. Hidden, normally hidden and virtual fields, belongs-to foreign keys and polymorphic backing fields are not automatic label sources. This keeps the default label from exposing private values or depending on a transient write input.
Sparse fieldsets can select just the label:
await api.resources.books.query({
format: 'jsonapi',
queryParams: { fields: { books: 'label' } }
})
The existing computed-field dependency mechanism fetches the candidate values
and removes unrequested dependencies from the result. Null fallbacks therefore
work with sparse fieldsets too. Pass { disable: true } when installing the
plugin to disable automatic labels.
Authored labels and record identity
An explicitly declared stored or computed label remains authoritative. The
plugin does not remove its schema definition or replace its computation. For
example, a resource may declare:
label: {
type: 'string',
computed: true,
dependencies: ['title'],
compute: ({ id, attributes }) => `${id}: ${attributes.title ?? ''}`
}
Computed callbacks receive the current resource’s public logical id separately
from attributes. This also works for custom idProperty names, collections
and included resources; the ID is not taken from the parent request. Direct
callers of enrichAttributes can supply its id argument explicitly.
Computed fields honor hidden and normallyHidden like stored fields: hidden
fields are omitted, and normally hidden fields appear only when requested.
Authors who intentionally derive a public value from normally hidden dependencies should
declare that computed field explicitly and review the information it returns.
The shared label suite (source checkout: tests/conformance-labels.test.js) checks these
contracts on regular and canonical storage. The
migration guide
describes corrections from the earlier behavior.