Are you a host app developer wanting to add your own draggable component? You don't need this doc. Use [defineComponent + PageHubConfig.components](registration-host.md) — one file, one function call, no SDK fork. This doc is for SDK con…
Are you a host app developer wanting to add your own draggable component? You don't need this doc. Use
defineComponent+PageHubConfig.components— one file, one function call, no SDK fork.This doc is for SDK contributors adding a new built-in that ships in
BUILTIN_COMPONENT_DEFSalongside Container / Text / Button.
A new built-in touches 8 places inside packages/sdk/src. Most are one-line registrations. The real work is the <Name>.craft.tsx file (#2) — that's where defaults, the inspector schema, presets, and the toHTML serializer live.
| # | File | What |
|---|---|---|
| 1 | components/<Name>.tsx | The React component. |
| 2 | components/<Name>.craft.tsx | defineComponent(...) with toHTML for static export. The real work. |
| 3 | chrome/toolbar/inspector/mainTabs/<Name>MainTab.tsx | Settings panel (and <Name>MainTabAdvanced if needed). |
| 4 | components/definitions.ts | export { <Name>Def } from "./<Name>.craft". |
| 5 | components/resolvers/viewer.ts | Add <Name>: cv(<Name>) to viewerResolver. Single source of truth — DEFAULT_CRAFT_RESOLVER (editor) derives from it via spread + SavedComponentLoader. Used by /view/, /static/, /build/. |
| 6 | core/componentRegistry.ts | Add <Name>Def to BUILTIN_COMPONENT_DEFS (def list for toolbox / static HTML / viewer processing). Resolver map is now derived — no edit needed there. |
| 7 | define.ts | Add the name to BUILT_IN_NAMES so collision detection works for host-app custom components. |
| 8 | index.ts | export { <Name> } from "./components/<Name>". |
After SDK changes, hard-reload the editor — Turbopack HMRs but the running React tree caches the resolver map.
viewer.ts → both editor and published pages crash with Cannot destructure 'type' of ue() is undefined (single resolver source).BUILT_IN_NAMES → a host-app custom component with the same name silently overrides yours.Anywhere reachable from low-level utilities a CraftJS component imports (e.g. utils/sanitizeNodeMap.ts, utils/tailwind/tailwind.ts, utils/cloneState.ts, components/selectors.ts) MUST NOT static-import BUILTIN_COMPONENT_DEFS, components/definitions.ts, or any *.craft.tsx. It forms a TDZ cycle that crashes at runtime with ReferenceError: Cannot access '<Component>' before initialization (whichever component is alphabetically first in definitions.ts). pnpm typecheck will not catch it.
Use the registry setter pattern instead — setSanitizeBuiltinDefs style, same shape as registerClientDataFetcher.
defineComponent (the doc for SDK consumers, not contributors).