FRL Reference

This page is a compact reference for Frontbacked Rule Language.

File Shape

fn helperName(arg) {
  return arg;
}

post postType {
  process {
    $post.field = "value";
  }

  schema {
    field: { type: "string", required: true }
  }

  create require[ $user.id ]
  read require[ $access.read ]
  edit require[ $access.edit ]
  delete require[ $access.delete ]

  create on create[]
  edit on edit[]
  delete on delete[]
}

auth {
  schema {
    displayName: { type: "string", max: 120 }
  }

  edit require[ $user.id ]
}

endpoint.post endpointName("/path/:id") {
  verify $request.params.id
  idempotency `${$request.params.id}:${$request.body.reference}`
  process {
    return { ok: true };
  }
}

Top-Level Declarations

DeclarationPurpose
fn name(params) { ... }Define a reusable helper function.
post name { ... }Define one post type.
auth { ... }Define schema and permissions for custom signed-in user data.
endpoint.get/post/put/delete name("/path") { ... }Define a custom guarded JSON endpoint.

Post Sections

SectionPurpose
process { ... }Normalize and compute values before validation.
create/edit/delete/read process { ... }Run extra action-specific normalization before schema validation.
after create/edit/delete/read { ... }Run after permission checks and before triggered writes.
schema { ... }Define allowed fields and validation rules.
list { ... }Set list visibility and default/max list sizes.
export name { ... }Render filtered rows into a downloadable file.
create require[...]Guard create actions.
read require[...]Guard read actions.
edit require[...]Guard update actions.
delete require[...]Guard delete actions.
on resolved field from "..." match ... { ... }Handle async webhook resolution.
create/edit/delete on create/edit/delete[...]Trigger related writes after a successful action.

Action Process and After Hooks

Use a shared process block for normalization that should run for every create/edit write. Use action-specific process blocks when one action needs extra data.

post articles {
  process {
    $post.title = str.trim($post.title);
  }

  create process {
    $post.slug = str.slug($post.title, str.id(8));
    $post.status = $post.status || "draft";
  }

  after edit {
    $context.auditId = str.id(18);
  }
}

after blocks run only after the matching require[...] passes, so they are useful for audit ids, post-action summaries, or context needed by triggers.

List Policies

post articles {
  list {
    require[ $access.read || $user.id ]
    defaultLimit: 20;
    maxLimit: 100;
  }
}
PropertyPurpose
require[...]Controls whether the current user can list this post type.
defaultLimitDefault list size when the frontend does not provide one.
maxLimitLargest list size this post type allows.

Export Blocks

Use export when a theme needs a downloadable file generated from filtered post rows.

post orders {
  export csv {
    rowsPerPage: 500;
    contentType: "text/csv; charset=utf-8";
    extension: "csv";
    filename: "orders.csv";

    require[ $access.read ]

    header {
      return "Order ID,Email,Amount";
    }

    row {
      return `${$rowMeta.id},${$row.email},${$row.amount}`;
    }
  }
}

Render it from the frontend with Frontbacked.exportPosts({ type: "orders", name: "csv" }).

Export blocks can use $row, $rowMeta, $query, $user, $secret, $private, $page, $currency, and $context.

Endpoint Blocks

endpoint.post createLead("/leads/submit") {
  verify $request.body.email
  idempotency `${str.lower(str.trim($request.body.email))}:${$request.body.campaign || "default"}`

  process {
    $context.email = str.lower(str.trim($request.body.email));

    return {
      ok: true,
      email: $context.email
    };
  }
}
SectionPurpose
verify expressionOptional guard evaluated before the endpoint process runs.
idempotency expressionOptional stable key for repeat-safe requests.
process { ... }Returns the endpoint response and can call endpoint-safe helpers.

Endpoint paths are called under /fb-endpoints, so the example above is called at /fb-endpoints/leads/submit. Use Endpoints and Idempotency for the full guide.

Schema Types

name: { type: "string", min: 1, max: 120, required: true }
age: { type: "number", min: 18 }
active: { type: "boolean" }
startsAt: { type: "datetime" }
createdAt: { type: "servertime" }
profile: {
  type: "object",
  fields: {
    bio: { type: "string", max: 400 }
  }
}
tags: { type: "array", of: { type: "string", max: 40 }, max: 10, unique: true }
image: { type: "file", maxSize: 7000000, mimeTypes: ["image/jpeg", "image/png"] }
video: { type: "file", maxSize: 500000000, maxDurationSeconds: 300, mimeTypes: ["video/mp4"] }
answer: { anyOf: [{ type: "string", max: 100 }, { type: "number", min: 1, max: 10 }] }

Schema Rules

