Fields

Ava CMS includes a field system that validates content and renders appropriate admin UI inputs. You define fields in your content type configuration, and Ava CMS handles the rest.

What fields give you:

  • Admin UI inputs — Each field type renders an appropriate input (text, date picker, image selector, etc.)
  • CLI validation — Run ./ava lint to check all content against field constraints
  • Real-time feedback — JavaScript validation in the admin editor catches errors as you type
  • Type conversion — Values are automatically converted for storage and retrieval

Quick Start

Define fields in app/config/content_types.php:

return [
    'post' => [
        'label' => 'Posts',
        'content_dir' => 'posts',
        'fields' => [
            'author' => [
                'type' => 'text',
                'label' => 'Author Name',
                'required' => true,
            ],
            'featured' => [
                'type' => 'checkbox',
                'label' => 'Featured Post',
            ],
            'publish_date' => [
                'type' => 'date',
                'label' => 'Publish Date',
                'includeTime' => true,
            ],
        ],
    ],
];

For complete content type configuration, see Configuration: Content Types.

How Fields Are Stored

Field values are saved in your content file's YAML frontmatter. Most fields store simple values:

---
title: My Blog Post
author: Jane Smith
featured: true
publish_date: 2025-01-15
accent_color: "#3b82f6"
---

Some field types store more complex structures:

---
title: Recipe: Sourdough Bread

# Simple array (list)
ingredients:
  - "500g bread flour"
  - "350g water"
  - "100g starter"
  - "10g salt"

# Associative array (key-value pairs)
nutrition:
  calories: 250
  protein: "8g"
  carbs: "50g"

# Gallery (multiple images)
photos:
  - "@media‎:recipes/bread-1.jpg"
  - "@media‎:recipes/bread-2.jpg"

# Multiple taxonomy terms
category:
  - baking
  - bread

# Multiple content references
related_posts:
  - pizza-dough-recipe
  - starter-guide
---

The @media: prefix is a path alias that references files in your /media folder.

Common Options

All field types support these options:

Option Type Default Description
type string required Field type identifier (see below)
label string from field name Human-readable label shown in the admin
description string Help text shown below the field
required bool false Whether a value is required
default mixed varies by type Default value for new content
placeholder string Placeholder text (for applicable fields)
group string Group name for organising fields (see Field Groups)

Field Types

Type Description
text Single-line text input
textarea Multi-line text input
number Integer or decimal input
checkbox Boolean toggle
select Dropdown selection
date Date or datetime picker
color Colour picker
image Image picker with preview
file General file picker
gallery Multiple image picker
array Dynamic list or key-value pairs
content Reference to other content
taxonomy Taxonomy term selector
status Content status toggle
template Theme template selector

text

Single-line text input.

'title' => [
    'type' => 'text',
    'label' => 'Title',
    'required' => true,
    'minLength' => 5,
    'maxLength' => 200,
    'pattern' => '^[A-Z].*',
    'placeholder' => 'Enter title...',
],
Option Type Description
minLength int Minimum character count
maxLength int Maximum character count
pattern string Regex pattern for validation (without delimiters)
patternMessage string Custom error message when pattern fails

textarea

Multi-line text input.

'excerpt' => [
    'type' => 'textarea',
    'label' => 'Excerpt',
    'minLength' => 50,
    'maxLength' => 500,
    'rows' => 6,
],
Option Type Default Description
minLength int Minimum character count
maxLength int Maximum character count (shows counter)
rows int 4 Number of visible text rows

number

Numeric input for integers or decimals.

'price' => [
    'type' => 'number',
    'label' => 'Price',
    'min' => 0,
    'max' => 10000,
    'step' => 0.01,
    'numberType' => 'float',
],
Option Type Default Description
numberType string 'int' 'int' or 'float'
min number Minimum allowed value
max number Maximum allowed value
step number 1 or 'any' Step increment (defaults to 'any' for floats)

checkbox

Boolean toggle.

'featured' => [
    'type' => 'checkbox',
    'label' => 'Featured',
    'description' => 'Show on homepage',
    'default' => false,
],
Option Type Default Description
checkboxLabel string same as label Text shown next to the checkbox

Stored as: true or false in YAML frontmatter.

select

Dropdown selection.

'difficulty' => [
    'type' => 'select',
    'label' => 'Difficulty',
    'required' => true,
    'options' => [
        'beginner' => 'Beginner',
        'intermediate' => 'Intermediate',
        'advanced' => 'Advanced',
    ],
],
Option Type Default Description
options array required Key-value pairs or simple values
multiple bool false Allow multiple selections
emptyOption string '— Select —' Text for the empty/placeholder option

