Dosu LogoDosu Logo
Ask
Join our Discord
StrapiPublic
Strapi
DocumentsStrapi
Content Type Builder
Content Type Builder
Type
Topic
Status
Published
Created
Jun 24, 2026
Updated
Jun 24, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

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:

LayerPackage
Admin UIpackages/core/content-type-builder/admin/
Serverpackages/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 exposes saveSchema()
  • DataManagerContext.ts — TypeScript interface for context value; declares moveAttribute, saveSchema, and other mutations
  • useDataManager.ts — thin useContext wrapper consumed by every CTB component
  • reducer.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#

ActionEffect
initPopulates both working and initial copies from server data
addAttribute / editAttribute / removeFieldMutates working copy, sets status to NEW / CHANGED / REMOVED
moveAttributeReorders attributes[] in-place
createSchema / deleteContentTypeCreates or tombstones a full content type
discardRestores working copy from initial copy (undo all unsaved changes)

Development mode gate: SortableRow disables drag handles when !isInDevelopmentMode or when the type/attribute status is REMOVED .

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 restrictToVerticalAxis modifier
  • A DragOverlay portal renders the dragged item on top of other content
  • Both PointerSensor and KeyboardSensor are 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:

  1. Filters only schemas with status NEW | CHANGED | REMOVED
  2. Maps each schema through formatTypeForRequest(), which translates status → action (create / update / delete) and flattens info/options into the root object
  3. Sends every attribute regardless of individual attribute status — the comment in the source explicitly notes this preserves field order
  4. 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#

  1. schema.ts controller — validates the payload via validateUpdateSchema(), sets internals.isUpdating = true to block concurrent saves, and delegates to the schema service
  2. schema.ts service — creates new types first, then updates existing ones, writes schema files to disk, and emits EventHub events for create / update / delete lifecycle hooks
  3. 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 with EMPTY_CONFIG ({ settings:{}, metadatas:{}, layouts:{ list:[], edit:[] } })
  • getModelConfigurations(keys[]) — batch loads multiple configs in one DB query using $in
  • setModelConfiguration(key, value) — merges partial updates into the stored record and skips writes when the config is unchanged
  • deleteKey(key) — hard-deletes from strapi::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_types or components) 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 UID
  • updateConfiguration(contentType, newConfiguration) — persists admin-edited layout changes
  • syncConfigurations() — 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) .

Documents
Admin Modal Management
Admin Panel Locale Management
admin-panel
faq
setting-up-admin-panel
Admin User Management
Blocks Editor
Content Manager i18n Preview
locale
Content Manager Layout Synchronization
breaking-changes
Content Manager Preview
breaking-changes
Content Manager RBAC
Content Manager URL & Filter State
content-manager
Content Type Builder
admin-panel-api
breaking-changes
content-manager
controllers
create-components-for-plugins
models
populate-creator-fields
quick-start
store-and-access-data
Custom Field Prop Spreading
Database Migration Concurrency and Idempotency
Date Field Serialization
Discard-Drafts Migration
Document ID Migration
breaking-changes
step-by-step
Draft & Publish Relation Synchronization
do-not-update-repeatable-components-with-document-service-api
document-service
lifecycle-hooks-document-service
populate
publishedat-always-set-when-dandp-disabled
relations
status
DynamicZone Stability
breaking-changes
components-and-dynamic-zones-do-not-return-id
components-dynamic-zones
no-shared-population-strategy-components-dynamic-zones
Edit View Layout Configuration
admin-panel
admin-panel-api
breaking-changes
content-manager
edit-view-layout-and-list-view-layout-rewritten
EnumerationInput Component
GraphQL Context Propagation
document-service
draft-and-publish
graphql
step-by-step
i18n Locale Validation
breaking-changes
locale
locale
i18n Locale-Scoped Operations
breaking-changes
crud
database-columns
document
document-service
draft-and-publish
i18n-content-manager-locale
locale
locale
locale
no-locale-all
parameters
populate
relations
Monorepo Module Resolution
create-a-plugin
Nested Relation Modal Navigation
Relation Field Validation
Relation Modal State Management
relations
Sharp Image Processing
media-library
Strapi Data Transfer
Strapi Project Scaffolding
create-a-plugin
How can I create my first Strapi project on a brand new MacBook with no prior coding experience, including all necessary terminal commands and setup steps?
quick-start
Strapi v5 Plugin API
admin-panel-rbac-store-updated
admin-permissions-for-plugins
breaking-changes
controllers
extension
faq
get-where-removed
helper-plugin
helper-plugin-deprecated
inject-content-manager-component
introduction
introduction-and-faq
model-config-path-uses-uid
pass-data-from-server-to-admin
plugins-migration
rbac
redux-content-manager-app-state
step-by-step
strapi-imports
Users-Permissions Plugin i18n
users-permissions
Vite Build Configuration
admin-panel
introduction-and-faq
vite
webpack-aliases-removed
Content Manager Mobile and Tablet UI Improvements
FilesManager API Reference
Focal Point Picker in Strapi Media Library
Nested Route File Structure for Strapi Plugins
Plugin-Specific Handling in the Strapi JavaScript Client
Strapi SDK Initialization and Architecture Guide
access-cast-environment-variables
access-configuration-values
adding-support-to-existing-project
admin-panel-customization
advanced-policies
advanced-queries
amazon-s3
api
api-tokens
attributes-and-content-types-names-reserved
audit-logs
auth-zero
authentication
aws-cognito
backend-customization
bulk-operations
bundlers
cas
cli
cli
client
cloudinary
community
configurations
configure-sso
content-api
content-history
content-manager-apis
content-type-builder
core-service-methods-use-document-service
cron
custom-fields
customization
data-management
database
database-identifiers-shortened
database-migrations
database-transactions
default-index-removed
default-input-validation
deployment
design-system
developing-plugins
development
discord
discord
docker
documentation
documents-and-entries
email
email-custom-providers
email-nodemailer
entity-service
entity-service-deprecated
environment
error-handling
examples
facebook
favicon
features
fetch
fields
filter
filtering
filters
filters
from-entity-service-to-document-service
functions
github
github
google
google
graphql
graphql-api-updated
guides
homepage
host-port-path
instagram
installation
installing-plugins-via-marketplace
interactive-query-builder
internationalization
intro
intro
is-supported-image-removed
keycloak
keycloak
koa-body-v6
license-only
linkedin
local-upload
locales-translations
logos
mailgun-provider-variables
media-library-providers
microsoft
middlewares
middlewares
middlewares
middlewares
mysql5-unsupported
new-provider-guide
new-response-format
no-find-page-in-document-service
no-upload-at-entry-creation
okta
only-better-sqlite3-for-sqlite
only-mysql2-package-for-mysql
openapi
order-pagination
order-pagination
patreon
plugin-sdk
plugin-structure
plugins
plugins-extension
policies
policies
populate
populate-select
populating
preview
project-structure
publication-state-removed
query-engine
rbac
react-router-dom-6
reddit
register-allowed-fields
releases
remove-webhook-populate-relations
removed-support-for-some-env-options
requests-responses
rest
review-workflows
routes
routes
sentry
server
server-api
server-default-log-level
server-proxy
services
services-and-controllers
setup-deployment
single-operations
sort-by-id
sort-pagination
sort-pagination
sso
status
strapi-container
strapi-utils-refactored
strict-requirements-config-files
templates
templates
testing
theme-extension
twitch
twitter
typescript
typescript
understanding-populate
upgrade-to-apollov4
upgrade-tool
upgrades
upload
usage-information
use-document-id
users-and-permissions-providers
vk
webhooks
wysiwyg-editor
yarn-not-default