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

TypeAcceptsTypical use
stringJavaScript string valuesNames, emails, titles, slugs, statuses.
numberJavaScript number valuesPrices, amounts, counts, percentages.
booleantrue or falseCheckboxes, toggles, feature flags.
datetimeDate strings or Date valuesEvent dates, expiry dates, scheduled times.
servertimeFrontbacked-managed timestamp valuescreatedAt, updatedAt.
objectPlain objectsStructured settings, address fields, nested metadata.
arrayArraysTags, gallery images, feature lists.
fileUploaded file metadata objectsImages, videos, PDFs, audio, documents.
anyOfOne of several field schema objectsFlexible 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 }
}
RuleMeaning
requiredValue must not be undefined or null.
minMinimum string length.
maxMaximum string length.
patternRegular expression the value must match.
enumAllowed string values.

Number Rules

schema {
  amount: { type: "number", min: 1, max: 10000000, required: true }
  discount: { type: "number", min: 0, max: 100 }
}
RuleMeaning
requiredValue must be present.
minMinimum numeric value.
maxMaximum 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:

RuleMeaning
requiredObject must be present.
minKeysMinimum number of object keys.
maxKeysMaximum number of object keys.
maxDepthMaximum nested object depth.
maxCharsMaximum 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:

RuleMeaning
requiredArray must be present.
minMinimum array length.
maxMaximum array length.
uniqueItems 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:

RuleMeaning
requiredFile must be present.
maxSizeMaximum 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.
mimeTypesAllowed MIME types.
maxDurationSecondsMaximum 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" }
  }
}