Register icons
Add icons to the global map so your plugin's UI can use them — and so other plugins can too.
ctx.registerIcons(map) adds arbitrary icon components to the global icon map. They become available
everywhere useIconMap() is read — including in your own panels and inspector sections. Use this
when:
- Your plugin needs a glyph the SDK doesn't ship (a brand icon, a domain-specific symbol).
- You want to override one of the built-in defaults from a plugin (you usually shouldn't — host-app
iconsoverrides are the right place).
Registering
import { createPlugin } from '@studio-dev/vsdk/plugins'
import { SparkleIcon, FlameIcon } from './icons'
export const myPlugin = () =>
createPlugin({
id: 'mycompany:my-plugin',
name: 'My Plugin',
version: '0.1.0',
onRegister(ctx) {
ctx.registerIcons({
sparkle: SparkleIcon,
flame: FlameIcon,
})
ctx.registerPanel({
id: 'my-panel',
label: 'My Panel',
icon: ctx.icons.sparkle, // refer back to the icon you just registered
component: lazy(() => import('./panel')),
order: 50,
})
},
})Each value must satisfy StudioIconComponent = ComponentType<{ className?: string }> — the SDK
applies sizing through className.
Precedence
The resolved icon map is built in this order:
defaults ← plugin-registered icons ← consumer icons propSo:
- Plugin-registered icons override defaults (use sparingly).
- Consumer (host app)
iconsprop onVideoStudioProvideralways wins.
This layering lets the host app rebrand the editor regardless of which plugins are loaded — a plugin
can register sparkle, but if the host also passes icons={{ sparkle: MyBrand }}, the brand wins.
When to register vs. override
| Use case | Mechanism |
|---|---|
| Your plugin needs a glyph the SDK doesn't ship | ctx.registerIcons |
| You want to globally rebrand all play/pause/save icons | Host app's icons prop |
| You want a custom icon visible only in your plugin's UI | Just import and use it directly — don't pollute the global map |
The last row is worth emphasizing: not every plugin icon needs to be registered. If only your panel renders it, importing the React component and using it directly is fine — the registry exists for cross-plugin sharing and theming, not as a generic icon catalog.
Reading icons from other plugins
If plugin A registers sparkle, plugin B's components can read it through useIconMap():
import { useIconMap } from '@studio-dev/vsdk'
function PluginBPanel() {
const icons = useIconMap()
const Sparkle = icons.sparkle // present if plugin A registered it
if (!Sparkle) return null
return <Sparkle className="size-4" />
}Useful for ecosystem icons — e.g. a "brand kit" plugin registers brand glyphs that other plugins opt-in to using.
TypeScript
StudioIconMap is an intersection of Record<StudioIconName, StudioIconComponent> with
Record<string, StudioIconComponent>, so reading an unknown key returns a possibly-undefined
component without a type error:
const icons = useIconMap()
const Sparkle = icons.sparkle // type: StudioIconComponent (may be undefined at runtime)For a stricter typing on your own plugin keys, extend the map type:
declare module '@studio-dev/vsdk' {
interface StudioIconMap {
sparkle: StudioIconComponent
flame: StudioIconComponent
}
}Use module augmentation sparingly — once it's in a d.ts shared with consumers, every host app sees
your keys.