Schemas and Validation
An FRL schema is the contract for one post type. It lists every field Frontbacked will accept and rejects extra fields. This is one of the main protections that keeps published sites from accepting unexpected data.
post products {
schema {
title: { type: "string", min: 1, max: 140, required: true }
price: { type: "number", min: 0, required: true }
status: { type: "string", enum: ["draft", "published", "archived"], required: true }
}
}
If the frontend sends a field that is not in the schema, validation fails.
Field Syntax
Each field is an object:
fieldName: { type: "string", ruleName: value }
Examples:
title: { type: "string", min: 1, max: 140, required: true }
price: { type: "number", min: 0, max: 1000000 }
published: { type: "boolean" }
expiresAt: { type: "datetime" }
createdAt: { type: "servertime" }
Boolean rules use true, such as required: true, unique: true, or immutable: true.
Supported Types
| Type | Accepts | Typical use |
|---|---|---|
string | JavaScript string values | Names, emails, titles, slugs, statuses. |
number | JavaScript number values | Prices, amounts, counts, percentages. |
boolean | true or false | Checkboxes, toggles, feature flags. |
datetime | Date strings or Date values | Event dates, expiry dates, scheduled times. |
servertime | Frontbacked-managed timestamp values | createdAt, updatedAt. |
object | Plain objects | Structured settings, address fields, nested metadata. |
array | Arrays | Tags, gallery images, feature lists. |
file | Uploaded file metadata objects | Images, videos, PDFs, audio, documents. |
anyOf | One of several field schema objects | Flexible values such as string or number. |
String Rules
schema {
title: { type: "string", min: 1, max: 140, required: true }
sku: { type: "string", pattern: "^[a-zA-Z0-9-]{3,120}$" }
status: { type: "string", enum: ["draft", "published", "archived"], required: true }
}
| Rule | Meaning |
|---|---|
required | Value must not be undefined or null. |
min | Minimum string length. |
max | Maximum string length. |
pattern | Regular expression the value must match. |
enum | Allowed string values. |
Number Rules
schema {
amount: { type: "number", min: 1, max: 10000000, required: true }
discount: { type: "number", min: 0, max: 100 }
}
| Rule | Meaning |
|---|---|
required | Value must be present. |
min | Minimum numeric value. |
max | Maximum numeric value. |
Object Fields
Use type: "object" with fields for nested structured values.
schema {
address: {
type: "object",
required: true,
fields: {
line1: { type: "string", min: 1, max: 180, required: true }
city: { type: "string", min: 1, max: 80, required: true }
country: { type: "string", min: 2, max: 80, required: true }
}
}
}
When an object has nested fields, extra keys inside that object are also rejected.
Object-level rules:
| Rule | Meaning |
|---|---|
required | Object must be present. |
minKeys | Minimum number of object keys. |
maxKeys | Maximum number of object keys. |
maxDepth | Maximum nested object depth. |
maxChars | Maximum JSON.stringify size. |
Example:
metadata: { type: "object", minKeys: 0, maxKeys: 20, maxDepth: 2, maxChars: 2000 }
Arrays
Use type: "array" with of or items for the element schema.
schema {
tags: {
type: "array",
of: { type: "string", min: 1, max: 40 },
min: 1,
max: 10,
unique: true
}
}
Array rules:
| Rule | Meaning |
|---|---|
required | Array must be present. |
min | Minimum array length. |
max | Maximum array length. |
unique | Items must be unique after JSON serialization. |
Arrays can contain objects or files:
gallery: {
type: "array",
max: 8,
of: {
type: "object",
fields: {
alt: { type: "string", max: 140 }
image: {
type: "file",
maxSize: 7000000,
mimeTypes: ["image/jpeg", "image/png"],
required: true
}
}
}
}
Union Values
Use anyOf when a value may be one of several allowed schema objects.
schema {
answer: {
anyOf: [
{ type: "string", max: 200 },
{ type: "number", min: 1, max: 10 }
]
}
}
The value passes validation if it matches any option.
File Fields
File fields validate the stored file metadata generated by Frontbacked upload handling.
schema {
avatar: {
type: "file",
maxSize: 3000000,
mimeTypes: ["image/jpeg", "image/png", "image/webp"],
required: true
}
}
For video fields, add maxDurationSeconds when the theme should keep playback to a specific length:
schema {
demoVideo: {
type: "file",
maxSize: 500000000,
maxDurationSeconds: 300,
mimeTypes: ["video/mp4", "video/webm"],
required: true
}
}
Supported file rules:
| Rule | Meaning |
|---|---|
required | File must be present. |
maxSize | Maximum file size in bytes. Frontbacked also has a default upload cap of 1GB, so use FRL to set a stricter field-level limit when your theme needs one. |
mimeTypes | Allowed MIME types. |
maxDurationSeconds | Maximum prepared playback duration in seconds. Longer videos keep the first allowed portion and ignore the ending portion. This only applies to video file fields. |
The stored file metadata must contain a valid url, size, mimeType, fileName, and uploadTime. Extra stored file metadata properties are rejected.
When video files are read back through FQL, Frontbacked may add a runtime-only media object with processing status and playback URLs. That object is for display and playback only, and is ignored on incoming writes before schema validation.
Server Time Fields
Use type: "servertime" for timestamps that should be set by Frontbacked.
schema {
createdAt: { type: "servertime" }
updatedAt: { type: "servertime" }
}
This keeps frontend clients from deciding trusted timestamps.
Immutable Fields
Use immutable rules to protect fields during updates. Immutable validation runs on edit actions and compares the final $post value against $prev.
schema {
id: { type: "string", required: true, immutable: true }
authorId: { type: "string", required: true, immutable: true }
}
Use immutableUnless when a field can change only under a specific guard.
schema {
slug: {
type: "string",
min: 3,
max: 160,
required: true,
immutableUnless: $post.status == "draft" && $user.id == $prev.authorId
}
status: {
type: "string",
enum: ["draft", "published", "archived"],
required: true,
immutableUnless: $access.edit
}
}
The guard can use the same runtime context available to permission expressions.
Admin-Supplied Rule Values
When a theme references $secret.someKey or $private.someKey in backend/index.rules, fb-admin automatically detects those keys and pre-populates fields for the site admin.
create require[
$secret.PAYSTACK_SECRET && $private.minimumDepositAmount && $user.id
]
The admin fills those values in Secret Keys or Private Settings. Theme developers do not need to build a separate settings form for them.
Use $secret for sensitive credentials such as API secrets and webhook signing keys. Use $private for private server-side settings that are not secrets, such as review email addresses or numeric limits.
A Complete Schema Example
post articles {
schema {
id: { type: "string", min: 1, max: 120, required: true, immutable: true }
authorId: { type: "string", min: 1, max: 120, required: true, immutable: true }
title: { type: "string", min: 1, max: 140, required: true }
slug: {
type: "string",
pattern: "^[a-z0-9-]+$",
min: 3,
max: 160,
required: true,
immutableUnless: $post.status == "draft"
}
summary: { type: "string", max: 300 }
body: { type: "string", min: 1, max: 50000, required: true }
status: { type: "string", enum: ["draft", "published", "archived"], required: true }
cover: {
type: "file",
maxSize: 7000000,
mimeTypes: ["image/jpeg", "image/png", "image/webp"]
}
tags: {
type: "array",
of: { type: "string", min: 1, max: 40 },
max: 12,
unique: true
}
seo: {
type: "object",
maxChars: 600,
fields: {
title: { type: "string", max: 80 }
description: { type: "string", max: 180 }
}
}
createdAt: { type: "servertime" }
updatedAt: { type: "servertime" }
}
}