Linked mode
Store preset references instead of resolved values, so palette changes propagate everywhere.
By default, picking a preset stores its literal value; documents keep the color they were saved with. Linked mode inverts that: picking a preset stores a reference, and the color resolves at read time. Change the preset's value once and every document using it updates on next read.
fields: [
...colorField({
name: 'accent',
linked: { fallback: '#64748b' },
presets: async ({ req }) => resolveGovernedPalette(req),
}),
]Linked mode returns a tuple, the stored field plus its resolved virtual sibling, so spread it into fields.
What gets stored
- Picking a preset stores
preset:<key>(for examplepreset:brand). - Picking a custom color stores the plain CSS string, exactly like default mode.
Both live in the same text column, so where: { accent: { equals: 'preset:brand' } } finds every document linked to a preset, which makes "where is this brand color used" a one-query answer.
The resolved sibling
The factory adds a hidden virtual sibling <name>Resolved populated on read with the resolved CSS value:
const doc = await payload.findByID({ collection: 'pages', id })
doc.accent // 'preset:brand'
doc.accentResolved // '#3b82f6'Resolution deliberately happens on the sibling, not in place. Resolving in place would feed the resolved value back through the admin form and silently overwrite the reference on the next save; the sibling keeps the stored field round-trip safe. Frontends read <name>Resolved; the admin and queries work with the stored reference.
Because the async preset resolver is memoized per request and shared across every document, it derives from req and returns the full governed palette (keyed by preset key); each document's stored reference selects its entry. Resolving a list view costs one palette lookup.
Missing presets
When a stored reference points at a preset that no longer exists (a tenant deleted a brand color), the sibling resolves to linked.fallback (default null), and the picker shows a missing-preset state prompting for a replacement. Nothing throws, and the stale reference stays queryable until an editor replaces it.
Limitations
The resolved sibling is derived at read time from the stored reference on the same record, which has two edges worth knowing:
- Reading a localized linked field with
locale=allreturnsnullfor${name}Resolved. The sibling resolves one reference, not the per-locale map thatlocale=allreturns, so per-locale resolved values are not represented. Read a single locale when you need the resolved value. - Selecting
${name}Resolvedwithout also selecting the main field returnsnull, because resolution reads the main field's stored reference from the same record. Co-select the main field (for exampleselect: { accent: true, accentResolved: true }).
When to use which mode
Use linked mode when colors are governed (tenant brands, design tokens) and should update globally. Use default mode when a document's color is that document's own data. The two can coexist: linked fields still allow custom one-off values, which store as plain strings.