Content Type Builder#
The Content Type Builder (CTB) is the admin UI plugin (packages/core/content-type-builder) for defining and editing the schemas of content types and components in Strapi. It operates exclusively in development mode — all edits are staged in client-side Redux state and not written to disk until the user explicitly saves.
The CTB is split across two packages:
| Layer | Package |
|---|---|
| Admin UI | packages/core/content-type-builder/admin/ |
| Server | packages/core/content-type-builder/server/ |
Important distinction: Schema files (on disk) define field types and structure. Display configuration — list/edit view layout, field metadata — lives separately in the database, managed by
packages/core/content-manager/server/. See the Plugin Configuration and Layout Storage section below.
Client-Side State: DataManager#
All pending schema changes live in a Redux store managed by the DataManager. The central files are:
DataManagerProvider.tsx— wraps the CTB UI, fetches initial schema data, and exposessaveSchema()DataManagerContext.ts— TypeScript interface for context value; declaresmoveAttribute,saveSchema, and other mutationsuseDataManager.ts— thinuseContextwrapper consumed by every CTB componentreducer.ts— Redux Toolkit slice with full undo/redo (50-step history limit)
The state shape holds a working copy (contentTypes, components) alongside the initial copy (initialContentTypes, initialComponents) loaded from the server . Each schema and attribute carries an explicit status field: NEW | CHANGED | UNCHANGED | REMOVED.
Key Reducers#
| Action | Effect |
|---|---|
init | Populates both working and initial copies from server data |
addAttribute / editAttribute / removeField | Mutates working copy, sets status to NEW / CHANGED / REMOVED |
moveAttribute | Reorders attributes[] in-place |
createSchema / deleteContentType | Creates or tombstones a full content type |
discard | Restores working copy from initial copy (undo all unsaved changes) |
Development mode gate:
SortableRowdisables drag handles when!isInDevelopmentModeor when the type/attribute status isREMOVED.
Drag-and-Drop Attribute Reordering#
The attribute list is rendered in List.tsx, which uses @dnd-kit/core with a verticalListSortingStrategy . Each row is a SortableRow wrapping AttributeRow via useSortable from @dnd-kit/sortable.
On DragEnd, the handler calls moveAttribute from useDataManager with the source and destination indices . This dispatches the moveAttribute reducer action, which does a splice/insert in the attributes array and marks the parent type as CHANGED .
Key behaviours:
- Movement is restricted to the vertical axis via
restrictToVerticalAxismodifier - A
DragOverlayportal renders the dragged item on top of other content - Both
PointerSensorandKeyboardSensorare registered for accessibility
Reordering is optimistic and local — the new order is not persisted until the user clicks "Save" and triggers saveSchema().
Saving Schema Changes#
When the user saves, saveSchema() in DataManagerProvider calls stateToRequestData(), which:
- Filters only schemas with status
NEW | CHANGED | REMOVED - Maps each schema through
formatTypeForRequest(), which translates status → action (create/update/delete) and flattensinfo/optionsinto the root object - Sends every attribute regardless of individual attribute status — the comment in the source explicitly notes this preserves field order
- Returns tracking event counts for analytics alongside the formatted payload
The formatted payload is POSTed to /update-schema , a development-mode-only route protected by admin::hasPermissions.
Server-Side Flow#
schema.tscontroller — validates the payload viavalidateUpdateSchema(), setsinternals.isUpdating = trueto block concurrent saves, and delegates to the schema serviceschema.tsservice — creates new types first, then updates existing ones, writes schema files to disk, and emits EventHub events forcreate/update/deletelifecycle hooks- Server reload —
strapi.reload()is called after the file write, restarting the Strapi process to pick up the new schema files
Plugin Configuration and Layout Storage (Content Manager)#
Schema files (on disk) define field types and constraints. Display configuration — list/edit view layout, field metadata, settings — is stored separately in the database via Strapi's strapi::core-store entity under the key prefix plugin_content_manager_configuration_ .
Store Layer (store.ts)#
packages/core/content-manager/server/src/services/utils/store.ts is the low-level utility:
getModelConfiguration(key)— reads from the core store and merges withEMPTY_CONFIG({ settings:{}, metadatas:{}, layouts:{ list:[], edit:[] } })getModelConfigurations(keys[])— batch loads multiple configs in one DB query using$insetModelConfiguration(key, value)— merges partial updates into the stored record and skips writes when the config is unchangeddeleteKey(key)— hard-deletes fromstrapi::core-store
Configuration Service (configuration.ts)#
packages/core/content-manager/server/src/services/configuration.ts is a factory that wraps storeUtils and adds:
- A
prefix(e.g.content_typesorcomponents) namespacing keys as{prefix}::{uid} syncConfigurations()— on each server start, compares stored UIDs against currently registered schemas and automatically creates, updates, or deletes configuration records to keep them in sync
Content Types Service (content-types.ts)#
packages/core/content-manager/server/src/services/content-types.ts is the public API consumed by controllers:
findConfiguration(contentType)— retrieves the stored config for a UIDupdateConfiguration(contentType, newConfiguration)— persists admin-edited layout changessyncConfigurations()— delegates to the configuration service for startup sync
The prefix for content types is 'content_types'; for components it is 'components' (defined when createConfigurationService is called for each service) .