Skip to content

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": [ ... ]
}
PropertyRequiredDescription
idYesLowercase alphanumeric and underscores, unique across the template
labelYesDisplay name in the editor (1–100 characters)
config.colorNoHex color for visual distinction (e.g. #3F51B5)
config.collapsibleNoIf true, the section can be collapsed in the editor
config.visibilityNoConditional: 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.

TypeID to useDescription
multilinedescriptionShort text summary, indexed for full-text search
imagefeature_imageFeature image URL
datetimeevent_start_dateEvent start date/time, filterable with event_start / event_end
datetimeevent_end_dateEvent end date/time
textlocation_nameVenue or place name, indexed for full-text search
linklocation_linkURL for the location (e.g. Google Maps link)
locationlocation_geoLatitude/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

TypeDescriptionKey validation rules
textSingle-line textminLength, maxLength, pattern, and optional helper
multilineMulti-line textminLength, maxLength
linkURL inputpattern, and optional helper
rich_textRich text editor (see Rich Text Output)minLength, maxLength
content_jsonStructured rich text (JSON)required
imageImage URLallowedTypes, maxItems
mediaFile attachmentallowedTypes, maxItems, minItems
booleanTrue/false togglerequired
integerWhole numbermin, max
decimalDecimal numbermin, max, precision
dateDate pickermin, max
datetimeDate and time pickermin, max
selectDropdown selectionrequired
locationLat/lon/altitude coordinatesaltMin, altMax
referenceReference to another content itemallowedTemplates
groupA 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:

PresetMatches
Email addressuser@example.com
URL / websitehttps://example.com
US phone number(555) 123-4567
US ZIP code12345 or 12345-6789
IPv4 address192.168.1.1
Letters onlyABCabc
AlphanumericABC123

Field Options

PropertyDescription
descriptionHelp text shown below the field in the editor
placeholderPlaceholder text shown inside empty inputs
initialValueDefault value pre-filled when creating new content. A reset button appears when the value differs from this.
lockedIf 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_formatDescription
html (default)The ProseMirror document is rendered to an HTML string
jsonThe 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

OperatorValue requiredBehavior
existsNoShow when the field has any non-empty value (not null, "", or [])
inArrayShow when the field value matches any item in the array
containsStringShow when the field value contains the word (case-insensitive, word boundaries)
lessThanNumberShow when the field value is less than the given number
greaterThanNumberShow 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": [ ... ]
}

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:

TransformEffect
@slugLowercase, spaces/symbols → hyphens ("Hello World""hello-world")
@lowerConvert to lowercase
@upperConvert to uppercase
@encodeURL-encode the value (for use inside query parameters)
@uuidGenerate a new random UUID (ignores the source value)
@nowOutput 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:

ExpressionOutput
{{ @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' }}"
}
}

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).