Deck Forms
Deck forms allow you to create and edit individual items within a deck property. Deck properties contain multiple sub-items, each following a schema referenced by schemaref.
Understanding Deck Properties
A deck property is a special property type that contains multiple sub-items. Each item has its own ID and follows a schema defined by the deck's schemaref:
{
"features": {
"type": "deck",
"label": "Product Features",
"schemaref": "https://www.totalcms.co/schemas/custom/feature-item.json",
"settings": {
"help": "Add individual feature items for this product"
}
}
}
Legacy alias: the
deckrefkey is still accepted as an alias forschemarefand will continue to work indefinitely. New schemas should useschemaref. When a schema is saved, the canonicalschemarefform is written; if both keys are present in the input, the duplicatedeckrefis dropped on save.
The referenced schema (feature-item.json) defines the structure of each deck item:
{
"$id": "https://www.totalcms.co/schemas/custom/feature-item.json",
"title": "Feature Item",
"type": "object",
"required": ["id", "title"],
"properties": {
"id": {
"type": "string",
"label": "Feature ID"
},
"title": {
"type": "string",
"label": "Feature Title"
},
"description": {
"type": "string",
"label": "Description",
"field": "textarea"
}
}
}
Auto-Built Deck Forms
The simplest way to create a deck item form:
{# Create new deck item #}
{{ cms.form.deck('products', productId, 'features') }}
{# Edit existing deck item #}
{{ cms.form.deck('products', productId, 'features', {itemId: 'feature-1'}) }}
Parameters:
- collection (string) - The parent collection name (e.g., 'products')
- id (string) - The parent object ID (e.g., product ID)
- property (string) - The deck property name (e.g., 'features')
- options (object) - Optional configuration (see below)
Deck Form Builder
For custom layouts and advanced control:
{# Create a deck form builder instance #}
{% set form = cms.form.deckBuilder('products', productId, 'features') %}
{# Build custom layout #}
{{ form.build(
form.field('id') ~
form.field('title') ~
form.field('description') ~
form.field('icon')
) }}
Deck Form Options
All standard form options are supported, plus deck-specific options:
{{ cms.form.deck('products', productId, 'features', {
itemId: 'feature-1', # Deck item ID (empty for new items)
save: 'Save Feature', # Custom save button text
delete: 'Delete Feature', # Custom delete button text
class: 'feature-form', # Additional CSS classes
helpOnHover: true, # Show help on hover
newActions: [ # Actions after creating new item
{
action: 'redirect',
link: '/products/{parentId}'
}
],
editActions: [ # Actions after editing item
{
action: 'refresh'
}
]
}) }}
How Deck Forms Work
- Auto-detection: The form automatically detects the
schemaref(or legacydeckref) from the property's schema - Schema Loading: Loads the deck schema (e.g.,
feature-item.json) for field definitions - Data Loading: If
itemIdis provided, loads existing deck item data - API Routes: Automatically sets correct routes:
- New items:
POST /collections/{collection}/{id}/{property}/deck - Edit items:
PUT /collections/{collection}/{id}/{property}/deck/{itemId}
- New items:
Query Parameter Support
Deck forms support automatic ID detection from URL parameters:
{# Form will read ?id=product-123&itemId=feature-1 from URL #}
{{ cms.form.deck('products', '', 'features') }}
This is useful for admin interfaces where IDs come from the URL.
Examples
Product Features Manager
{# products/edit.twig #}
{% set product = cms.collection.object('products', productId) %}
<h2>{{ product.name }} - Manage Features</h2>
{# List existing features #}
{% if product.features %}
<ul>
{% for featureId, feature in product.features %}
<li>
{{ feature.title }}
<a href="/admin/products/{{ productId }}/features/{{ featureId }}">Edit</a>
</li>
{% endfor %}
</ul>
{% endif %}
{# Add new feature button #}
<a href="/admin/products/{{ productId }}/features/new">Add New Feature</a>
{# products/feature-edit.twig #}
<h2>Edit Feature</h2>
{# Auto-built form with custom actions #}
{{ cms.form.deck('products', productId, 'features', {
itemId: featureId,
save: 'Save Feature',
delete: 'Delete Feature',
editActions: [
{
action: 'redirect',
link: '/admin/products/' ~ productId
}
],
deleteActions: [
{
action: 'redirect',
link: '/admin/products/' ~ productId
}
]
}) }}
Custom Layout with Form Builder
{# Custom two-column layout for deck items #}
{% set form = cms.form.deckBuilder('products', productId, 'features', {
itemId: featureId,
save: 'Save Feature'
}) %}
{# Build custom layout #}
{% set col1 = form.field('id') %}
{% set col1 = col1 ~ form.field('title') %}
{% set col1 = col1 ~ form.field('subtitle') %}
{% set col2 = form.field('icon', {field: 'image'}) %}
{% set col2 = col2 ~ form.field('description', {field: 'styledtext'}) %}
{% set col2 = col2 ~ form.field('priority', {field: 'number'}) %}
{% set layout = form.layout2Columns(col1, col2) %}
{{ form.build(layout) }}
Portfolio Project Items
{# Portfolio collection with "projectItems" deck property #}
{% set form = cms.form.deckBuilder('portfolio', portfolioId, 'projectItems', {
itemId: itemId,
newActions: [
{
action: 'message',
text: 'Project item added successfully!'
},
{
action: 'redirect',
link: '/admin/portfolio/' ~ portfolioId
}
]
}) %}
{{ form.build(
form.field('id') ~
form.field('title') ~
form.field('image', {
settings: {
rules: {
width: {min: 800, max: 2400},
aspectratio: '16:9'
}
}
}) ~
form.field('description') ~
form.field('tags', {field: 'list'})
) }}
Best Practices
- Always provide collection, parent ID, and property name for deck forms
- Use
itemIdin options to edit existing items - Set appropriate actions to redirect users after save/delete
- Leverage form builder for complex custom layouts
- Use query parameters for admin interfaces with URL-based routing
Common Use Cases
- Product Features - Individual features for products
- FAQ Items - Question/answer pairs for FAQ pages
- Team Members - Individual team member profiles
- Portfolio Items - Project details in a portfolio
- Testimonials - Individual customer testimonials
- Timeline Events - Events in a timeline
- Pricing Tiers - Individual pricing plan details