Targeting
How a guide declares which collections, globals, fields, blocks, and custom surfaces it explains.
A guide stores what it is about in string lists, and every surface in the admin resolves itself against them.
The target kinds
The Targets tab of a wiki page holds one list per kind:
| Field | Kind | Value format | Example | Entered by |
|---|---|---|---|---|
targetCollections | Collection | Bare slug | posts | Picking from the collections the plugin covers |
targetGlobals | Global | Bare slug | settings | Picking from the globals the plugin covers |
targetFields | Field | Owner-qualified schema path | collection:posts.hero.title | Picking fields from a rendered form, typing a path, or a write affordance |
targetBlocks | Block | Bare block slug | heroBanner | Picking from the blocks the plugin covers |
targetCustom | Custom | Bare declared key | dashboard | Picking from the keys you declared. Only present when you declared any |
One guide may fill all of them, and any number of guides may name the same target. A surface with several guides shows them all, ordered featured-first, then by title.
Collections and Globals are multi-selects. Blocks is a select whose menu is Payload's own block drawer, with thumbnails, groups, and search, so blocks are chosen by their label rather than by slug. All three list only what the plugin covers, so an excluded entity cannot be targeted. Labels follow the reader's admin language wherever the entity declares one per locale.
Fields is a grouped list. See Picking fields.
Custom targets is a multi-select over the keys your config declares. See Custom targets.
Field schema paths
Field targets carry the thing they belong to as a prefix:
collection:posts.title
collection:posts.hero.headline
global:settings.siteNameThe prefix is required. Payload only enforces slug uniqueness within collections, so a collection and a global may both be called settings, and an unqualified path would merge their fields.
Paths are index-free. A field inside an array is addressed once, by its position in the schema, not per row:
collection:posts.gallery.captionThat covers the caption field of every row of gallery.
Unnamed structural fields add no segment: rows, collapsibles, and unnamed tabs are invisible to the path. Named tabs and named groups add their name.
Fields inside blocks
A block is its own root. Fields inside one are rooted at the block slug, not at the collection or global the block happens to be used in:
block:heroBanner.heading
block:heroBanner.cta.labelThat is deliberate: a block is shared, so a guide about its heading follows the block into every collection, global, and enclosing block that uses it. Writing it once is the whole point; the entity a reader happens to be editing does not change what the field means.
The blocks field itself still belongs to its entity, since that is where the field lives:
collection:posts.layout the blocks field on posts
block:heroBanner.heading a field inside the blockNested blocks work the same way. A block inside a block is rooted at its own slug, however deep it is used:
block:accordion.panels a blocks field inside the accordion block
block:panel.title a field inside the block that field allowsTwo consequences worth knowing:
- Targeting a block's field in one collection only is not expressible. If you need that, document the whole surface with a collection or field target on the enclosing form instead.
- Block slugs are a global namespace here, exactly as they are for whole-block targets. If two different block configs share a slug, their fields merge under the one root and the plugin logs a warning on boot naming the slug and where each was found. Payload permits the collision (its duplicate check runs per blocks field, not across the config) and resolves it registry-first in parts of its own admin, so the wiki follows that precedence.
Picking fields
The Fields list is grouped by what each target is rooted at: one collapsible section per collection, global, and block, labelled with its kind and carrying a count. Sections holding more than five paths start collapsed.
Each path reads as a breadcrumb of field labels, in the reader's admin language: collection:posts.branding.links.url shows as Branding › Links › URL. The stored value is on the row's tooltip. Named groups, named tabs, and arrays contribute a crumb; rows and unnamed tabs contribute none; a collapsible contributes its label. A path that no longer resolves against the running config is shown verbatim instead.
Targets whose owner no longer resolves are listed in a final Unresolved section under their raw stored value, still readable and still removable.
The picker
Pick fields, above the list, opens a menu of what to pick from: Collection, Global, Block. A kind the plugin covers nothing of is left out, and when only one kind is covered the button opens it directly.
Each kind has its own drawer, rendering the real fields of whatever is selected: your labels, your custom field components, your layout, in your admin language. One control at the top switches within that kind:
- collections and globals switch through a menu
- a block switches through Payload's own block drawer, with thumbnails, groups, and search
Block opens that block drawer straight away, since a block has no default. The paths a block produces are block-scoped (block:heroBanner.heading).
Each field carries a Cover this field plate in place of its help surface. Selecting is a toggle, and the drawer opens with the guide's existing targets already selected. Apply writes the result into the form; nothing is stored until you save the guide.
Every section header in the Fields list has an Edit shortcut, opening that kind's drawer on that collection, global, or block.
The drawer reads only. It is not a document view: no save, no autosave, no draft, no document lock, and no write to the entity whose fields it shows.
Arrays and blocks in the picker
Every array opens with one empty row, recursively. Paths stay index-free: picking caption in the first row of gallery targets that field in every row.
A blocks field is shown as itself and stays empty. Its fields are reached by picking the block from the Block drawer.
What the picker cannot show
- Fields hidden by a condition. A field whose
admin.conditionis false against empty data is not rendered. - Custom field components with no Description slot. See Customization.
- Blocks that render nowhere. A block declared in
config.blocksand referenced by noblocksfield is left out of the block drawer. When no covered block renders anywhere, Block is absent from the menu. - Blocks that only a rich text editor renders. See Blocks in lexical editors.
The path input under the list accepts any owner-qualified path and appends it on Add or Enter. It reaches every case above except a block declared only inside an editor, and it is how you attach a guide to a field that does not exist yet.
Blocks in lexical editors
The config walk finds blocks through fields of type blocks, plus everything in config.blocks. A block used by a rich text editor's BlocksFeature falls into one of two cases.
Declared inline in the feature (BlocksFeature({ blocks: [...] })), present in no blocks field and not in config.blocks:
- Not walked. No target keys, no help surfaces.
- Absent from the Blocks list and from the field picker.
- A path typed by hand resolves to nothing and is reported by the orphan banner.
Referenced from the registry (BlocksFeature({ blockReferences: [...] }) with the block in config.blocks), present in no blocks field:
- Walked, like everything in
config.blocks.block:<slug>and its field paths are valid targets. block:<slug>is selectable in the Blocks list.- Field paths such as
block:<slug>.<field>work when typed into the Fields list, and the help surfaces render inside the editor. - Only the picker is unavailable: the block is absent from the block drawer's grid.
Custom targets
The four kinds above are derived by walking your config. A screen the config does not describe is none of them: a view registered through admin.components.views, a panel inside one, a report. Declare those keys yourself and they become a target kind like any other.
adminWiki({
customTargets: [
{ key: 'dashboard', label: { de: 'Übersicht', en: 'Dashboard' } },
{ key: 'dashboard.attention', label: 'Dashboard · Needs attention' },
'traffic',
],
})A key is a bare slug of your choosing. The plugin namespaces it to custom:<key> internally, so it can never collide with a collection, global, block, or field target, and you never type the namespace. The string shorthand declares a target labelled by its own key; an object takes a plain label or one per admin language. A key written with the namespace already on it (custom:dashboard) is accepted and stripped, blanks are dropped, and a key declared twice keeps its first label.
Declaring at least one key adds the Custom targets list to the Targets tab, as a multi-select over exactly what you declared. Declaring none leaves the guide collection, and its database schema, exactly as it was: there is no field, no column, and no migration for a project that does not use this.
Rendering the help surface takes one component, which needs nothing but the bare key:
import { WikiCustomHelp } from '@10x-media/admin-wiki/client'
export const Dashboard = () => (
<>
<h1>
Dashboard <WikiCustomHelp target="dashboard" />
</h1>
<section>
<h2>
Needs attention <WikiCustomHelp target="dashboard.attention" />
</h2>
</section>
</>
)It behaves exactly like the field surfaces: nothing renders until a guide targets that key, the trigger shows each guide's summary on hover and opens the full guide on click, and an unguided key offers a write affordance that opens a create drawer with the target already filled in. Custom targets also carry their label on the Covers chips, on the wiki index, and in its filters.
A stored key you later stop declaring is reported by the orphan banner, the same way a deleted field is: the declaration is the only thing that makes a custom key valid, since nothing in the config describes what it names.
Target keys
Internally every target becomes one string, and that string is what surfaces listen on:
collection:posts
global:settings
block:heroBanner
custom:dashboard
field:collection:posts.title
field:block:heroBanner.headingField keys carry a double prefix: field: plus the owner-qualified path the author stored. These are what the orphan banner prints and what the component API takes.
Targets are stored as plain strings
Every list is a text field with hasMany, not relationships and not select fields with stored options. Every picker is a UI over the same string storage. That has three consequences worth knowing:
- A field guide can be written before the field exists, which is normal when documenting a form while it is still being built. The picker cannot offer a field that does not exist; the path input can.
- A guide survives its surface being removed, renamed, excluded, or, for a custom target, undeclared. The value stays readable and removable in the picker, listed under its raw slug or in the Unresolved group; only the attachment is stale, and the content is still there to reattach.
- Nothing in the wiki schema depends on the shape of your config, so a field moving never means a migration.
The cost is that a typo in a field target attaches a guide to nothing, which the orphan banner catches.
Orphaned targets
The Wiki Pages list view shows a banner listing every guide whose stored targets no longer resolve against the running config, with the offending keys as pills and a link to the guide's edit view.
Stored targets are compared against the set of keys the walker collected on boot, so it catches renamed fields, removed blocks, and dropped collections. Drafts are included. The banner is visible only to users whose update permission on the wiki collection resolves true.
The underlying endpoint is GET /api/wiki-pages/orphaned-targets.
Prefilled targets
Schema paths do not have to be typed by hand, and the picker is not the only way to avoid it. Every unguided surface can render a "write this guide" affordance that opens a create drawer with the matching target list already filled in, so a guide started from the field it is about arrives with that field already targeted. See Write affordances.
Localization
Targets are not localized: a guide points at the same field in every language. Title, summary, and content are localized when your config declares localization, so one guide document holds every language's version.
Readers resolve guides through their admin UI language. When your admin languages and content locales use different keys, map them:
adminWiki({
localeMap: { 'en-US': 'en', 'de-DE': 'de' },
})Resolution order is the mapped locale, then the admin language itself when it is a content locale, then the default locale.
Next
Surfaces: where each target kind renders, and how to move or disable it.