Templates
Templates are the content schema for your organization. Each template defines what fields a content item has, how they’re organized, what values are valid, and what contributors see in the editor. Templates are JSON documents that you define in your FlareBuilder workspace.
Structure
Every template is a JSON document with a top-level title field and a sections array. Sections hold fields. Fields hold data.
{ "schema_version": "6.0", "name": "Blog Post", "description": "Standard article format", "title": { "label": "Post Title", "placeholder": "Enter a title...", "config": { "color": "#e8ca93" }, "validation": { "required": { "value": true, "message": "A title is required" }, "maxLength": { "value": 256, "message": "Title cannot exceed 256 characters" } } }, "sections": [ { "id": "content", "label": "Content", "config": { "color": "#3F51B5" }, "fields": [ { "id": "summary", "label": "Summary", "type": "multiline", "enabled": true, "description": "A one or two sentence description shown in feeds" }, { "id": "body", "label": "Body", "type": "rich_text", "enabled": true } ] } ]}Sections
Sections group related fields visually and semantically. Each section has an id, a label, and a fields array.
{ "id": "event_details", "label": "Event Details", "config": { "color": "#4CAF50", "collapsible": true, "visibility": { "field": "is_event", "operator": "in", "value": [true] } }, "fields": [ ... ]}| Property | Required | Description |
|---|---|---|
id | Yes | Lowercase alphanumeric and underscores, unique across the template |
label | Yes | Display name in the editor (1–100 characters) |
config.color | No | Hex color for visual distinction (e.g. #3F51B5) |
config.collapsible | No | If true, the section can be collapsed in the editor |
config.visibility | No | Conditional: hide the entire section based on another field’s value |
Fields
Fields are the individual data inputs within a section. Every field has at minimum id, label, type, and enabled.
{ "id": "registration_url", "label": "Registration Link", "type": "link", "enabled": true, "description": "Where attendees sign up", "initialValue": "https://", "validation": { "required": { "value": true, "message": "A registration link is required" } }}Field Types
FlareBuilder has two categories of fields: built-in (indexed and searchable) and custom (JSON storage, flexible).
Built-in Fields
These fields map to dedicated database columns. They’re indexed for full-text search and range filtering in the Feed API.
| Type | ID to use | Description |
|---|---|---|
multiline | description | Short text summary, indexed for full-text search |
image | feature_image | Feature image URL |
datetime | event_start_date | Event start date/time, filterable with event_start / event_end |
datetime | event_end_date | Event end date/time |
text | location_name | Venue or place name, indexed for full-text search |
link | location_link | URL for the location (e.g. Google Maps link) |
location | location_geo | Latitude/longitude/altitude coordinates, filterable with geobox |
Built-in fields have reserved ids. Use the exact field IDs listed above.
{ "id": "event_start_date", "label": "Start Date", "type": "datetime", "enabled": true}Custom Fields
| Type | Description | Key validation rules |
|---|---|---|
text | Single-line text | minLength, maxLength, pattern, and optional helper |
multiline | Multi-line text | minLength, maxLength |
link | URL input | pattern, and optional helper |
rich_text | Rich text editor (see Rich Text Output) | minLength, maxLength |
content_json | Structured rich text (JSON) | required |
image | Image URL | allowedTypes, maxItems |
media | File attachment | allowedTypes, maxItems, minItems |
boolean | True/false toggle | required |
integer | Whole number | min, max |
decimal | Decimal number | min, max, precision |
date | Date picker | min, max |
datetime | Date and time picker | min, max |
select | Dropdown selection | required |
location | Lat/lon/altitude coordinates | altMin, altMax |
reference | Reference to another content item | allowedTemplates |
group | A set of nested fields (see Groups) | minItems, maxItems |
Validation Rules
All validation rules follow the same shape: an object with value and message.
"validation": { "required": { "value": true, "message": "This field is required" }, "maxLength": { "value": 500, "message": "Maximum 500 characters" }, "minLength": { "value": 10, "message": "At least 10 characters" }, "pattern": { "value": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", "message": "Must be a valid email address" }, "min": { "value": 0, "message": "Must be 0 or greater" }, "max": { "value": 100, "message": "Must be 100 or less" }, "precision": { "value": 2, "message": "Maximum 2 decimal places" }, "allowedTypes": { "value": ["jpg", "png", "webp"], "message": "Images only" }, "minItems": { "value": 1, "message": "At least one item required" }, "maxItems": { "value": 5, "message": "Maximum 5 items" }, "altMin": { "value": 0, "message": "Altitude cannot be negative" }, "altMax": { "value": 8848, "message": "Altitude seems too high" }}Built-in validation presets are available for common pattern values:
| Preset | Matches |
|---|---|
| Email address | user@example.com |
| URL / website | https://example.com |
| US phone number | (555) 123-4567 |
| US ZIP code | 12345 or 12345-6789 |
| IPv4 address | 192.168.1.1 |
| Letters only | ABCabc |
| Alphanumeric | ABC123 |
Field Options
| Property | Description |
|---|---|
description | Help text shown below the field in the editor |
placeholder | Placeholder text shown inside empty inputs |
initialValue | Default value pre-filled when creating new content. A reset button appears when the value differs from this. |
locked | If true, the field definition cannot be changed in the template editor (used for built-in fields) |
Rich Text Output
The rich_text field type supports an output_format option that controls how the field value is delivered in Feed and Content API responses.
output_format | Description |
|---|---|
html (default) | The ProseMirror document is rendered to an HTML string |
json | The raw ProseMirror JSON document is returned as-is |
Set output_format on the field definition:
{ "id": "body", "label": "Body", "type": "rich_text", "enabled": true, "output_format": "html"}HTML output (default):
{ "body": "<p>Hello <strong>world</strong></p>"}JSON output — returns the ProseMirror document structure, useful for client-side rendering with TipTap, ProseMirror, or custom renderers:
{ "body": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Hello " }, { "type": "text", "marks": [{ "type": "bold" }], "text": "world" } ] } ] }}Groups
Groups are fields with type: "group". They contain a nested fields array and can be made repeatable.
Non-repeatable Group
A non-repeatable group is a logical grouping of fields presented together in the editor. No minItems or maxItems in validation.
{ "id": "contact", "label": "Contact", "type": "group", "enabled": true, "config": { "inline": true }, "fields": [ { "id": "contact_name", "label": "Name", "type": "text", "enabled": true }, { "id": "contact_email", "label": "Email", "type": "text", "enabled": true, "validation": { "pattern": { "value": "^[^@]+@[^@]+\\.[^@]+$", "message": "Must be a valid email" } } } ]}Setting config.inline: true renders the group’s fields side-by-side rather than stacked.
Repeatable Group
Adding minItems or maxItems to the group’s validation makes it repeatable. Contributors can add, remove, and reorder entries.
{ "id": "speakers", "label": "Speakers", "type": "group", "enabled": true, "validation": { "minItems": { "value": 1, "message": "At least one speaker is required" }, "maxItems": { "value": 10, "message": "Maximum 10 speakers" } }, "fields": [ { "id": "speaker_name", "label": "Name", "type": "text", "enabled": true, "validation": { "required": { "value": true, "message": "Speaker name is required" } } }, { "id": "speaker_bio", "label": "Bio", "type": "multiline", "enabled": true }, { "id": "speaker_photo", "label": "Photo", "type": "image", "enabled": true } ]}In the Feed API response, repeatable groups appear as an array:
{ "id": "speakers", "label": "Speakers", "data": { "speakers": [ { "speaker_name": "Alice Chen", "speaker_bio": "...", "speaker_photo": "https://..." }, { "speaker_name": "Bob Torres", "speaker_bio": "...", "speaker_photo": null } ] }}Group constraints:
- Groups cannot contain other groups (one level of nesting only)
- Groups cannot contain built-in (reserved id) fields
- All nested field IDs must be unique within the template
Conditionals
Sections and groups can be conditionally shown or hidden based on the value of another field in the same section.
Set config.visibility on the section or group:
"config": { "visibility": { "field": "field_id_to_watch", "operator": "in", "value": ["yes", "maybe"] }}Operators
| Operator | Value required | Behavior |
|---|---|---|
exists | No | Show when the field has any non-empty value (not null, "", or []) |
in | Array | Show when the field value matches any item in the array |
contains | String | Show when the field value contains the word (case-insensitive, word boundaries) |
lessThan | Number | Show when the field value is less than the given number |
greaterThan | Number | Show when the field value is greater than the given number |
Examples
Show a “Details” section only when a checkbox is enabled:
{ "id": "details", "label": "Details", "config": { "visibility": { "field": "has_details", "operator": "exists" } }, "fields": [ ... ]}Show event fields only for specific content types:
{ "id": "event_info", "label": "Event Info", "config": { "visibility": { "field": "content_type", "operator": "in", "value": ["in-person", "hybrid"] } }, "fields": [ ... ]}Show an “Additional Guests” group only when guest count exceeds 1:
{ "id": "additional_guests", "label": "Additional Guests", "type": "group", "config": { "visibility": { "field": "guest_count", "operator": "greaterThan", "value": 1 } }, "fields": [ ... ]}Show a disclaimer field when the body contains a specific word:
{ "id": "legal", "label": "Legal", "config": { "visibility": { "field": "body", "operator": "contains", "value": "investment" } }, "fields": [ ... ]}Colors
Sections and the title field support a config.color hex value. Colors appear as visual identifiers in the template editor and as left-border accents on section panels in the content editor.
"config": { "color": "#4CAF50"}Color must be a 6-digit hex string: #RRGGBB. Colors are cosmetic only — they have no effect on the Feed API response.
Helpers
link and text fields support a helper configuration. A button appears next to the field; when clicked, it fills the field with a value generated from other fields in the form.
{ "id": "map_url", "label": "Map Link", "type": "link", "enabled": true, "helper": { "label": "Open in Google Maps", "template": "https://www.google.com/maps/search/?api=1&query={{ $location_name | @encode }}" }}Template Expression Syntax
helper.template is a plain string that may contain one or more {{ expression }} blocks. Each block is replaced with a resolved value; everything else is kept verbatim.
Reference a field value with $field_id:
{{ $location_name }}Apply transforms with | pipes:
{{ $title | @slug }}Transforms chain left-to-right. Available transforms:
| Transform | Effect |
|---|---|
@slug | Lowercase, spaces/symbols → hyphens ("Hello World" → "hello-world") |
@lower | Convert to lowercase |
@upper | Convert to uppercase |
@encode | URL-encode the value (for use inside query parameters) |
@uuid | Generate a new random UUID (ignores the source value) |
@now | Output the current ISO timestamp (ignores the source value) |
@format('pattern') | Format a date value using the given date-fns pattern. Defaults to yyyy-MM-dd when no pattern is provided. |
Pass a parameter to a function with parentheses and single quotes:
{{ @now('yyyy') }}{{ $event_start_date | @format('MMM d, yyyy') }}Any @function can accept an optional ('arg'). Functions that don’t use the argument simply ignore it. Currently @now and @format use the parameter as a date-fns format pattern:
| Expression | Output |
|---|---|
{{ @now }} | 2026-02-16T12:00:00.000Z (full ISO timestamp) |
{{ @now('yyyy') }} | 2026 |
{{ @now('MMM d, yyyy') }} | Feb 16, 2026 |
{{ $event_start_date | @format }} | 2025-09-15 (default yyyy-MM-dd) |
{{ $event_start_date | @format('h:mm a') }} | 9:00 AM |
Provide a fallback default with a single-quoted literal at the end:
{{ $title | @slug | 'untitled' }}If the field is empty, the default is used instead. Fields that have a default never disable the helper button — it stays active even when those fields are empty. Fields without a default keep the button disabled until they have a value.
Examples
Generate a Google Maps search URL from the venue name:
{ "id": "map_url", "label": "Map Link", "type": "link", "enabled": true, "helper": { "label": "Open in Google Maps", "template": "https://www.google.com/maps/search/?api=1&query={{ $location_name | @encode }}" }}Build a Wikipedia search link from the session title, falling back to a search for “FlareBuilder”:
{ "id": "reference_url", "label": "Reference Link", "type": "link", "enabled": true, "helper": { "label": "Search Wikipedia", "template": "https://en.wikipedia.org/wiki/Special:Search?search={{ $title | @encode | 'FlareBuilder' }}" }}Generate a URL slug from the title (falls back to "untitled" when title is empty):
{ "id": "slug", "label": "URL Slug", "type": "text", "enabled": true, "helper": { "label": "Generate from title", "template": "{{ $title | @slug | 'untitled' }}" }}Generate a unique external ID combining the slugified title and a UUID:
{ "id": "external_id", "label": "External ID", "type": "text", "enabled": true, "helper": { "label": "Generate ID", "template": "{{ $title | @slug | 'item' }}-{{ @uuid }}" }}Generate a display-friendly date from a datetime field:
{ "id": "display_date", "label": "Display Date", "type": "text", "enabled": true, "helper": { "label": "Format start date", "template": "{{ $event_start_date | @format('MMMM d, yyyy') }}" }}Stamp the current year into a copyright line:
{ "id": "copyright", "label": "Copyright", "type": "text", "enabled": true, "helper": { "label": "Set copyright year", "template": "© {{ @now('yyyy') }} All rights reserved." }}Complete Example
A template for a conference session:
{ "schema_version": "6.0", "name": "Conference Session", "title": { "label": "Session Title", "placeholder": "e.g. Building with Cloudflare Workers", "config": { "color": "#e8ca93" }, "validation": { "required": { "value": true, "message": "Title is required" }, "maxLength": { "value": 200, "message": "Keep it under 200 characters" } } }, "sections": [ { "id": "overview", "label": "Overview", "config": { "color": "#3F51B5" }, "fields": [ { "id": "description", "label": "Abstract", "type": "multiline", "enabled": true, "description": "A brief abstract shown in schedules and the public feed", "validation": { "required": { "value": true, "message": "An abstract is required" }, "maxLength": { "value": 500, "message": "Keep it under 500 characters" } } }, { "id": "track", "label": "Track", "type": "select", "enabled": true, "description": "Which conference track this session belongs to" }, { "id": "level", "label": "Audience Level", "type": "select", "enabled": true } ] }, { "id": "schedule", "label": "Schedule", "config": { "color": "#009688" }, "fields": [ { "id": "event_start_date", "label": "Start Time", "type": "datetime", "enabled": true }, { "id": "event_end_date", "label": "End Time", "type": "datetime", "enabled": true }, { "id": "location_name", "label": "Room", "type": "text", "enabled": true } ] }, { "id": "speakers", "label": "Speakers", "config": { "color": "#E91E63" }, "fields": [ { "id": "speaker_list", "label": "Speaker List", "type": "group", "enabled": true, "validation": { "minItems": { "value": 1, "message": "At least one speaker required" }, "maxItems": { "value": 4, "message": "Maximum 4 speakers per session" } }, "config": { "inline": false }, "fields": [ { "id": "name", "label": "Name", "type": "text", "enabled": true, "validation": { "required": { "value": true, "message": "Name is required" } } }, { "id": "company", "label": "Company", "type": "text", "enabled": true }, { "id": "bio", "label": "Bio", "type": "multiline", "enabled": true }, { "id": "photo", "label": "Photo", "type": "image", "enabled": true }, { "id": "profile_url", "label": "Profile Link", "type": "link", "enabled": true } ] } ] }, { "id": "recording", "label": "Recording", "config": { "color": "#607D8B", "collapsible": true, "visibility": { "field": "track", "operator": "exists" } }, "fields": [ { "id": "recording_url", "label": "Recording URL", "type": "link", "enabled": true }, { "id": "slides_url", "label": "Slides URL", "type": "link", "enabled": true } ] } ]}How Template Data Appears in the Feed
Content fields appear in the feed response under sections, organized by section ID:
{ "id": "abc123", "title": "Building with Cloudflare Workers", "template_name": "Conference Session", "tags": ["platform", "workers"], "date_published": "2025-06-01T00:00:00Z", "date_created": "2025-05-20T10:00:00Z", "date_expires": null, "author": { "id": "user-uuid" }, "permalink": "https://your-org.flarebuilder.com/p/abc123", "sections": [ { "id": "overview", "label": "Overview", "data": { "description": "A deep dive into building edge-native APIs...", "track": "Infrastructure", "level": "Intermediate" } }, { "id": "schedule", "label": "Schedule", "data": { "event_start_date": "2025-09-15T09:00:00Z", "event_end_date": "2025-09-15T10:00:00Z", "location_name": "Hall B" } }, { "id": "speakers", "label": "Speakers", "data": { "speaker_list": [ { "name": "Alice Chen", "company": "Cloudflare", "bio": "Alice works on the Workers runtime...", "photo": "https://...", "profile_url": "https://linkedin.com/in/..." } ] } } ]}The recording section is absent because it was conditionally hidden (the visibility condition was not met when this content was saved).