Fields

Field types define how content values are decoded and rendered. Declare them per view; access them in templates via the page object.

Two different things named similarly

Fields (site/fields/{name}/index.json) are named, reusable definitions: a label, a type, and default options. Field types (phlat/fieldtypes/{type}/ or site/fieldtypes/{type}/) are the PHP classes that give a type its decode/encode/validate behaviour. A field picks a type; a type has no name of its own.

// site/fields/body/index.json, a named, reusable field definition
{
    "label": "Body",
    "type": "markdown",
    "options": { "required": false }
}

Accessing fields in templates

Record::__get (inherited by Page) resolves any undeclared property against data.json, returning a FieldValue:

{$page->title}              {* FieldValue::__toString(), decoded string *}
{$page->body|render}        {* rendered via the field type's render template *}

{if !$page->cover->empty()}
    <img src="{$page->cover}">
{/if}

Use ->empty() to check whether a field has content before rendering, since a FieldValue object is always truthy in PHP regardless of its value.

Accessing fields in PHP

$field = $page->body;         // FieldValue
$raw   = $field->value;       // raw stored value from data.json
$out   = $field->decode();    // decoded value (HTML for markdown, etc.)
$empty = $field->empty();     // true if decoded value is empty

Declaring fields for a view

A view's own index.json can override a field's options per-view, under a fields key keyed by field name (not an array, and no type here, type comes from the field's own definition):

// site/views/post/index.json
{
    "fields": {
        "title": { "required": true, "max_length": 40 },
        "body": {}
    }
}

Built-in field types

Type Description
text Plain string
markdown Markdown source rendered to HTML via CommonMark
code Source code, syntax-highlighted with Tempest Highlight
image Image file reference, decodes to a PageImage with src(), srcset(), base64(), and transform helpers
datetime ISO 8601 timestamp
richtext HTML content, passed through unescaped

Custom field types

Create site/fieldtypes/{type}/{type}.php with a class extending one of the four primitive bases, FieldTypeText, FieldTypeNumber, FieldTypeList, or FieldTypeObject, matching the JSON shape the type stores:

<?php
namespace Phlat;

class FieldTypeRating extends FieldTypeNumber
{
    public function decode(): mixed
    {
        return max(0, min(5, (int) $this->value));
    }
}

Add site/fieldtypes/{type}/index.json with a title/description for the admin UI. To control |render output, add a template at site/views/fields/{type}.latte (checked after site/views/fields/{view}/{name}.latte and site/views/fields/{name}.latte), it receives $field and $value (the decoded value). Admin field editing is a separate template, phlat/fieldtypes/{type}/edit.latte (or site/fieldtypes/{type}/edit.latte), unrelated to |render.

site/fieldtypes/
  rating/
    rating.php   ← class FieldTypeRating extends FieldTypeNumber
    index.json   ← title/description for the admin UI
    edit.latte   ← optional, admin field-editing UI

Field types in site/fieldtypes/ override matching types in phlat/fieldtypes/.

<?php

namespace Phlat;

class FieldTypeRating extends FieldTypeNumber
{
    public function decode(): mixed
    {
        // clamp stored value to 0-5
        return max(0, min(5, (int) $this->value));
    }

    public function stars(): string
    {
        $filled = str_repeat('★', $this->decode());
        $empty  = str_repeat('☆', 5 - $this->decode());
        return $filled . $empty;
    }
}