Status: Draft
Updated: 2026-05-23
Owner: Core
Purpose: Draft for database-backed content schemas, variable fieldsets, schema-based validation, and user-defined content types.
- Defines how content types can use configurable fieldsets.
- Defines database-backed schemas as first-class content-type definitions that users can create and edit.
- Explains how shipped presets provide a complete default CMS experience while custom schemas extend project-specific modeling.
- Covers schema storage, validation, rendering metadata, persistence, and migration concerns.
- Depends on the static/dynamic content model and editor experience drafts.
- Separates flexible content fields from package-owned domain tables.
Schema-driven fields should let editors model project-specific content without changing database schema for every content type. This is useful for page sections, repeatable content metadata, flexible editor forms, import/export, API output, custom inner content rendering, and generic fallback rendering.
Content schemas are database-backed content-type definitions. A schema defines which fields a content type has, which values each field expects, which fields are required or localized, how values are validated, and how the schema's inner content block may be rendered through optional custom Twig. Users should be able to create and maintain these schemas through the admin UI without writing migrations or PHP classes for normal content-model changes.
The CMS must still be fully usable without custom schema authoring. Shipped factory presets should provide complete, production-ready content types for common website needs such as static pages, list views, blog posts, and project hubs. Custom schemas then let advanced users and site builders extend, copy, or replace those defaults when a project needs a more specific model.
Fieldsets should not become a dumping ground for every kind of package data. Package-owned domain data should prefer package-owned tables and migrations when it needs durable structure, relationships, or efficient queries. Schema fields should focus on content-like extension data that belongs to CMS content items.
Relationship fields are part of the content model when they describe editorial relationships between content entities. A schema may define direct reference fields for selecting one or more target entities, and controlled query/list fields for resolving dynamic sets such as all memories linked to a character, all locations used in a scene, or all entries matching a project-specific filter. These fields should let users build navigable project graphs without inventing custom tables for every editorial relationship.
The first implementation should support a small set of field types, clear validation, deterministic serialization, editor rendering metadata, complete schema presets, immutable factory schemas, editable custom schemas, and stable mapping between schema definitions and stored field values. More advanced field types can be added through tagged services once the field type contract is stable.
Variable fieldsets need efficient assembly. The database should not have to scan all field values on every content request just to determine which values belong to an entity, language, variant, schema field, or relationship. A lean resolver/reference index should help the query layer locate relevant field values, relationship edges, and query targets while the actual field content remains stored in the normal content/field tables.
- Define content schemas separately from content values.
- Store schema definitions as versioned database records or payloads that include field definitions, validation rules, localization flags, rendering metadata, and optional custom Twig.
- Ship immutable factory schema presets for common use cases such as static pages, list views, blog posts, and project hubs so the CMS is usable immediately after installation.
- Require every schema preset and custom schema to include the reserved base field identifiers
titleandsubtitle. - Let users copy factory schemas to create editable custom schemas, and let users create new schemas when a project needs a bespoke content type.
- Save schema edits as new schema versions instead of mutating an existing schema in place.
- Use a nullable
active_version_uidon schemas so a schema can be disabled, staged, restored, or cleaned up without deleting its historical versions. - Store exact schema-version references on content revisions so each stored fieldset remains traceable to the schema definition that validated it.
- Store field values in a structure that supports validation, rendering, export, and search indexing.
- Store each field value with the associated revision UID, language, generic variant, field identifier, and field content.
- Use
field_identifierto map each stored field value back to its schema field definition. - Keep entity-level flexible metadata separate from schema field values. Reserved base fields such as
titleandsubtitlemust be schema field values so validation, localization, previews, menus, search output, generic rendering, API output, and import/export share one source of truth. - Keep durable content identity separate from field values.
- Start with simple field types such as text, textarea, rich text/markdown, boolean, number, date/time, select, media reference, content reference, repeatable group, and controlled content-query/list fields.
- Let reference field definitions declare cardinality such as one target, many targets, required target, optional target, and allowed target schemas.
- Let inverse relationship views be derived from indexed references where practical instead of requiring duplicated back-reference fields.
- Let controlled content-query/list fields define safe filters, ordering, pagination, target schemas, language, generic variant, and ACL behavior.
- Execute content-query/list fields through controlled content-query services with documented filters, ordering, pagination, language, and variant behavior.
- Keep query fields declarative. Field content may store parameters and filters, but execution must remain inside trusted query services.
- Allow schemas to store optional custom Twig markup for inner fieldset rendering, validated before saving or activation.
- Consider a safety dictionary for allowed and disallowed Twig functions, filters, tags, or variables in schema-rendering markup.
- Aggregate CSS class usage from active custom schema Twig into a generated Tailwind build input, because
tailwind:buildcannot scan Twig markup that only exists in the database. - Use Symfony Validator constraints where possible.
- Use field-type services for custom validation, form building, rendering metadata, normalization, and export behavior.
- Register field types through tagged services.
- Keep field values serializable for import/export and API output.
- Track schema changes that may affect existing content values.
- When a schema change is proposed, lint existing content values against the new schema version.
- Present affected fields and entities before activation when values would become invalid or obsolete.
- Let the user abort and resolve issues manually, or continue when incompatible and obsolete fields should be removed.
- Allow saving a changed schema as a new schema and manually moving selected entities to it when automatic migration is too risky.
- Provide migration or review flows for schema changes that invalidate existing content.
- Define how field references participate in cross-reference indexing.
- Define how reference fields and query fields contribute to context graphs for search, exports, and LLM collaboration.
- Ensure reference and query fields expose stable field identifiers so resolver tokens can target specific fields.
- Let the resolver index use field identifiers and content UID/version/language/variant metadata to locate field values efficiently without scanning all field values for every lookup or fieldset assembly.
- Avoid storing unrelated module domain records inside generic field value tables.
- Test schema validation separately from content value validation.
- Test schema validation rejects schemas that omit
titleorsubtitle. - Test shipped factory presets provide usable default content types without requiring custom schema authoring.
- Test users can create custom schemas with field definitions, validation, localization flags, and optional rendering configuration.
- Test each built-in field type with valid, invalid, empty, and localized examples.
- Test field values map back to schema definitions through
field_identifier. - Test entity-level metadata does not bypass schema field validation when a value belongs to a field definition.
- Test language and generic variant field values separately.
- Test controlled content-query/list fields with filters, ordering, pagination, and permissions.
- Test reference field cardinality for single-target and multi-target relationship shapes.
- Test inverse relationship discovery through the cross-reference index where available.
- Test controlled query fields do not bypass ACL restrictions when aggregating related content.
- Test schema Twig validation and safety dictionary behavior once implemented.
- Test schema Twig CSS class aggregation feeds Tailwind builds before schema-authored Tailwind classes are considered production-ready.
- Test serialization round trips for import/export and API output.
- Test schema changes against existing content fixtures.
- Test immutable factory schemas can be copied but not edited directly.
- Test schema edits create new schema versions.
- Test schema change review detects invalid, incompatible, and obsolete fields.
- Test continuing a breaking schema change removes incompatible/obsolete values only after explicit confirmation.
- Test reference fields with missing, unpublished, and moved content.
- Test reference/query field identifiers can be used by resolver-token field targeting.
- Test resolver/index metadata can accelerate fieldset assembly without duplicating full field content.
- Test tagged field type discovery and duplicate field type conflicts.
- Decision proposed: Start with a small core field type set and make field types additive through tagged services.
- Decision proposed: Keep schema values content-focused; use package-owned tables for package domain data.
- Decision recorded: Field values include content UID, version, language, variant, field identifier, and field content. The field identifier maps the value to the schema definition.
- Decision recorded: Entity-level flexible metadata is separate from schema field values;
titleandsubtitleare reserved required base field identifiers that every schema must define. - Decision recorded: Schemas may include controlled content-query/list fields, but execution must go through content-query services rather than arbitrary Twig logic.
- Decision recorded: Relationship fields belong in schemas when they model editorial relationships between content entities. Reference fields and controlled query/list fields should support 1:1, 1:n, n:1, and n:n content relationship shapes.
- Decision recorded: Inverse relationships should be derived from indexed references where practical, avoiding redundant manual back-reference fields.
- Decision recorded: Query/list fields are declarative and must execute through trusted services with documented filters, ordering, pagination, language, variant, and ACL behavior.
- Decision recorded: Reference and query fields must expose stable field identifiers for resolver-token field targeting, efficient index lookup, and efficient variable fieldset assembly.
- Decision recorded: Schemas may store database-backed Twig markup for inner fieldset rendering, validated before saving or activation.
- Decision recorded: Schema Twig needs a generated Tailwind source or safelist layer because database-stored markup is invisible to Tailwind's filesystem scan.
- Decision recorded: Field value storage should be hybrid: explicit relational/indexable metadata plus field content payloads whose storage shape can be defined by the schema or field type.
- Decision recorded: Ship immutable factory schema presets as a complete default CMS content model that users can use immediately or copy into editable custom schemas.
- Decision recorded: Schema edits create new schema versions instead of mutating existing schemas in place.
- Decision recorded: Schema changes must lint existing entity field values and present impacted invalid or obsolete fields before activation.
- Decision recorded: Users may abort a breaking schema change, resolve issues manually, save a new schema, or explicitly continue and remove incompatible/obsolete values.
- Decision recorded: Schemas should be database-backed and editable through admin UI in early releases, using complete presets, strict validation, versioning, preview, and review flows.