Options format: Use ['value' => 'Display Label'] or simple ['Option 1', 'Option 2'].

Stored as: Single value, or array if multiple: true.

date

Date or datetime picker.

'publish_date' => [
    'type' => 'date',
    'label' => 'Publish Date',
    'includeTime' => true,
    'min' => '2020-01-01',
    'max' => '2030-12-31',
],
Option Type Default Description
includeTime bool false Include time picker
min string Earliest date (YYYY-MM-DD)
max string Latest date (YYYY-MM-DD)

Stored as: YYYY-MM-DD or YYYY-MM-DD HH:MM:SS (with time).

color

Colour picker.

'accent_color' => [
    'type' => 'color',
    'label' => 'Accent Colour',
    'default' => '#3b82f6',
    'format' => 'hex',
    'alpha' => false,
],
Option Type Default Description
format string 'hex' Output format: 'hex', 'rgb', 'rgba', or 'hsl'
alpha bool false Allow transparency values

Stored as: Colour string in the specified format (e.g., #3b82f6, rgba(59, 130, 246, 0.5)).

image

Image picker with preview.

'featured_image' => [
    'type' => 'image',
    'label' => 'Featured Image',
    'description' => '1200×630 recommended',
    'required' => true,
],
Option Type Default Description
allowedTypes array ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.avif'] Allowed extensions
basePath string '/media' Base path for file browser
showPreview bool true Show image preview in editor

Stored as: Path string with @media: alias (e.g., @media:images/hero.jpg). See Path Aliases.

file

General file picker.

'download' => [
    'type' => 'file',
    'label' => 'Download File',
    'accept' => '.pdf,.zip,.docx',
    'required' => true,
],
Option Type Default Description
accept string '*/*' Comma-separated extensions or MIME types
basePath string '/media' Base path for file browser

Stored as: Path string with @media: alias (e.g., @media:documents/guide.pdf).

Multiple image picker with drag-and-drop reordering.

'photos' => [
    'type' => 'gallery',
    'label' => 'Photo Gallery',
    'minItems' => 3,
    'maxItems' => 20,
],
Option Type Default Description
allowedTypes array ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.avif'] Allowed extensions
basePath string '/media' Base path for file browser
minItems int Minimum number of images
maxItems int Maximum number of images

Stored as: Array of paths:

photos:
  - "@media<span></span>:gallery/photo1.jpg"
  - "@media<span></span>:gallery/photo2.jpg"

array

Dynamic list of values or key-value pairs.

# Simple list
'ingredients' => [
    'type' => 'array',
    'label' => 'Ingredients',
    'minItems' => 1,
    'maxItems' => 50,
],

# Key-value pairs
'metadata' => [
    'type' => 'array',
    'label' => 'Metadata',
    'associative' => true,
    'keyPlaceholder' => 'Property',
    'valuePlaceholder' => 'Value',
],
Option Type Default Description
associative bool false Store as key-value pairs instead of simple list
minItems int Minimum number of items
maxItems int Maximum number of items
keyPlaceholder string 'Key' Placeholder for key input (associative only)
valuePlaceholder string 'Value' Placeholder for value input (associative only)
allowEmptyValues bool true Allow empty values (associative only)

Stored as (simple list):

ingredients:
  - "2 cups flour"
  - "1 tsp salt"

Stored as (associative):

metadata:
  author: "John Doe"
  version: "1.0"

content

Reference picker for linking to other content items.

'related_posts' => [
    'type' => 'content',
    'label' => 'Related Posts',
    'contentType' => 'post',
    'multiple' => true,
],
Option Type Default Description
contentType string required Content type to select from
multiple bool false Allow multiple selections
displayField string 'title' Field to show in dropdown
valueField string 'slug' Field to store

Stored as: Slug string, or array of slugs if multiple: true.

taxonomy

Taxonomy term selector. Use this to assign categories, tags, or other taxonomy terms.

'category' => [
    'type' => 'taxonomy',
    'taxonomy' => 'category',
    'label' => 'Categories',
    'required' => true,
    'multiple' => true,
],
Option Type Default Description
taxonomy string required Taxonomy name (from taxonomies.php)
multiple bool true Allow multiple terms
allowNew bool true Allow entering new terms

Stored as: Term slug, or array of slugs if multiple: true.

For more on taxonomies, see Content: Taxonomies and Configuration: Taxonomies.

status

Content status selector (draft, published, unlisted).

'status' => [
    'type' => 'status',
    'label' => 'Status',
    'default' => 'draft',
],

Renders a visual toggle between the three states. Most content types don't need to define this explicitly—it's handled automatically by the core status frontmatter field.

Valid values: draft, published, unlisted

template

Template file selector based on available theme templates.

'template' => [
    'type' => 'template',
    'label' => 'Page Template',
    'defaultTemplate' => 'page.php',
],
Option Type Default Description
defaultTemplate string Template to mark as default

Populates with .php files from your theme directory. See Theming for template details.

Field Groups

Organise related fields into collapsible panels in the admin editor:

'fields' => [
    'author' => [
        'type' => 'text',
        'label' => 'Author',
        'group' => 'Meta',
    ],
    'publish_date' => [
        'type' => 'date',
        'label' => 'Publish Date',
        'group' => 'Meta',
    ],
    'featured_image' => [
        'type' => 'image',
        'label' => 'Featured Image',
        'group' => 'Media',
    ],
],

Fields with the same group value appear together. Groups render in the order the first field of each group appears.

Validation

CLI Linting

Run ./ava lint to validate all content against your field definitions:

./ava lint

Example output:

Linting content files...

  ✗ content/posts/2024-01-15-new-post.md
    Line 5: Field 'author' is required but missing.
    Line 8: Field 'price' must be at least 0.

Found 2 errors in 1 file.

For more CLI commands, see CLI Reference.

Admin Validation

The admin dashboard provides real-time validation as you edit. Each field type has JavaScript validation that matches the server-side rules.

Accessing Fields in Templates

Field values are stored in frontmatter and accessible via the Item API:

// Get a field value
$author = $content->get('author');

// Check if a field has a value
if ($content->has('featured_image')) {
    echo '<img src="' . $content->get('featured_image') . '">';
}

// Get taxonomy terms
$categories = $content->terms('category');

For more on working with content in templates, see Theming: Template Variables.

Custom Field Types

Register custom field types for specialised inputs. Implement the FieldType interface:

// In theme.php or a plugin
use Ava\Fields\FieldRegistry;
use Ava\Fields\FieldType;
use Ava\Fields\ValidationResult;

$registry = $ava->container()->get(FieldRegistry::class);

$registry->register(new class implements FieldType {
    public function name(): string
    {
        return 'rating';
    }
    
    public function label(): string
    {
        return 'Rating';
    }
    
    public function schema(): array
    {
        return [
            'min' => ['type' => 'int', 'default' => 1],
            'max' => ['type' => 'int', 'default' => 5],
        ];
    }
    
    public function validate(mixed $value, array $config): ValidationResult
    {
        if (!is_numeric($value)) {
            return ValidationResult::error('Rating must be a number.');
        }
        
        $min = $config['min'] ?? 1;
        $max = $config['max'] ?? 5;
        
        if ($value < $min || $value > $max) {
            return ValidationResult::error("Rating must be between {$min} and {$max}.");
        }
        
        return ValidationResult::success();
    }
    
    public function toStorage(mixed $value, array $config): mixed
    {
        return (int) $value;
    }
    
    public function fromStorage(mixed $value, array $config): mixed
    {
        return (int) $value;
    }
    
    public function defaultValue(array $config): mixed
    {
        return $config['default'] ?? null;
    }
    
    public function render(string $name, mixed $value, array $config, array $context = []): string
    {
        $min = $config['min'] ?? 1;
        $max = $config['max'] ?? 5;
        $id = 'field-' . htmlspecialchars($name);
        
        return '<input type="range" id="' . $id . '" name="fields[' . $name . ']" '
             . 'min="' . $min . '" max="' . $max . '" '
             . 'value="' . ($value ?? $min) . '">';
    }
    
    public function javascript(): string
    {
        return ''; // Optional: add client-side validation
    }
});

Then use your custom type:

'rating' => [
    'type' => 'rating',
    'label' => 'Recipe Rating',
    'min' => 1,
    'max' => 10,
],

Best Practices

  1. Choose specific types — Use date for dates, number for numbers. This gives you proper inputs and validation.

  2. Always set labels — Clear labels and descriptions help content editors understand each field.

  3. Use validation constraints — Set min, max, minLength, maxLength to prevent bad data.

  4. Group related fields — Use the group option to organise complex content types.

  5. Be thoughtful with required — Only mark fields required if truly necessary.

  6. Provide sensible defaults — Set default values to streamline content creation.