← Back to list

I accidentally built a plugin system (The Registry Pattern)

A central map where components announce themselves — instead of a central file that imports everything.

Ponbaskar · 2026-06-12 19:51 · 4 claps · 1.6 min read
#registry-pattern #design-patterns #best-practices #typescript #react
Open on Medium ↗
Wiki topics: 🌐 · Web Development

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 callregister()themselves at module load time. Nothing imports them centrally.
  • One entry import.A singleimport './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 aComponentRegistryinterface 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