Schema Builder
A comprehensive guide for developers using TentoCMS's Schema Builder to create and manage page type and component schemas.
Table of Contents
- Quick Start
- Creating Page Types
- Creating Components
- Using Components
- Field Configuration
- Validation & Error Handling
- Breaking Changes
- Schema Organization
- Common Patterns
- Troubleshooting
Quick Start
Accessing the Schema Builder
- Log in to the TentoCMS admin panel
- Navigate to Settings → Page Types or Settings → Components
- Click + New Page Type or + New Component
- The Schema Builder opens with an empty schema
Basic Workflow
- Enter basic info
- Name: Display name for editors
- Slug: URL-safe identifier (auto-generated from name)
- Add fields
- Click Add Field
- Select field type from the grid
- Configure field properties
- Save
- Click Save
- Address any validation errors
- Schema is published immediately
Creating Page Types
Page types define the structure for all pages of that type. They're templates for content editors.
Step-by-Step: Create a Blog Post Page Type
1. Navigate to Page Types
Settings → Page Types → Create Page Type
2. Enter Basic Information
- Name:
Blog Post - Slug:
blog-post(auto-generated)
The slug identifies your page type in the system. Use lowercase, hyphens only.
3. Build Your Schema
Add fields in order:
Field 1: Post Title
- Type:
text - Label:
Post Title - Required:
Yes - Options:
- Placeholder:
Enter the article title - Max Length:
200
- Placeholder:
Field 2: Author
- Type:
reference - Label:
Author - Required:
Yes - Options:
- Collection Type:
authors
- Collection Type:
Field 3: Publish Date
- Type:
datetime - Label:
Publish Date - Required:
No
Field 4: Featured Image
- Type:
image - Label:
Featured Image - Required:
No
Field 5: Post Content
- Type:
wysiwyg - Label:
Post Content - Required:
Yes
Field 6: Tags
- Type:
reference - Label:
Tags - Required:
No - Options:
- Collection Type:
tags - Multiple:
Yes
- Collection Type:
4. Review and Save
Check your schema is complete:
- All required fields have descriptive labels
- Options are set appropriately
- Fields are in logical order
Click Save to create the page type.
Creating Components
Components are reusable schema fragments that can be embedded in page types or composed together.
Step-by-Step: Create a Hero Component
1. Navigate to Components
Settings → Components → Create Component
2. Enter Basic Information
- Name:
Hero Section - Slug:
hero
3. Build Your Schema
Field 1: Headline
- Type:
text - Label:
Headline - Required:
Yes - Options:
- Max Length:
200 - Placeholder:
Main headline
- Max Length:
Field 2: Subheading
- Type:
textarea - Label:
Subheading - Required:
No - Options:
- Max Length:
500
- Max Length:
Field 3: Background Image
- Type:
image - Label:
Background Image - Required:
Yes
Field 4: Call-to-Action Buttons
- Type:
componentPicker - Label:
CTA Buttons - Required:
No - Options:
- Allowed Components:
button,icon-button - Max Items:
2
- Allowed Components:
4. Save
Click Save. The Hero component is now available for use in page types and other components.
Using Components
In Page Types
Embed components to create flexible page structures.
Using Nested Component (Single, Required)
Example: Every page must have a header.
Page Type: Landing Page
├── header (nestedComponent → "header" component)
├── hero (nestedComponent → "hero" component)
└── sections (componentPicker → multiple components)
Schema field:
{
"name": "header",
"label": "Page Header",
"type": "nestedComponent",
"required": true,
"options": {
"componentSlug": "header"
}
}
Using Component Picker (Multiple, Optional)
Example: Let editors build pages from sections.
Page Type: Landing Page
├── sections
├─ hero component
├─ features component
├─ testimonials component
└─ [add more...]
Schema field:
{
"name": "sections",
"label": "Page Sections",
"type": "componentPicker",
"required": false,
"options": {
"allowedComponents": ["hero", "features", "testimonials", "cta"],
"maxItems": 10
}
}
In Other Components
Components can reference other components for modular design.
Example: Hero component allows button components.
Hero Component
├── title (text)
├── background (image)
└── buttons (componentPicker → button components)
Editors can customize the hero's buttons without creating new components.
Field Configuration
Adding Required vs Optional Fields
Required fields (required: true)
- Enforced at publish time, not on save — editors can save (and keep saving) a draft with required fields left empty; the API only blocks the action when they try to publish
- Use for content that must exist before a page or item goes live
Optional fields (required: false)
- Can be left empty
- Use for supplementary content
- Provide defaults when possible
Naming Fields
Good field names:
- Use camelCase:
firstName,productId - Be descriptive:
seoTitle(not justtitle) - One per line in the UI
- Avoid abbreviations
Field names are auto-generated from labels:
Label: "Featured Image" → name: "featuredImage"
Label: "Meta Description" → name: "metaDescription"
Edit the name manually if you need different format.
Using Field Options
Every field type has specific options:
Text fields - Set character limits:
{
"maxLength": 200,
"minLength": 10
}
Number fields - Set numeric ranges:
{
"min": 0,
"max": 100,
"step": 5
}
Reference fields - Link to other content. referenceType picks what the field links to (defaults to collectionItem); collectionType/pageType are only relevant for their matching mode:
// Default mode — links to a collection item
{
"referenceType": "collectionItem", // or omit — this is the default
"collectionType": "authors",
"multiple": true
}
There are three other referenceType modes, each with its own picker in the field editor's "Reference Target" dropdown:
referenceType | Links to | Extra option |
|---|---|---|
collectionItem (default) | An item in a collection type | collectionType (required) |
page | A page | pageType (optional filter — leave empty to allow any page type) |
blogPost | A blog post | — |
user | A user | — |
page/blogPost/user modes must not set collectionType; blogPost/user modes must not set pageType either — the field editor hides irrelevant options automatically when you switch referenceType.
Component fields - Limit allowed components:
{
"allowedComponents": ["button", "icon-button"],
"maxItems": 3
}
Repeater fields - A simpler alternative to componentPicker for repeatable groups of plain fields (text, number, image, etc. — not other components). Define the sub-fields once and editors add/reorder/remove items:
{
"name": "faqs",
"label": "FAQs",
"type": "repeater",
"required": false,
"options": {
"minItems": 0,
"maxItems": 20,
"fields": [
{ "name": "question", "label": "Question", "type": "text", "required": true },
{ "name": "answer", "label": "Answer", "type": "textarea", "required": true }
]
}
}
Reach for repeater when each item is just a handful of simple fields (an FAQ list, a set of stats); reach for componentPicker when items need to vary in type or reuse an existing component definition. Sub-fields inside a repeater can't themselves be componentPicker, nestedComponent, or another repeater — keep nested structures to componentPicker/nestedComponent instead.
Validation & Error Handling
Common Validation Errors
Duplicate Field Name
Error: Duplicate field name: title
Problem: Two fields have the same name
Fix: Rename one field to be unique
❌ title, title
✅ title, postTitle
Invalid Length Range
Error: minLength cannot be greater than maxLength
Problem: minLength is set higher than maxLength
Fix: Correct the values
❌ minLength: 200, maxLength: 100
✅ minLength: 10, maxLength: 200
Invalid Component Reference
Error: Component "nonexistent" does not exist
Problem: Referencing a component that doesn't exist
Fix:
- Check the component slug is correct
- Create the component first, then reference it
- Refresh to see newly created components
Circular Reference
Error: A component cannot reference itself
Problem: Component A references Component A (directly or indirectly)
Fix: Remove the self-reference
❌ Component A → nestedComponent → Component A
✅ Component A → nestedComponent → Component B
Nesting Limit Exceeded (not currently reachable)
Error: Component nesting is limited to 1 level
The validator defines this check, but no current save path (POST/PUT on page types or components) ever triggers it — every caller always validates at a fixed nesting level of 0, and that level is never incremented when validation recurses into a nested component's own schema. In practice, you can nest nestedComponent chains deeper than 1 level today (e.g. Component A → nestedComponent B → nestedComponent C) without hitting this error — only genuine circular references (a component pointing back to itself, directly or through a chain) are actually rejected. Deep, non-circular nesting is still worth avoiding for clarity and editor usability, but it is not currently blocked by validation.
Viewing Validation Errors
Errors appear in two places:
- Above the problematic field - Inline error messages
- At the top of the form - Summary of all errors
Address errors in order and save again.
Breaking Changes
When modifying a page type or component that has content, the API warns about breaking changes.
Breaking Change Types
FIELD_REMOVED
What: You deleted a field that exists in live content
Impact: Pages lose access to this data
Example:
Old schema: [title, content, author]
New schema: [title, content]
Breaking change: author field removed
Decision:
- Safe if the field wasn't widely used
- Creates orphaned data (still in database but hidden)
- Can be re-enabled by adding the field back
FIELD_TYPE_CHANGED
What: You changed a field's type
Impact: Existing content might become invalid
Example:
Old: author (reference)
New: author (text)
Breaking change: Type changed from reference to text
Decision:
- Data loses its structure
- Old IDs become text strings
- Might need manual migration
FIELD_NOW_REQUIRED
What: You made an optional field required
Impact: Existing pages without this field are now invalid
Example:
Old: seoTitle (optional)
New: seoTitle (required)
Breaking change: Field is now required
Decision:
- Existing pages must be edited to add values
- New pages will require the field
- Can create content management burden
Handling Breaking Changes
When you see warnings:
- Read the warning - Understand what changed
- Assess impact - How many pages are affected?
- Plan migration - Do you need to update pages?
- Decide to proceed - Is the change necessary?
The API still saves if you proceed. The warning is advisory.
Schema Organization
Ordering Fields Logically
Arrange fields in the order editors will fill them:
Blog Post:
1. title (essential, at top)
2. author (key metadata)
3. publishedAt (scheduling)
4. featuredImage (visual)
5. content (main work)
6. tags (categorization)
7. seoTitle (technical)
8. seoDescription (technical)
Group by purpose:
- Essential fields first
- Metadata middle
- Technical/SEO last
Naming Conventions
Consistent naming:
✅ postTitle, postContent, postAuthor
❌ title, content, author_name
Prefixes for clarity:
seoTitle, seoDescription (SEO fields)
metaAuthor, metaKeywords (Metadata fields)
internalNotes, internalStatus (Admin-only fields)
Schema Size
Keep schemas focused:
- 5-15 fields per page type
- 3-8 fields per component
- Too many fields overwhelms editors
Consider splitting:
❌ ProductPage: 25 fields
✅ ProductPage: core + section components for extensibility
Common Patterns
Blog Site
Page Types:
blog-post- Individual articlesblog-index- Blog listing pageblog-category- Category page
Components:
post-card- Blog post previewauthor-bio- Author informationrelated-posts- Related articles list
E-Commerce Product
Page Types:
product- Product detail page
Components:
product-header- Title, price, ratingproduct-images- Image galleryproduct-description- Details and specsproduct-reviews- Customer reviewsrelated-products- Cross-sell itemsadd-to-cart-button- Purchase CTA
Landing Page
Page Types:
landing-page- Marketing page
Components:
hero- Hero sectionfeatures- Feature gridtestimonials- Customer quotespricing-table- Pricing optionscta-section- Call-to-actionfaq- Frequently asked questions
Troubleshooting
Component Not Showing in Dropdown
Problem: Created a component but can't select it
Solution:
- Ensure component is saved (no errors)
- Refresh the page
- Check component slug matches exactly
- Make sure it's not soft-deleted
Can't Add Field Due to "Circular Reference"
Problem: Error when adding component reference
Solution:
- Check if component references itself
- Check for indirect chains (A→B→A)
- Try adding to a different component first
- Verify component hierarchy is acyclic
Schema Won't Save
Problem: Getting validation errors on save
Solution:
- Read the error message carefully
- Check for duplicate field names
- Verify component/collection references exist
- Ensure constraint ranges are valid (min ≤ max)
- Check field types are valid
Page Type Can't Be Deleted
Problem: "Cannot delete: X page(s) are using this type"
Solution:
- Delete all pages of this type first
- Archive pages instead of deleting
- Create a new page type and migrate pages to it
- Bulk update pages to different type (if needed)
Component Can't Be Deleted
Problem: Cannot delete component: it is used by N page(s)/item(s) (IN_USE, 409)
This is a content-embedding check, not a schema-reference check. Deletion is only blocked when the component is actually embedded in real page or collection-item content (its usageCount — see Usage Tracking — is greater than zero). A component that is merely listed in another page type's or component's schema (e.g. in a componentPicker's allowedComponents, or as a nestedComponent's componentSlug) but never actually used in any saved content is freely deletable — removing it from those schema references first is not required and won't change whether it can be deleted.
Solution:
- Find and update (or delete) the pages/collection items that actually embed this component
- Check the component's
usageCountin the Components list to see how many records are affected - Once
usageCountreaches 0, the component can be deleted regardless of any schema still listing it as an allowed/nested option
Advanced Topics
Using Placeholders Effectively
Placeholders guide editors on what to enter:
{
"name": "metaDescription",
"label": "Meta Description",
"type": "text",
"options": {
"placeholder": "2-3 sentences describing the page (max 160 chars)"
}
}
Good placeholders:
- Show expected format
- Mention character limits
- Give examples
- Suggest SEO best practices
Help Text for Complex Fields
Use help text to explain non-obvious fields:
{
"name": "relatedProducts",
"label": "Related Products",
"type": "reference",
"options": {
"collectionType": "products",
"multiple": true,
"helpText": "Select up to 5 related products. These will appear in the 'You might also like' section at the bottom of the page."
}
}
helpTextsupports a limited, safe subset of Markdown:**bold**/__bold__,*italic*/_italic_,`code`, and[link text](url)(http/https links only — other protocols are stripped). It's rendered to sanitised HTML wherever the field is shown to editors, so you can use it to add emphasis or link out to fuller documentation. Anything outside that subset (headings, lists, images, etc.) is not supported and renders as plain text.
Creating Versioned Schemas
For major changes, create a new page type:
blog-post (original)
blog-post-v2 (with new fields)
blog-post-v3 (refined)
Migrate pages gradually:
- Create new page type
- Create pages with new type
- Migrate old pages over time
- Eventually remove old type
This avoids breaking changes and allows gradual migration.
Best Practices Summary
- Plan first - Sketch your schema before creating
- Start simple - Add complexity gradually
- Use descriptive labels - Help editors understand fields
- Set constraints early - minLength, maxLength prevent issues
- Group components - Related fields together
- Document via help text - Explain non-obvious fields
- Test with real data - Create test content to validate
- Review breaking changes - Understand impact before saving
- Keep schemas focused - Don't include everything in one type
- Use components for reuse - Don't duplicate field definitions
Related Documentation
- Field Types Reference
- Validation Rules
- Page Types API
- Components API
- Page Management Guide - Creating and editing content
FAQ
Can I rename a field after creating it?
Answer: Not in the same builder session — but the lock has nothing to do with whether any page or item actually has content yet. The Field Name input is disabled as soon as a field has been added to the schema in the builder (i.e. for any field you're editing rather than adding fresh); this is pure UI/session state in the Schema Builder, not a check against existing content. In other words, a brand-new page type with zero pages still locks its field names the moment you add them. If you need a different field name, create a new field with the name you want and migrate data across (data is keyed by name, so a straight rename would orphan any content already stored under the old key).
How many fields can I have?
Answer: No hard limit, but keep under 20 for usability. Use components to organize large schemas.
Can I add fields without updating existing pages?
Answer: Yes. New optional fields don't affect existing content. Existing pages will see the new field as empty.
What happens if I delete a page type?
Answer: Page types are soft-deleted. Existing pages orphaned. Can be restored from database if needed.
Can components reference other components?
Answer: Yes, using nestedComponent (single instance) or componentPicker (multiple). A component can't reference itself, directly or through a chain of other components (that's rejected as a circular reference) — but nesting depth beyond 1 level isn't currently enforced; see Nesting Limit Exceeded above.
What's the difference between nestedComponent and componentPicker?
Answer:
nestedComponent- Single fixed component instancecomponentPicker- Zero or more components, editor-selected and reorderable
required is an independent, settable option on both field types — the same as on any other field — not something nestedComponent always carries. Set required: true on a nestedComponent when a page must always have that section (e.g. every page needs a header); leave it false (the default) when the section is optional. Use nestedComponent for a single fixed slot, componentPicker for a flexible, editor-managed list.
Support & Feedback
For questions or issues:
- Check Field Types Reference
- Review Validation Rules
- See Common Patterns section

