I accidentally built a plugin system (The Registry Pattern)
A central map where components announce themselves — instead of a central file that imports everything.
I accidentally built a plugin system
(The Registry Pattern)
A central map where components announce themselves — instead of a central file that imports everything.

Problem
Before — tight coupling
Every new component requires editing a shared central file. Multiple developers conflict on the same import list.
After — self-registration
Each component declares itself to the registry. No shared file is touched. Developers never conflict.
Core implementation
// registry.ts
type Component = React.ComponentType<any>
const map = new Map<string, Component>()
export function register(name: string, c: Component) {
map.set(name, c)
}
export function get(name: string) {
return map.get(name)
}
How components self-register
Button.tsx
import { register } from './registry'
function Button({ label }: ButtonProps) {
return <button>{label}</button>
}
register('Button', Button) // ← one line, no other files touched
export default Button
How Components Use It
Each component now registers itself:
// Button.tsx
import { register } from './registry'
interface ButtonProps {
label: string
onClick?: () => void
}
function Button({ label, onClick }: ButtonProps) {
return <button onClick={onClick}>{label}</button>
}
register('Button', Button) // <-- One line at the bottom
export default Button
Now we have one generic renderer:
// Renderer.tsx — dynamic dispatch
export function Renderer({ type, ...props }) {
const C = get(type)
return C ? <C {...props} /> : null
}
attern rules
- One map, one source of truth.All components register into the same map instance — never multiple registries.
- Self-registration only.Components call
register()themselves at module load time. Nothing imports them centrally. - One entry import.A single
import './components'triggers all registrations. App code never imports individual components directly. - Handle missing gracefully.Renderer must warn and return null for unknown types — never throw silently.
- Type safety is optional but recommended.Use a
ComponentRegistryinterface for autocomplete, but don't let it become a second central file to maintain.
When To Use
Use it when
- 10+ components
- Multiple developers
- Backend drives layout
- CMS or page builder
- Plugin system needed
Skip it when
- Solo, simple project
- Bundle size is critical
- Static, known component set
- Tree-shaking matters
Pattern in the wild
Same pattern, different language
Django model registry · Spring bean container · Laravel service container · Unity component system · Windows Registry
Key tradeoff
Bundlers can’t tree-shake dynamic registries. All registered components ship, even unused ones. Worth it at scale — not for small apps.
메타데이터
- post_id
- f174b5f5d031
- slug
- i-accidentally-built-a-plugin-system-and-you-can-too-f174b5f5d031
- url
- https://medium.com/@ponbaskar397/i-accidentally-built-a-plugin-system-and-you-can-too-f174b5f5d031
- canonical_url
- https://medium.com/@ponbaskar397/i-accidentally-built-a-plugin-system-and-you-can-too-f174b5f5d031
- author_url
- https://medium.com/@ponbaskar397
- status
- ok
- fetched_at
- 2026-06-13 12:55:53