Entity configuration reference

Edit on GitHub

This 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 salutation and createdAt (fields with filterable: 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

  1. System generates all components from fields and ui sections
  2. You add view.components with only the parts you want to change
  3. Deep merge combines your overrides with auto-generated components
  4. 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:

  1. Define components in view.components with unique IDs, for example, field.customer.email, form.customer.edit
  2. Reference them using use: {component-id}
  3. The system replaces use with 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.email
  • form.{entity}.{action} - Forms, for example, form.customer.create, form.customer.edit
  • headline.{entity}.{action} - Headlines, for example, headline.customer.edit
  • table.{entity}.list - Tables, for example, table.customer.list
  • action.{entity}.{action} - Action buttons, for example, action.customer.create
  • layout.{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