Entity configuration reference
Edit on GitHubThis document explains how to configure entity UI using YAML files in Composable UI modules.
Overview
An entity configuration file is a YAML file that describes the entire UI for one entity (like Customer, Product, Order). The file is located at resources/entity/{entity}.yml in your module.
Composable UI supports three configuration formats:
| Format | Description | Use case |
|---|---|---|
| Auto-generated | Minimal configuration, UI is fully generated | Standard CRUD operations |
| Partial override | Auto-generated base with specific customizations | CRUD with minor tweaks |
| Custom mode | Full manual control over every component | Complex or non-standard UIs |
Choosing the right format
┌─────────────────────────────────────────────────────────────────┐
│ Do you need standard CRUD? │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
YES NO
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ Do you need to tweak │ │ Custom mode │
│ pagination, headlines, │ │ (full manual control) │
│ or specific fields? │ └─────────────────────────┘
└─────────────────────────┘
│
┌─────────┴─────────┐
▼ ▼
YES NO
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Partial │ │ Auto- │
│ override │ │ generated │
└─────────────┘ └─────────────┘
Format 1: Auto-generated
The simplest format. Define fields and UI sections—the system generates everything else.
Complete example
Auto-generated mode example
entity: Customer
navigation:
title: 'Customers'
fields:
customerReference:
readonly: true
searchable: true
email:
type: email
required: true
searchable: true
firstName:
required: true
searchable: true
lastName:
required: true
searchable: true
salutation:
type: select
required: true
datasource:
url: /salutations
filterable: true
dateOfBirth:
type: date
createdAt:
type: date
label: Registration Date
format: dd.MM.y
filterable: true
reference:
type: hidden
label: reference
ui:
list:
columns:
- customerReference
- email
- salutation
- firstName
- lastName
- createdAt
rowAction: edit
create:
fields:
- email
- firstName
- lastName
- salutation
edit:
fields:
- email
- firstName
- lastName
- createdAt
- salutation
What gets auto-generated
From this configuration, the system automatically creates:
- Table with 6 columns, pagination (5, 10, 20), and search
- Filters for
salutationandcreatedAt(fields withfilterable: true) - “Create” button that opens a drawer with 4 fields
- Row click opens edit drawer with 5 fields and delete button
- All forms have validation, success/error notifications, and proper actions
Structure breakdown
entity
The entity name in singular form. Maps to:
- API resource:
/customers(plural) - Database table:
spy_customer
navigation
navigation:
title: 'Customers' # Title shown in breadcrumbs
fields
Define all fields used in forms and tables:
fields:
fieldName:
type: string # Field type (default: string)
label: 'Field Label' # Display label (auto-generated if not specified)
required: true # Required in forms
readonly: true # Cannot be edited
searchable: true # Included in table search
filterable: true # Adds filter to table
format: dd.MM.y # Display format for dates
datasource: # For select fields
url: /options
Field types:
| Type | Description | Form control |
|---|---|---|
string |
Text (default) | Text input |
email |
Email with validation | Email input |
date |
Date value | Date picker |
select |
Dropdown selection | Select with options |
hidden |
Not visible | Hidden input |
number |
Numeric value | Number input |
textarea |
Multi-line text | Text area |
checkbox |
Boolean value | Checkbox |
toggle |
On/off switch | Toggle switch |
radio |
Single selection | Radio buttons |
ui
Define what appears in list, create, and edit views:
ui:
list:
columns: [field1, field2, field3] # Columns to show in table
rowAction: edit # Action on row click
create:
fields: [field1, field2] # Fields in create form
edit:
fields: [field1, field2, field3] # Fields in edit form
Format 2: Partial override
Start with auto-generated configuration, then override specific components.
Complete example
Partial override example
entity: Customer
navigation:
title: 'Customers (Partial Override)'
fields:
customerReference:
readonly: true
searchable: true
email:
type: email
required: true
searchable: true
firstName:
required: true
searchable: true
lastName:
required: true
searchable: true
salutation:
type: select
required: true
datasource:
url: /salutations
filterable: true
createdAt:
type: date
label: Registration Date
format: dd.MM.y
filterable: true
ui:
list:
columns:
- customerReference
- email
- salutation
- lastName
- createdAt
rowAction: edit
create:
fields:
- email
- firstName
- lastName
- salutation
edit:
fields:
- email
- firstName
- lastName
- createdAt
- salutation
# Override specific components
view:
components:
headline.customer.edit:
style:
background-color: 'var(--spy-red)'
contains:
content: 'Custom: Update ${row.customerReference} Customer'
table.customer.list:
pagination: [25, 50, 100]
search: 'Search by name or email...'
How it works
- System generates all components from
fieldsanduisections - You add
view.componentswith only the parts you want to change - Deep merge combines your overrides with auto-generated components
- Result: Only specified properties are overridden
Component IDs for overrides
Auto-generated components have predictable IDs:
| Component | ID Pattern | Example |
|---|---|---|
| Table | table.{entity}.list |
table.customer.list |
| Create form | form.{entity}.create |
form.customer.create |
| Edit form | form.{entity}.edit |
form.customer.edit |
| Create headline | headline.{entity}.create |
headline.customer.create |
| Edit headline | headline.{entity}.edit |
headline.customer.edit |
| Field | field.{entity}.{fieldName} |
field.customer.email |
Common overrides
Custom pagination:
view:
components:
table.customer.list:
pagination: [25, 50, 100]
Custom search placeholder:
view:
components:
table.customer.list:
search: 'Search customers...'
Custom headline styling:
view:
components:
headline.customer.edit:
style:
background-color: 'var(--spy-red)'
contains:
content: 'Edit: ${row.customerReference}'
Format 3: Custom mode
Full manual control over every UI component. Uses a simplified syntax.
Complete example
Custom mode example
entity: Customer
navigation:
title: 'Customers (Custom)'
ui:
mode: custom
view:
layout:
use: layout.customer.page
components:
# ═══════════════════════════════════════════════════════════
# LAYOUT
# ═══════════════════════════════════════════════════════════
layout.customer.page:
component: LayoutComponent
id: 'page-layout'
virtualRoute: 'root'
className: 'page-layout'
contains:
actions:
- use: action.customer.create
content:
- use: table.customer.list
# ═══════════════════════════════════════════════════════════
# FIELDS
# ═══════════════════════════════════════════════════════════
field.customer.email:
type: email
label: 'Email'
required: true
searchable: true
field.customer.firstName:
label: 'First Name'
required: true
searchable: true
field.customer.lastName:
label: 'Last Name'
required: true
searchable: true
field.customer.salutation:
type: select
label: 'Salutation'
required: true
datasource:
url: '/salutations'
field.customer.registrationDate:
type: date
label: 'Registration Date'
field.customer.reference:
type: hidden
# ═══════════════════════════════════════════════════════════
# HEADLINES
# ═══════════════════════════════════════════════════════════
headline.customer.create:
component: HeadlineComponent
level: 'h3'
style:
background-color: 'var(--spy-white)'
padding: '15px 30px'
contains:
content: 'Create New Customer'
headline.customer.edit:
component: HeadlineComponent
level: 'h3'
style:
background-color: 'var(--spy-white)'
padding: '15px 30px'
contains:
content: 'Update ${row.customerReference} Customer'
actions:
- use: form.customer.delete
# ═══════════════════════════════════════════════════════════
# FORMS
# ═══════════════════════════════════════════════════════════
form.customer.create:
component: DynamicFormComponent
style:
padding: '30px'
fields:
- use: field.customer.email
- use: field.customer.firstName
- use: field.customer.lastName
- use: field.customer.salutation
submit:
label: 'Create'
url: '/customers'
success: 'The customer is created.'
error: 'Failed to create customer.'
form.customer.edit:
component: DynamicFormComponent
style:
padding: '30px'
fields:
- use: field.customer.email
- use: field.customer.firstName
- use: field.customer.lastName
- use: field.customer.registrationDate
- use: field.customer.salutation
submit:
label: 'Save'
url: '/customers/${row.customerReference}'
success: 'The customer is saved.'
error: 'Failed to save customer.'
form.customer.delete:
component: DynamicFormComponent
slot: 'actions'
fields:
- use: field.customer.reference
submit:
label: 'Delete'
url: '/customers/${row.customerReference}'
variant: 'critical'
success: 'The customer is deleted.'
error: 'Failed to delete customer.'
# ═══════════════════════════════════════════════════════════
# BUTTON
# ═══════════════════════════════════════════════════════════
action.customer.create:
component: ButtonActionComponent
contains:
content: 'Create Customer'
action:
type: 'drawer'
drawer:
- use: headline.customer.create
- use: form.customer.create
# ═══════════════════════════════════════════════════════════
# TABLE
# ═══════════════════════════════════════════════════════════
table.customer.list:
component: TableComponent
id: 'customer-table'
dataSource:
url: '/customers'
columns:
- { id: 'customerReference', title: 'Reference' }
- { id: 'email', title: 'Email' }
- { id: 'salutation', title: 'Salutation' }
- { id: 'firstName', title: 'First Name' }
- { id: 'lastName', title: 'Last Name' }
- id: 'registrationDate'
title: 'Registration Date'
type: 'date'
format: 'dd.MM.y'
filters:
- id: 'salutation'
title: 'Salutation'
type: 'select'
datasource:
url: '/salutations'
- { id: 'registrationDate', title: 'Registered', type: 'date-range' }
pagination: [5, 10, 20]
search: 'Search customers...'
rowClick:
drawer:
- use: headline.customer.edit
- use: form.customer.edit
Structure breakdown
Required settings
ui:
mode: custom # Enables custom mode
view:
layout:
use: layout.customer.page # Entry point component
components:
# All components defined here
Component types
| Component | Purpose |
|---|---|
LayoutComponent |
Page container with slots for actions and content |
TableComponent |
Data table with columns, filters, pagination |
DynamicFormComponent |
Forms for create, edit, delete |
HeadlineComponent |
Headers with optional action buttons |
ButtonActionComponent |
Action buttons that open drawers |
For a complete list of available components and their inputs, see the Spryker UI Components Library.
Component inputs
Important: Available inputs vary by component. Check the Spryker UI Components Library to see which inputs each component supports.
Common examples:
# HeadlineComponent - 'level' input for heading size
headline.customer.edit:
component: HeadlineComponent
level: 'h3'
# TableComponent - pagination and search
table.customer.list:
component: TableComponent
pagination: [10, 25, 50]
search: 'Search...'
# DynamicFormComponent - fields and submit
form.customer.edit:
component: DynamicFormComponent
fields:
- use: field.customer.email
submit:
label: 'Save'
url: '/customers/${row.id}'
Component slots with contains
The contains keyword defines content for component slots. Each component type has different available slots.
Syntax:
contains:
content: 'text content' # Simple text
action: # Or nested components
- use: {component-id}
# LayoutComponent - has 'actions' and 'content' slots
layout.customer.page:
component: LayoutComponent
contains:
actions:
- use: action.customer.create
content:
- use: table.customer.list
# ButtonActionComponent - has 'content' slot
action.customer.create:
component: ButtonActionComponent
contains:
content: 'Create Customer'
# TagComponent - has 'label' slot
tag.customer.create:
component: TagComponent
contains:
label: 'Tag Customer'
Important: Available slots vary by component. Check the Spryker UI Components Library to see which slots each component supports.
Component references with use
The use keyword references a component defined elsewhere in view.components. This enables:
- Reusability: Define a component once, use it in multiple places
- Composition: Build complex UIs by combining smaller components
- Overrides: Modify specific properties when reusing
Syntax:
- use: {component-id} # Reference by component ID
overrides: # Optional: override specific properties
property: 'new value'
How it works:
- Define components in
view.componentswith unique IDs, for example,field.customer.email,form.customer.edit - Reference them using
use: {component-id} - The system replaces
usewith the full component definition at runtime
Examples:
# In form fields - reference field components
fields:
- use: field.customer.email # Use as-is
- use: field.customer.firstName
overrides:
value: '${row.firstName}' # Override specific property
# In drawers - reference headline and form components
rowClick:
drawer:
- use: headline.customer.edit # First component in drawer
- use: form.customer.edit # Second component in drawer
# In layout slots - reference action buttons and tables
contains:
actions:
- use: action.customer.create # Button in actions slot
content:
- use: table.customer.list # Table in content slot
Component ID naming convention:
field.{entity}.{fieldName}- Form fields, for example,field.customer.emailform.{entity}.{action}- Forms, for example,form.customer.create,form.customer.editheadline.{entity}.{action}- Headlines, for example,headline.customer.edittable.{entity}.list- Tables, for example,table.customer.listaction.{entity}.{action}- Action buttons, for example,action.customer.createlayout.{entity}.page- Page layouts, for example,layout.customer.page
Variable interpolation
Use ${row.fieldName} to insert values from the current table row:
# In URLs
url: '/customers/${row.customerReference}'
# In headlines
content: 'Edit ${row.customerReference}'
# In field values
overrides:
value: '${row.email}'
Reference
Field types
| Type | Description | Example |
|---|---|---|
string |
Text input (default) | Name, description |
email |
Email with validation | Email address |
date |
Date picker | Birth date, created date |
select |
Dropdown with options | Status, category |
hidden |
Not visible to user | IDs, references |
number |
Numeric input | Age, quantity |
textarea |
Multi-line text | Description, notes |
checkbox |
Boolean checkbox | Subscribe, agree to terms |
toggle |
On/off switch | Active, enabled |
radio |
Radio button group | Gender, priority |
Field properties
| Property | Type | Description |
|---|---|---|
type |
string | Field type |
label |
string | Display label |
required |
boolean | Required in forms |
readonly |
boolean | Cannot be edited |
searchable |
boolean | Included in table search |
filterable |
boolean | Adds filter to table |
format |
string | Display format, for example, dd.MM.y |
datasource |
object | Dynamic options from API (for select fields) |
options |
array | Static options (for select/radio fields) |
Select field options
For select fields, you can provide options in two ways:
Static options (defined in YAML):
fields:
salutation:
type: select
label: 'Salutation'
options:
- { value: 'mr', title: 'Mr' }
- { value: 'mrs', title: 'Mrs' }
- { value: 'ms', title: 'Ms' }
Dynamic options (fetched from API):
fields:
salutation:
type: select
label: 'Salutation'
datasource:
url: '/salutations'
valueField: 'value' # Optional, default: 'value'
titleField: 'title' # Optional, default: 'title'
Use options for small, static lists. Use datasource when options come from the database or need to be dynamic.
Validators
| Validator | Description | Example |
|---|---|---|
required: true |
Field must have value | All required fields |
email: true |
Valid email format | Email fields |
minLength: N |
Minimum string length | minLength: 3 |
maxLength: N |
Maximum string length | maxLength: 100 |
min: N |
Minimum number | min: 0 |
max: N |
Maximum number | max: 100 |
pattern: 'regex' |
Regex pattern | pattern: '^[A-Z]+$' |
HTTP methods
| Operation | Method | URL pattern |
|---|---|---|
| Create | POST |
/entities |
| Update | PATCH |
/entities/${row.id} |
| Delete | DELETE |
/entities/${row.id} |
Troubleshooting
Form doesn’t pre-fill in edit mode
Add overrides with ${row.fieldName}:
fields:
- use: field.customer.email
overrides:
value: '${row.email}'
Table row click doesn’t open drawer
Add rowClick (custom mode) or rowAction: edit (auto-generated):
# Auto-generated
ui:
list:
rowAction: edit
# Custom mode
rowClick:
drawer:
- use: headline.customer.edit
- use: form.customer.edit
Table doesn’t refresh after form submit
Ensure refresh-table action is included (automatic in simplified syntax):
submit:
success: 'Saved!' # Includes close-drawer and refresh-table by default
Date shows raw timestamp
Add type: date and format:
columns:
- id: 'createdAt'
title: 'Created'
type: 'date'
format: 'dd.MM.y'
Next steps
Thank you!
For submitting the form