nobrainer / silverstripe-icon-picker
A self-hosted, SVG-based icon picker field for Silverstripe CMS.
Package info
bitbucket.org/nobrainerweb/silverstripe-icon-picker/
Type:silverstripe-vendormodule
pkg:composer/nobrainer/silverstripe-icon-picker
Requires
- php: ^8.3
- ext-dom: *
- silverstripe/admin: ^3
- silverstripe/framework: ^6
Requires (Dev)
- phpunit/phpunit: ^11.3
- silverstripe/cms: ^6
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-21 09:59:55 UTC
README
A self-hosted, SVG-based icon picker field for Silverstripe CMS. Editors pick an icon from a searchable modal; the choice renders as inline <svg> in templates, styleable with plain CSS.
- Ships the full Tabler icon set (v3.46.0, 6,184 icons, outline and filled) as SVG files, not a font — no paid tier, no external CDN.
- Swap in a different icon library, or add your own icons, purely through YAML config.
- Renders real inline SVG, so
color,widthand hover states work like any other element.
Requirements
- PHP 8.3+
- Silverstripe CMS 6 (
silverstripe/framework^6,silverstripe/admin^3)
Installation
composer require nobrainer/silverstripe-icon-picker
Then run dev/build to generate the icon manifest.
Usage
Declare the field as DBIcon:
use Nobrainer\IconPicker\ORM\FieldType\DBIcon;
private static array $db = [
'Icon' => DBIcon::class,
];
That's all you need — getCMSFields() scaffolds the picker from $db automatically, on a plain DataObject and on a Page/SiteTree subclass alike.
To control where the field lands (a specific tab, alongside other fields), place it explicitly instead:
use Nobrainer\IconPicker\Forms\IconPickerField;
public function getCMSFields()
{
$fields = parent::getCMSFields();
// Scaffolding already added a field named 'Icon' - remove it before adding your own.
$fields->removeByName('Icon');
$fields->addFieldToTab('Root.Main', IconPickerField::create('Icon', 'Icon'));
return $fields;
}
Don't add a : FieldList return type there — core SiteTree subclasses such as ErrorPage declare getCMSFields() without one, and a narrower signature on your Page is a fatal error.
Render it in a template. The field outputs the <svg> directly, with no wrapper
element, so it can sit inline in text or inside a button:
<% if $Icon %>$Icon<% end_if %>
Each icon carries a base class and a per-icon modifier — class="icon-picker-icon icon-picker-icon--home" — so you can style all icons at once or target one:
.icon-picker-icon {
width: 24px;
height: 24px;
color: currentColor;
}
Configuration
Icon sources
A source is a folder of SVGs plus a label. The module ships with two:
Nobrainer\IconPicker\Model\IconManifest:
sources:
tabler:
enabled: true
path: 'nobrainer/silverstripe-icon-picker: client/icons/tabler'
label: 'Tabler'
styles:
outline: true
filled: true
custom:
enabled: true
path: 'app/icons'
label: 'Custom'
pathis eithervendor/module: sub/path(a folder inside a Composer package) or a project-relative path.stylessplits a source into subfolders, each becoming its own tab (e.g. "Tabler – Outline"). Omit it for a flat source, likecustom.- Nothing in the PHP or JS knows which libraries exist, so adding, disabling or repointing a source is a config change only.
Class names on rendered icons
The base class is configurable; each icon also gets a --<name> modifier built from its filename. Set it empty to render icons with no class at all.
Nobrainer\IconPicker\Model\IconManifest:
css_class: 'my-icon'
It's baked into the manifest, so rebuild after changing it.
Overriding in a project
Target the same config class from your project's YAML and restate only what changes:
---
Name: app-icon-picker
After: '#nobrainer-icons'
---
Nobrainer\IconPicker\Model\IconManifest:
sources:
custom:
path: 'themes/client/assets/icons/custom'
tabler:
styles:
filled: false
That repoints the custom folder, keeping its enabled and label, and drops the Tabler Filled tab.
stylesis a map rather than a list because Silverstripe's config merge only overrides string keys. Integer-keyed lists are appended to, so a list could be added to by a project but never trimmed.
Adding your own icons
Drop .svg files into the custom source's folder (app/icons by default), then rebuild:
sake dev/tasks/generate-icon-manifest
Filenames become the icon's name and what search matches on, so arrow-up-right.svg is findable as "arrow", "up" or "right".
How it works
IconManifest scans every enabled source for .svg files and caches a flat list of entries — id, name, group and cleaned markup. It's rebuilt on dev/build, or manually with sake dev/tasks/generate-icon-manifest, which is required after adding icon files or changing sources.
The CMS field is a hidden input holding a compact id (tabler:outline:home), enhanced into a React picker. Icon data reaches it in two requests, so neither scales with the size of the library:
/admin/icon-picker/manifest— the searchable index (id, group, name), no markup. Fetched once, the first time a modal opens. Search and tab filtering run against it client-side./admin/icon-picker/svgs?ids=…— markup for one grid page of icons. Anything already held is never re-fetched.
On the front end, DBIcon::forTemplate() looks the id up in the manifest and returns the inline SVG.
Development
The built bundle in client/dist/ is committed, so consuming projects never need Node. Only rebuild when changing files under client/:
npm install
npm run build # or: npm run watch
Tests
Run from a project that has the module installed:
vendor/bin/phpunit --bootstrap vendor/silverstripe/framework/tests/bootstrap.php \
vendor/nobrainer/silverstripe-icon-picker/tests/php/
Set SS_PHPUNIT_FLUSH=1 after changing config or adding a test class, otherwise the cached manifests serve stale values.
Troubleshooting
Icons don't appear after a config or file change. Run sake dev/build --flush. Silverstripe caches config and this module caches the manifest; neither picks up changes without a flush.
A source is missing entirely. dev/build and the manifest task print a warning naming any configured path that doesn't exist — check the output for a typo'd path.
Changes still don't apply after a flush. A long-running PHP-FPM worker can keep serving a stale manifest even after dev/build --flush or an HTTP ?flush=1 — most likely after replacing a large number of icon files at once, and can happen even with opcache.validate_timestamps enabled. If the icon count in the CMS doesn't match what a flush reported, restart PHP-FPM (or the container, in a local Docker/ddev setup) rather than flushing again. Production deploys that restart PHP-FPM per release aren't affected.
License
BSD-3-Clause — see LICENSE.
The bundled Tabler icons (v3.46.0) under client/icons/tabler/ are MIT licensed, © Paweł Kuna, and are redistributed with their notice intact — see client/icons/tabler/LICENSE and tabler/tabler-icons.