Adapters
First-party Lucide, Radix, and Tabler adapters, and the contract for defining your own.
An adapter connects one icon library to the field: a lazy manifest (names, tags, categories) for browsing and validation, and client components that render icons in the admin.
First-party adapters
import { lucideAdapter } from '@10x-media/fields/icon/adapters/lucide'
import { radixAdapter } from '@10x-media/fields/icon/adapters/radix'
import { tablerAdapter } from '@10x-media/fields/icon/adapters/tabler'Each adapter has an optional peer dependency on its icon package (lucide-react, @radix-ui/react-icons, @tabler/icons-react); install the ones you use. Manifests are generated from the libraries' own metadata at this package's build time and ship as lazily imported modules, so registering an adapter adds nothing to any eager bundle.
Register adapters globally through the plugin, or per field:
fields({ icon: { adapters: [lucideAdapter(), tablerAdapter()] } })
iconField({ name: 'icon', adapters: [radixAdapter()] }) // this field: Radix onlyDefining an adapter
defineIconAdapter pins the adapter contract version; you provide the rest:
import { defineIconAdapter } from '@10x-media/fields/icon'
export const acmeIcons = defineIconAdapter({
slug: 'acme',
label: 'Acme Icons',
loadManifest: () => import('./generated/manifest').then((m) => m.manifest),
Icon: '@acme/payload-icons/client#AcmeAdapterIcon',
Assets: '@acme/payload-icons/client#AcmeAdapterAssets',
})slugbecomes the value prefix (acme:rocket) and must stay stable forever; it is stored data.loadManifestis called on first drawer open, never eagerly, and resolves the generatedmanifest. Its shape is{ icons: [{ name, tags, categories, label? }], categories: string[] }.labelon an icon is optional and replaces the label otherwise derived from its name. Supply it wherever a name is a code rather than words:HUNreads asHunwithout one, and a screen reader announces exactly that. It accepts a per-locale record as well as a string. Every manifest without one is unaffected.resolveMeta(name, ctx)is optional and answers one name exactly, serving both validation and label resolution. Supply it for a library whose contents change at runtime; it stays correct where a cached manifest snapshot cannot. See layers.IconandAssetsare import-map component paths (the same string-path convention as every admin component).Iconrenders one icon by name, lazily;Assetsloads the manifest for the drawer. An optionalNodespath supplies bulk glyph data for fast inline-SVG drawer rendering; omit it for libraries the drawer renders through the per-iconIconcomponent (Radix does). The first-party adapters are the reference implementation for these components.
Labels an editor sees
Where a library supplies label, the drawer deliberately shows two different things. The accessible name carries the label alone, so a screen reader browsing a country library announces "Hungary" rather than "Hungary HUN" across every cell. The tooltip shows the label with the raw name beside it, because that tooltip is the only place an editor can discover that "Hungary" stores as flag:HUN, which they need in order to write frontend code against the value.
Hand-writing a manifest for a large library is busywork; codegen generates the manifest, and the per-icon import map those components load, from an icon set you describe.