Adds an icon picker field to Payload 3 that stores the provider and icon name. It discovers icons from the Lucide and Phosphor packages installed in your app and serves serialized SVGs from plugin endpoints, so provider modules stay out of client bundles. Requires Payload 3.37 or later, React 19, and a Next.js config wrapper.
Icon picker field for Payload CMS with Lucide and Phosphor providers.
Installation
npm i payload-plugin-icons2
Stars
0
Forks
0
Open Issues
1 months ago
Last Updated
0
Weekly Downloads
v1.1.5
npm Version
170.7 KB
Unpacked Size
0
Dependencies
Trends appear after 7 days of data. Daily snapshots have not started yet.
Trends appear after 7 days of data. Daily snapshots have not started yet.
Each point is npm's rolling 7-day download count.
Health Score
Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.
Badge for your README
[](https://payloaddirectory.dev/plugins/1197210288-root)<a href="https://payloaddirectory.dev/plugins/1197210288-root"><img src="https://payloaddirectory.dev/badge/1197210288-root.svg" alt="Payload Directory"></a>README
An icon picker field for Payload 3. It stores { provider, name }, discovers icons from the provider versions installed in your app, and renders provider-neutral serialized SVGs.
payload-plugin-icons is sponsored by BrainyBuilds. Thank you for supporting the project!
npm install payload-plugin-icons lucide-react @phosphor-icons/react
Requirements: Payload ^3.37.0, React 19, Lucide React >=0.400.0, and Phosphor React >=2.0.0.
import { buildConfig } from 'payload'
import { createIconPlugin } from 'payload-plugin-icons'
const { iconField, iconPlugin } = createIconPlugin()
export default buildConfig({
plugins: [iconPlugin],
collections: [
{
slug: 'pages',
fields: [
iconField({
name: 'icon',
label: 'Icon',
required: false,
}),
],
},
],
})
Configure next.config so provider packages are loaded natively only when an icon endpoint runs. (Lucide uses Node runtime loading because Next optimizes it by default; other providers are added to serverExternalPackages.)
import { withPayload } from '@payloadcms/next/withPayload'
import { withPayloadIcons } from 'payload-plugin-icons'
const nextConfig = {}
export default withPayload(
withPayloadIcons(nextConfig),
{ devBundleServerPackages: false },
)
Next.js output tracing cannot discover the provider packages because the plugin loads them through opaque runtime imports. This does not affect next dev or a conventional next start deployment where the project's complete node_modules remains available. However, output: 'standalone' copies only files discovered by output tracing into .next/standalone.
Explicitly include each configured provider package when building a standalone deployment:
import { withPayload } from '@payloadcms/next/withPayload'
import type { NextConfig } from 'next'
import { withPayloadIcons } from 'payload-plugin-icons'
const nextConfig: NextConfig = {
output: 'standalone',
outputFileTracingIncludes: {
'/*': [
'./node_modules/lucide-react/**/*',
'./node_modules/@phosphor-icons/react/**/*',
],
},
}
export default withPayload(
withPayloadIcons(nextConfig),
{ devBundleServerPackages: false },
)
If the plugin is configured with only one built-in provider, include only that provider's package. Add equivalent patterns for custom provider packages. Patterns are resolved from the Next.js project root; monorepos may also need Next.js's outputFileTracingRoot setting.
withPayloadIcons configures provider packages for native server loading, but opaque runtime imports still require these explicit tracing includes in standalone builds.
packageImport defaults to payload-plugin-icons. Set it when the package is exposed through a different monorepo alias:
createIconPlugin({ packageImport: '@acme/payload-icons' })
After changing the field configuration, regenerate Payload's import map and types:
payload generate:importmap
payload generate:types
Icon is a Client Component. It fetches a small serialized SVG definition from the plugin endpoint; provider packages stay on the server. Requests mounted in the same turn are automatically batched and definitions are shared in a module-level cache.
'use client'
import type { IconData } from 'payload-plugin-icons'
import { Icon } from 'payload-plugin-icons/client'
export function FeatureIcon({ icon }: { icon?: IconData | null }) {
return <Icon aria-hidden className="feature-icon" icon={icon} size={24} />
}
SVG props and event handlers are applied to the outer <svg>. Phosphor's weight is included in the request and cache key.
Use a subset of the built-in providers or change their labels:
import { createIconPlugin, lucideProvider } from 'payload-plugin-icons'
const { iconField, iconPlugin } = createIconPlugin({
providers: [lucideProvider({ label: 'Interface icons' })],
})
Field options:
name: stored field namelabel: admin labelrequired: require an icon namedefaultProviderId: provider selected for new documentsproviderIds: provider subset for this fieldlabelsById: field-specific provider labelspackageImport: field-specific package name or aliasadmin: additional Payload field admin optionsA custom provider implements the server-only IconProvider contract. Keep its package import inside loadCatalog / loadIcons, serialize only validated names from your catalog, and add the package name to withPayloadIcons.
import type { IconProvider } from 'payload-plugin-icons'
import { createIconPlugin, serializeIconComponent } from 'payload-plugin-icons'
const customProvider: IconProvider = {
id: 'custom',
label: 'Custom',
packageName: '@acme/icon-library',
async loadCatalog() {
return { provider: this.id, version: '1', icons: [{ name: 'Home' }] }
},
async loadIcons({ names }) {
const icons = await import('@acme/icon-library')
return Object.fromEntries(names.flatMap((name) =>
name === 'Home' ? [[name, serializeIconComponent(icons.Home)]] : [],
))
},
}
const { iconField, iconPlugin } = createIconPlugin({
providers: [customProvider],
})
Call withPayloadIcons(nextConfig, ['@acme/icon-library']) for this provider.
If /api/payload-icons/:provider/catalog or /api/payload-icons/:provider/icons works in development but returns 500 from a container that copies only .next/standalone, the provider package was probably omitted by Next.js output tracing. The endpoint may report that it is unable to load the icon catalog or icons.
Add the provider package to outputFileTracingIncludes as shown in Standalone and Docker deployments, rebuild the image, and confirm that its directory exists under the deployed node_modules. A typical standalone image copies .next/standalone and .next/static; copying .next/static does not add missing server dependencies.
npm ci
npm run dev
The development app uses a local SQLite database at dev/payload.db, so no external database is required. You can optionally copy dev/.env.example to dev/.env to set PAYLOAD_SECRET or override DATABASE_URL. The development admin runs at http://localhost:2515.
Useful commands:
npm run check: lint and create a clean package buildnpm run generate:importmap: regenerate the development import mapnpm run generate:types: regenerate development Payload typesnpm run build:next: build the development applicationdist is committed and verified in CI.
MIT
Tags
payload icon picker
Icon picker field with searchable dropdown or drawer grid that stores the icon name and raw SVG
690/wk52v3
icons
SVG icon collections with upload optimization, an admin icon select, and a server Icon component for Payload
540/wk37v3
payload icon picker field
Icon picker field for Payload using react-icons packs or custom SVG icons
410/wk57v3
payload icon picker
Icon picker field that stores a selected icon from registered libraries as a text value
360/wk0v3
encrypted fields
Encrypt field values at rest in the database with a drop-in encryptedField helper
6955/wk83v3
payload phone number plugin
Phone number field with country selection, formatting and validation powered by libphonenumber-js
64114/wk16v3