RuleWorks withExample
requiredall field typesemail: { type: "string", required: true }
minstring, number, arraytitle: { type: "string", min: 1 }
maxstring, number, arraytags: { type: "array", max: 10 }
patternstringslug: { type: "string", pattern: "^[a-z0-9-]+$" }
enumstringstatus: { type: "string", enum: ["draft", "published"] }
minKeysobjectmeta: { type: "object", minKeys: 1 }
maxKeysobjectmeta: { type: "object", maxKeys: 20 }
maxDepthobjectmeta: { type: "object", maxDepth: 2 }
maxCharsobjectmeta: { type: "object", maxChars: 2000 }
uniquearraytags: { type: "array", unique: true }
maxSizefileimage: { type: "file", maxSize: 3000000 }
mimeTypesfilefile: { type: "file", mimeTypes: ["application/pdf"] }
maxDurationSecondsvideo filevideo: { type: "file", maxDurationSeconds: 300 }
immutableedit validationid: { type: "string", required: true, immutable: true }
immutableUnlessedit validationstatus: { type: "string", immutableUnless: $access.edit }

Runtime Variables

VariableAvailable in
$idCurrent generated post id, or current signed-in user id inside auth rules.
$postprocess, schema immutable guards, permissions, triggers, resolved handlers
$prevedit/read/delete flows, immutable guards, permissions, triggers
$userpermissions, process, triggers
$visitorendpoint and rule context for guest/user/admin detection
$adminadmin context when the current user is acting as a site admin
$accesspermissions and guarded workflow fields
$walletsigned-in user's wallet balances
$requestendpoint requests, exports, and request-aware rule helpers
$queryquery/filter data for lists, exports, and endpoints
$secretsecret values referenced by FRL; fb-admin automatically creates Secret Keys fields for referenced keys
$privateprivate server-side settings referenced by FRL; fb-admin automatically creates Private Settings fields for referenced keys
$pagepage settings from the site's settings
$currencyplatform currency config; $currency.value is the selected code and $currency.mode is currently fixed
$contextscratch data created during the current rule run
$resolvedresolved webhook handlers

Admin-Supplied Variables

If a theme references $secret.PAYSTACK_SECRET or $private.minimumDepositAmount, fb-admin infers those names from backend/index.rules and shows matching fields on the Secret Keys and Private Settings pages. Site admins fill the values there; theme developers do not need to create separate admin UI for them.

Operators

OperatorExample
`
&&$post.email && $post.acceptedTerms
==, !=$post.status == "published"
>, <, >=, <=$post.price > 0
includes$post.role includes ["admin", "editor"]
+, -, *, /, %$post.quantity * $post.price
!, not!$post.archived

Built-In Functions

FunctionPurpose
str.lower(value)Lowercase a string.
str.upper(value)Uppercase a string.
str.trim(value)Trim a string.
str.includes(value, textOrArray)Check whether a string contains one or more values.
str.replace(value, search, replacement)Replace all matching text.
str.join(values, separator)Join array values as strings.
str.slug(...values)Create a URL-friendly slug.
str.id(length)Generate a random id.
arr.size(array)Return array length.
arr.map(array, fn)Map array values.
arr.join(array, separator)Join array values.
math.min(...values)Return minimum.
math.max(...values)Return maximum.
time.now()Return the current timestamp.
time.add(value, { days, hours, minutes })Add time to a date value and return an ISO timestamp.
http.post(url, options)Perform an HTTP POST request.
console.log(...values)Log from a rule runtime.

Rule Resource Helpers

These helpers are available inside FRL expressions and process blocks when the current runtime supports them.

HelperPurpose
posts.findOne(args)Read one visible post that matches the given filters.
posts.findAll(args)Read visible posts that match the given filters.
posts.count(args)Count visible posts that match the given filters.
trigger.post.create(type, data)Queue a related post create after the current action succeeds. Frontbacked generates the id and author.
trigger.post.edit(id, data)Queue a related post edit by generated post id.
trigger.post.delete(id)Queue a related post delete by generated post id.
chargeUser(args)Request wallet confirmation before continuing a paid rule action.

Price Fields

Use type: "price" when a post schema is allowed to create server-enforced price metadata:

post products {
  schema {
    title: { type: "string", min: 1, max: 140, required: true }
    price: { type: "price", required: true }
  }

  create require[ $user.id ]
  read require[ true ]
  edit require[ $user.id == $prev.authorId ]
  delete require[ $user.id == $prev.authorId ]
}

The frontend submits price metadata through special keys. These keys are not stored as normal post data; Frontbacked stores them as protected price metadata for the price field:

await Frontbacked.uploadPost({
  type: "products",
  post: {
    title: "Starter Plan",
    "$minAmount.price": 500,
    "$maxAmount.price": 10000
  }
})

Use "$fixedAmount.price" for fixed one-time prices, or "$minAmount.price" with optional "$maxAmount.price" for variable amounts. Fixed prices cannot be mixed with min/max metadata.

Secure Rule Checklist

Before publishing a theme, check every post type:

  1. Every stored field is in schema.
  2. Owner, site, ids, and external reference fields are immutable.
  3. Workflow fields use immutableUnless when only editors or trusted flows may change them.
  4. Every action has a require[...] block.
  5. Public create rules still require meaningful submitted fields.
  6. Edit/delete rules use $prev for ownership checks.
  7. File fields declare maxSize, mimeTypes, and video maxDurationSeconds where needed.
  8. Trigger targets have their own post rules.
  9. Custom endpoints use verify and idempotency when repeated requests could duplicate work.
  10. List and export policies have explicit require[...] blocks when the data is not public.