FQL Reference

This page is a compact reference for Frontbacked Query Language.

State Comment

<!-- {STATE}
  state = {
    "key(defaultValue)": "sourceExpression",
    "derived": "$state.key || 'fallback'"
  }
-->

State Key Defaults

SyntaxResult
"accepted(true)"$state.accepted starts as true.
"count(0)"$state.count starts as 0.
"name('Guest')"$state.name starts as "Guest".
"coupon(null)"$state.coupon starts as null.

Event Source Syntax

selector.onevent.target.path.functionName()

Examples:

SourceValue
#name.oninput.target.valueInput value.
#terms.onchange.target.checkedCheckbox checked state.
#avatar.onchange.target.files[0]First selected file.
#form.onsubmit.handleSubmit()Return value of handleSubmit(event).

Multiple Sources

{
  "terms(true)": "#terms.onchange.target.checked, #termsButton.onclick.target.toggleTerms()"
}

Use commas for multiple event sources for the same state key.

Fallbacks

{
  "displayName": "$state.form.name || $query.name || 'Guest'",
  "canSubmit": "$state.form.email && $state.form.terms"
}

Use || to resolve the first non-null expression.

Use && for truthy gating. It evaluates left to right, returns the first falsy value, and returns the last value when all values are truthy. && has higher precedence than ||.

Data Roots

RootExample
$state$state.form.email
$query$query.plan
$params$params.slug
$siteInfo$siteInfo.name
$auth$auth.email
$currency$currency.value
$wallet$wallet.default
$settings$settings.site.name
bare post typeproducts{id:$query.id}
$posts or $post$posts.products{id:$query.id}
$transactions$transactions.{status:'successful'}.$counts
$$.title inside an f-list template

Single Post Lookup

{
  "product": "products{id:$query.productId}",
  "sameProduct": "$posts.products{id:$query.productId}",
  "title": "$state.product.title || 'Product'",
  "id": "$state.product.$id"
}

The object inside braces is the lookup condition. A single post lookup can start with the bare post type, such as products{...}, or with $posts/$post, such as $posts.products{...}.

Single post lookups resolve to visible post data:

{
  ...publicPostFields,
  ...authorOnlyPostFieldsWhenCurrentUserIsAuthor,
  $id,
  $type,
  $createdOn,
  $updatedOn
}

$id, $type, $createdOn, and $updatedOn are generated by Frontbacked for posts submitted with Frontbacked.uploadPost() and preserved for posts changed with Frontbacked.updatePost(). Do not submit these fields yourself; read them from single post lookups and list items.

Plain lookup fields such as name or slug read visible post data. System fields use $ names:

{
  "byName": "$posts.profile_updates{name:'Elijah'}.name",
  "byAuthor": "$posts.profile_updates{$authorId:$auth.$id}.name",
  "created": "$posts.profile_updates{name:'Elijah'}.$createdOn"
}

Auth Data

Use $auth for the current user:

{
  "isSignedIn": "$auth.exists",
  "userId": "$auth.$id",
  "displayName": "$auth.name || 'Guest'",
  "email": "$auth.email",
  "customPhone": "$auth.phone"
}

$auth contains system-managed account values created by Frontbacked.signUp(), refreshed by Frontbacked.signIn(), and extended through Frontbacked.updateUserData(). $id, $createdOn, and $updatedOn are generated auth fields. Read them as $auth.$id, $auth.$createdOn, and $auth.$updatedOn. Read custom user data directly as $auth.phone, $auth.country, or any other non-$ field you store.

Currency Data

Use $currency.value to read the site owner's selected platform currency. If no currency has been selected yet, it resolves to USD.

<span f="true" f-text="$currency.value">USD</span>

Wallet Data

Use $wallet for the signed-in user's wallet balances. $wallet.default reads the balance for the current platform currency, so a site using NGN can render the user's NGN balance without hardcoding the currency key.

<strong f="true" f-text="$wallet.default.formatMoney()">USD 0</strong>
<span f="true" f-text="$wallet.USD.formatMoney()">USD 0</span>

If the user has no stored balance yet, missing wallet values resolve to 0.

Transaction Data

Use $transactions to render the current user's site transactions:

<!-- {STATE}
  state = {
    "transactions": {
      "$list": "$transactions",
      "$order": { "createdOn": "desc" },
      "$limit": 10
    }
  }
-->
<tbody id="transactions" f="true" f-list="$state.transactions">
  <tr>
    <td f="true" f-text="$.tag">investment</td>
    <td f="true" f-text="$.requestedAmount">500</td>
    <td f="true" f-text="$.requestedCurrency">USD</td>
    <td f="true" f-text="$.status">successful</td>
  </tr>
</tbody>

Use requestedAmount and requestedCurrency for the amount the site asked the user to pay. Use paidAmount and paidCurrency for the actual method amount after conversion, including crypto payments.

Transaction list selectors use special state keys such as $list, $where, $order, $limit, and $page.

List Query

{
  "products": {
    "$list": "products",
    "$where": { "status": "published" },
    "$order": { "createdAt": "desc" },
    "$limit": 6
  }
}

Use that selector with f-list="$state.products". The Frontbacked response for the list is also available through $lists.<listId> and includes paging metadata plus items. Each item has the same visible post data shape as a single post lookup, including $id, $type, $createdOn, and $updatedOn.

Those $... fields are the system-generated post values added by Frontbacked when posts are read as list items.

Inside an f-list template, $ is the current item:

<span f="true" f-text="$.name"></span>
<time f="true" f-text="$.$createdOn"></time>

Selector Operators

Use $where objects for list and aggregate filters. Plain keys inside $where are equality checks. Add a comparison suffix for non-equality checks:

{
  "pendingTransactions": {
    "$list": "$transactions",
    "$where": { "status": "pending", "expiresOn.$gt": "$now" },
    "$order": { "expiresOn": "asc", "createdOn": "desc" },
    "$limit": 20
  }
}

Supported suffixes are $gt, $gte, $lt, $lte, $ne, $contains, $startsWith, and $endsWith. Use $and or $or with arrays for grouped logic:

$transactions.{$where: {$or: [{status: 'pending'}, {status: 'pending_review'}]}}.$counts

In f-text and other raw FQL attributes, $now can be written with or without quotes when it is the whole value:

$transactions.{$where: {status: 'pending', 'expiresOn.$gt': $now}}.$counts
$transactions.{$where: {status: 'pending', 'expiresOn.$gt': '$now'}}.$counts

Both forms resolve $now to the page's current-time snapshot. In {STATE} selectors, $now may be written bare in JS-style state declarations or as "$now" in JSON-compatible state. For transaction filters, use expiresOn, not expires.

Aggregates

Aggregates are exposed as special path segments.

SyntaxResult
$transactions.{$where: {status:'successful'}}.$countsCount matching current-user transactions.
$transactions.{$where: {status:'successful'}}.$sum.requestedAmountSum requestedAmount on matching current-user transactions.
$posts.investments.{$where: {status:'active'}}.$countsCount matching posts.
$posts.investments.{$where: {status:'active'}}.$sum.amountSum the amount field on matching posts.

For sums, the field is written after $sum:

$transactions.{$where: {status: 'successful'}}.$sum.requestedAmount

Function calls are written as path segments. The resolved value before .functionName() is passed to the browser function:

$transactions.{$where: {status: 'successful'}}.$sum.requestedAmount.formatMoney()
$transactions.{$where: {status: 'pending'}}.$counts.formatCount()

Frontbacked returns the aggregate first, then the browser passes that returned value to the local function. Do not write $sum(requestedAmount); FQL function calls do not accept arguments.

When summing paidAmount, filter by paidCurrency or another single-method condition to avoid summing mixed units:

$transactions.{$where: {status: 'successful', paidCurrency: 'BTC'}}.$sum.paidAmount

DOM Attributes

AttributePurpose
f="true"Marks an element for Frontbacked rendering.
f-text="expression"Sets textContent.
f-attr-name="expression"Sets/removes the named attribute.
f-show="expression"Shows the element only when the expression resolves truthy. The element is hidden by default while needed data is unavailable.
f-hide="expression"Hides the element when the expression resolves truthy. The element is shown by default while needed data is unavailable.
f-list="$state.selectorName"Repeats the first child using a state selector object.
f-list="$state.settingsList"Repeats a settings selector such as { $list: "$settings.path.to.items" }; authored children remain default/showcase items.
f-insert-pagination="$state.selectorName"Inserts generated pagination buttons into the host element for the matching list selector.
f-insert-pagination="#listId"Inserts generated pagination buttons for the list element with that id.
f-replace-pagination="#listId"Replaces the host element with generated pagination buttons for the list element with that id.
`f-access="guestauth
f-redirect-to="/path"Redirect target used with f-access.
f-loaderMarks an element that should be shown while Frontbacked is loading another page through SPA navigation. It is hidden automatically when idle.
f-nav="reload"Opts out of SPA navigation. Put it on one link, an ancestor, or <body> to make matching links use normal browser reloads.
f-min-load-time="1000"Keeps SPA navigation in the loading state for at least the given milliseconds. Put it on a link, an ancestor, or <body> to preview/test loader UI.
f-insert="/components/header.html"Imports a same-theme HTML fragment and inserts it inside the element before the page is served.
f-replace="/components/footer.html"Imports a same-theme HTML fragment and replaces the host element before the page is served.

Visibility directives are intentionally asymmetric during initial loading. Use f-hide for content that is safe to show first and should disappear only when the expression becomes truthy. Use f-show for content that should stay hidden until Frontbacked has enough data to prove it should be visible, such as dashboard links that depend on $auth.exists.

<a href="/signin" f="true" f-hide="$auth.exists">Sign in</a>
<a href="/dashboard" f="true" f-show="$auth.exists">Dashboard</a>

When Frontbacked hides an element through f-show or f-hide, it sets hidden, aria-hidden="true", and a managed inline style with display: none !important and opacity: 0 !important. When the element becomes visible again, Frontbacked restores the inline display and opacity values that were present before it hid the element.

Settings-backed lists can include more than one authored child. The first child is still the template used for saved data. The remaining children are default/showcase items for fresh sites:

<!-- {STATE}
  state = {
    "services": { "$list": "$settings.home.services.items" }
  }
-->
<section id="services" f="true" f-list="$state.services">
  <article>
    <span class="service-icon">...</span>
    <h3 f="true" f-text="$.title">Default service</h3>
    <p f="true" f-text="$.body">Default copy.</p>
  </article>
  <article>
    <span class="service-icon">...</span>
    <h3>Second default service</h3>
    <p>Second default copy.</p>
  </article>
</section>

If the site owner has no saved array for that setting yet, Frontbacked renders all authored children. Once Frontbacked has a saved array, even an empty array, the saved array controls the public render. If the admin removes every item, the public page renders no list items.

In settings edit mode, when the saved array is shorter than the authored defaults, Frontbacked renders the missing authored defaults as grey restore suggestions, not as live content. Each suggestion has an add-back button that writes a local draft. When the saved array is empty, Frontbacked also shows an Edit List button so the admin can add new items or restore the theme defaults from the list editor.

In settings edit mode, the list container itself is not edited as raw JSON. Frontbacked adds edit controls to the fields declared in the first template, such as $.title and $.body, so theme developers can keep icons, badges, and layout details theme-owned while making only the intended text fields editable.

Settings edit submissions are local drafts first. The editor writes draft values to localStorage; the fixed edit bar shows a Save button with the number of pending edits, plus undo and redo controls. Frontbacked is updated only when the admin clicks the edit bar Save button.

Client Methods

Frontbacked.init({ endpoint, debug, state })
Frontbacked.navigate(url, options?)
Frontbacked.startEditMode(redirectTo?)
Frontbacked.exitEditMode()

Frontbacked.getState(path?)
Frontbacked.setState(path, value)
Frontbacked.setState(values)
Frontbacked.checkState(path, schema, check?)
Frontbacked.confirm(message, title?)

Frontbacked.getSkin()
Frontbacked.listSkins()
Frontbacked.setSkinMode(mode)
Frontbacked.toggleSkinMode()

Frontbacked.image.resize(source, options)
Frontbacked.image.srcset(source, entries)

Frontbacked.video.attach(videoElement, source, options?)
Frontbacked.video.setSimulation(profile)
Frontbacked.video.clearSimulation()
Frontbacked.video.environment(videoElement)

Frontbacked.uploadPost({ type, post })
Frontbacked.updatePost({ id, type, post, merge })
Frontbacked.deletePost({ id })

Frontbacked.signUp({ email, password, name, data, emailVerification })
Frontbacked.signIn({ email, password, rememberMe })
Frontbacked.signOut()
Frontbacked.logout()
Frontbacked.sendEmailVerification({ email, redirectTo })
Frontbacked.passwordReset({ email, path })
Frontbacked.confirmPasswordReset({ email, token, newPassword })
Frontbacked.updateUserData({ data, merge })
Frontbacked.updatePassword({ currentPassword, newPassword })

Frontbacked.billing.getMethods()
Frontbacked.billing.createPayment({ priceRef, amount, tag, metadata })
Frontbacked.billing.showPayment({ paymentId })
Frontbacked.billing.claimPayment({ paymentId, methodId, txHash, receipt, metadata })

All network helpers use the endpoint configured in Frontbacked.init(). They return JSON responses, usually shaped like { ok: boolean, error?: string, message?: string }. Authenticated helpers include the stored bearer token automatically. If a response includes { ok: true, localEmail: { sent: true, mailboxUrl } }, Frontbacked shows a local development notification with a mailbox link for 20 seconds.

Custom FRL endpoints are called under /fb-endpoints with normal fetch:

const response = await fetch("/fb-endpoints/leads/submit", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: "ada@example.com" })
});

const result = await response.json();

Use the FRL Endpoints and Idempotency guide to define the endpoint rule, request guard, response, and repeat-request behavior.

Initialization

Frontbacked.init(options) boots FQL for the current page. It parses state from the {STATE} comment or options.state, scans f-* bindings, fetches required server data, applies page access rules, wires SPA navigation, and renders the DOM.

OptionPurpose
endpointBase API path used for query, auth, and post requests. Defaults to /api.
debugEnables runtime debugging and exposes internal state for inspection.
stateOptional FQL state declaration object. When provided, it is used before the {STATE} HTML comment.

Use f-access and f-redirect-to on <body> to let Frontbacked gate pages without custom page code:

<body f-access="guest" f-redirect-to="/dashboard">
<body f-access="auth" f-redirect-to="/signin">
<body f-access="admin" f-redirect-to="/signin">

Frontbacked.startEditMode(redirectTo = "/") enables settings edit mode by storing the edit-mode flag and navigating to redirectTo when provided. Frontbacked.exitEditMode() disables settings edit mode, removes edit controls, and refreshes the current FQL render.

State

Frontbacked.getState(path?) reads the full state object when path is omitted, or one nested value when path is provided.

const state = Frontbacked.getState();
const email = Frontbacked.getState("form.email");

Frontbacked.setState(path, value) writes one nested value. Frontbacked.setState(values) merges an object into the current state.

Frontbacked.setState("form.status", "Saved");
Frontbacked.setState({
  form: {
    email: "ada@example.com"
  }
});

getState(path) and checkState(path, schema) safely return undefined for paths that move through missing objects. They throw only when the path tries to read deeper through a defined non-object value, such as Frontbacked.getState("ages.array[0].throws") when ages.array[0] is 24.

Array indexes can use square brackets or dot indexes. Frontbacked.getState("sample.array[0]") and Frontbacked.getState("sample.array.0") read the same path. Frontbacked.setState("sample.array[0]", 1) and Frontbacked.setState("sample.array.0", 2) write to the same path.

Confirmation

Frontbacked.confirm(message, title?) shows Frontbacked's built-in confirmation modal and resolves to true or false:

const ok = await Frontbacked.confirm("Do you want to sign out?", "Sign out?");
if (ok) Frontbacked.signOut();

Navigation

Frontbacked.navigate(url, options?) loads a same-site page without a full browser refresh. It fetches the next HTML document, swaps in the new head/body, re-runs FQL for the new page, and uses the browser cache normally so server ETags can revalidate unchanged pages.

Frontbacked.navigate("/packages");
Frontbacked.navigate("/signin", { history: "replace" });
Frontbacked.navigate("/dashboard#deposits", { scroll: "top" });

Navigation options:

OptionPurpose
history"push" by default. Use "replace" to replace the current history entry or "none" for popstate handling.
scroll"top" by default. Hashes scroll to the matching element when present. Use "preserve" to keep the current scroll position.
minLoadTimeMinimum loading time in milliseconds before the loaded page is committed. Useful for testing f-loader.
fallbackReloads the browser on navigation errors by default. Set false if you want to handle failures yourself.

Use a loader anywhere in the page:

<div class="page-loader" f-loader hidden>
  <span></span>
</div>

Opt a link or page out of SPA navigation when you need a hard reload:

<a href="/download" f-nav="reload">Download</a>
<body f-nav="reload">

To inspect a custom loader during development, add a minimum load time:

<body f-min-load-time="1000">
<a href="/packages" f-min-load-time="1500">Compare packages</a>

Frontbacked also prefetches same-site pages on link hover, focus, and touch start. It fetches the HTML with normal browser cache rules, reads the next page's stylesheets, and preloads missing CSS before the actual navigation. During navigation, the old page stays visible until new stylesheets are ready, which prevents a flash of unstyled content.

Skin Helpers

Frontbacked.getSkin() returns the active skin runtime selection, including current mode details when the server provided skin metadata.

Frontbacked.listSkins() returns the available skin catalog from the current runtime settings.

Frontbacked.setSkinMode(mode) sets the current skin mode preference. mode can be a concrete mode such as "dark" or a comma-separated fallback such as "system,dark". Frontbacked stores the preference in localStorage, writes a fb_skin_mode cookie, and reloads the page so server-rendered skin placeholders match the selected mode.

Frontbacked.toggleSkinMode() cycles through available modes for the selected skin, updates storage/cookie state, and reloads the page. It returns the current skin selection when no modes are available.

Image Helpers

Frontbacked.image.resize(source, options) builds a resized URL for a Frontbacked-owned image. source can be a URL string or a file metadata object returned from an upload. At least one of w/width or h/height is required.

const imageUrl = Frontbacked.image.resize(post.image, {
  w: 640,
  h: 420,
  fit: "cover",
  f: "auto",
  q: 82
});

Frontbacked.image.srcset(source, entries) builds a comma-separated srcset string from resize option entries.

External image URLs are not transformed. Use the Image Resizing guide for the full option table and examples.

Video Helpers

Use f-video for Frontbacked-owned video file fields:

<video
  f="true"
  f-video="$.video"
  f-video-player="standard"
  f-video-quality-selector="true"
  f-video-timeline-thumbnails="true"
  playsinline
  preload="metadata"
></video>

Frontbacked.video.attach(videoElement, source, options?) attaches the same adaptive playback behavior from JavaScript and returns a player instance.

const player = await Frontbacked.video.attach(video, post.video, {
  player: "standard",
  qualitySelector: true,
  timelineThumbnails: true,
  hiddenControls: ["time"],
  onPlayback(event, player) {
    console.log(event.remaining);
  }
});

player.seekBy(10);
player.setQuality("auto");
player.appendVideo(nextVideo);
await player.next();
await player.prev();

Video file objects include runtime media metadata on reads. Check video.media.status for uploaded, queued, processing, ready, or failed. Use the Adaptive Video guide for player options, quality selection, timeline thumbnails, playlists, append behavior, previous-video loading, and callbacks.

Upload Helpers

Frontbacked.uploads.status(sessionId?) returns upload progress for files being uploaded in the current page session.

Frontbacked.uploads.pending(post) returns unfinished uploads found in a post object or post id.

Frontbacked.uploads.resume({ post, files?, onFile?, onProgress? }) resumes interrupted uploads. When no matching file is supplied, Frontbacked shows a resume modal that asks the user to choose the original file.

await Frontbacked.uploads.resume({
  post,
  onProgress(progress) {
    console.log(progress.fieldPath, progress.percent);
  }
});

Use the Upload File guide for progress fields, limits, and resume examples.

Post Helpers

Frontbacked.uploadPost({ type, post }) creates a post.

OptionRequiredPurpose
typeyesFRL post type, such as products.
postyesPost data to validate and store.

If post contains File or Blob values at any depth, Frontbacked uploads each file with a resumable flow and stores file metadata in the post. The post body itself is sent as JSON.

Post IDs are generated by Frontbacked. Theme code can use the exact string "$id" as a normal field value on create when that field should store the generated post ID.

Frontbacked.updatePost({ id, type, post, merge }) updates an existing post. id and post are required. merge defaults to true; when true, Frontbacked can merge submitted fields with the stored post before validation.

Frontbacked.deletePost({ id }) deletes a post after Frontbacked checks the post type's delete rule.

Auth Helpers

Frontbacked.signUp({ email, password, name, data, emailVerification }) creates a user account. On success, if the response returns a token, Frontbacked stores it in localStorage and updates $auth.

const result = await Frontbacked.signUp({
  email: "ada@example.com",
  password: "secret-password",
  name: "Ada",
  data: {
    plan: "starter"
  },
  emailVerification: {
    redirectTo: "/dashboard"
  }
});

emailVerification.redirectTo is optional. When present, Frontbacked adds it to the verification email link. After the user clicks the email link and the token is accepted, the page redirects to that local path.

Frontbacked.signIn({ email, password, rememberMe }) signs in. On success, Frontbacked stores the token and updates $auth. rememberMe defaults to true; true stores the token in localStorage, while false stores it in sessionStorage.

const result = await Frontbacked.signIn({
  email: "ada@example.com",
  password: "secret-password",
  rememberMe: true
});

Frontbacked.signOut() and Frontbacked.logout() are the same operation. They remove the auth token from both browser storage locations, set $auth.exists to false, and re-render state-dependent UI.

Frontbacked.sendEmailVerification({ email, redirectTo }) sends or resends the current user's email verification link. Frontbacked checks the current $auth.emailVerified value first; when it is already true, it returns { ok: true, message: "Email is already verified." } without sending a request. Frontbacked also enforces that only unverified email addresses for the authenticated user are sent.

const result = await Frontbacked.sendEmailVerification({
  email: Frontbacked.getState("profile.email"),
  redirectTo: "/dashboard"
});

Frontbacked.passwordReset({ email, path }) requests a password reset email. path is the local page the reset email link should open, such as /password-reset. The response uses a generic success message so account existence is not leaked.

const result = await Frontbacked.passwordReset({
  email: "ada@example.com",
  path: "/password-reset"
});

Frontbacked.confirmPasswordReset({ email, token, newPassword }) submits the new password from a password reset page. email and token normally come from the reset email URL query string.

const params = new URLSearchParams(location.search);
const result = await Frontbacked.confirmPasswordReset({
  email: params.get("email") || "",
  token: params.get("token") || "",
  newPassword: Frontbacked.getState("reset.newPassword")
});

Frontbacked.updateUserData({ data, merge }) updates the signed-in user's public auth data. merge defaults to true; when false, the submitted object replaces the existing data payload.

Frontbacked.updatePassword({ currentPassword, newPassword }) updates the signed-in user's password. Frontbacked verifies currentPassword before storing newPassword.

Billing Helpers

Frontbacked.billing.getMethods() returns the billing methods currently available to the site.

Frontbacked.billing.createPayment({ priceRef, amount, tag, metadata }) creates a deposit payment from a verified price reference. On success, frontbacked.js opens the payment widget. Crypto methods are detected automatically through the payment event socket. Manual methods ask the user to submit a claim only when a manual method is selected.

Post prices use a post reference:

Frontbacked.billing.createPayment({
  priceRef: {
    source: "post",
    type: "products",
    id: productId,
    field: "price"
  },
  amount: 750
})

Settings prices use the exact settings path:

Frontbacked.billing.createPayment({
  priceRef: {
    source: "settings",
    path: "packages[0].price"
  },
  amount: 750
})

frontbacked.js computes a price fingerprint from the FQL data it already has when possible. Theme code should not provide that fingerprint manually. Frontbacked still resolves the authoritative post or settings price again before creating the invoice, locks the requested amount/currency on the transaction, and rejects stale fingerprints when the page is out of date.

Crypto invoices also lock the payable crypto quote. When a user pays less than the quoted crypto amount, Frontbacked converts the received crypto through that locked quote, marks the transaction successful as wallet funding, and credits the user's wallet in the platform currency. When a user pays more than the quote, the original payment succeeds and only the excess converted value is credited to the wallet.

Wallet funding does not use a price reference because the user is choosing how much to add to their wallet:

Frontbacked.billing.createPayment({ amount: 500 })

Frontbacked creates the payment in the platform currency and credits the user's wallet when the payment becomes successful.

Price metadata is exposed to FQL through special buckets on the item or settings object:

<strong f="true" f-text="$.$minAmount.price.formatMoney()">USD 500</strong>
<strong f="true" f-text="$.$fixedAmount.price.formatMoney()">USD 99</strong>

Frontbacked.billing.showPayment({ paymentId }) reopens the payment widget for a pending, unexpired transaction. This is useful for dashboard or transactions rows rendered from $transactions.

<button
  type="button"
  data-payment-id="pay_123"
  onclick="Frontbacked.billing.showPayment({ paymentId: this.dataset.paymentId })"
>
  View payment
</button>

Frontbacked.billing.claimPayment({ paymentId, methodId, txHash, receipt, metadata }) submits a manual payment claim for admin review. Use it only for manual billing methods.

Validation

Frontbacked.checkState(path, schema, check?) validates one state path or a whole state object against a schema. It returns { ok, value, values, errors }. If validation fails, Frontbacked shows messages and returns ok: false. See Schema and Validation for full examples.

const result = Frontbacked.checkState("form", {
  name: {
    type: "string",
    required: true,
    trim: true,
    min: 2
  },
  email: {
    type: "email",
    required: true,
    trim: true
  },
  terms: {
    type: "boolean",
    accepted: true
  },
  avatar: {
    type: "file",
    maxSize: 3000000,
    mimeTypes: ["image/jpeg", "image/png"]
  }
});

if (!result.ok) return;
const { name, email } = result.values;

The optional third argument is a mutable check object:

const check = { hasError: false, errors: [] };
const nameResult = Frontbacked.checkState("form.name", schema.name, check);
const emailResult = Frontbacked.checkState("form.email", schema.email, check);

if (check.hasError) return;

const name = nameResult.value;
const email = emailResult.values.email;

Useful schema keys:

KeyPurpose
typestring, email, number, boolean, object, array, file, or datetime.
requiredRequire a present value. Empty strings, empty arrays, and missing files fail.
trim, lowercase, uppercaseNormalize string values before validation returns. email lowercases by default.
min, maxString length, number value, or array length limit.
patternRegular expression for string values.
enumAllowed values.
sameAsRequire the value to equal another state path.
acceptedRequire a boolean value to be true, useful for terms checkboxes.
maxSizeMaximum file size in bytes.
mimeTypesAllowed file MIME types.
maxDurationSecondsMaximum prepared video playback duration in seconds.
fieldsNested object schema.
of or itemsArray item schema.
messagesOptional messages by failed rule, such as required, min, email, sameAs, accepted, maxSize, or mimeTypes.
feedbackMessage destination options. Omit for automatic inline placement, or use { to: "alert" }, { to: "toast", duration: 4000 }, { to: "formErrors" }, or { to: "showFormError()" }.
validateCustom validation function that receives (value, state). Return false or a string message to fail.
allowFalseTreat boolean false as valid.
allowEmptyStringTreat an empty string as valid.
allowEmptyArrayTreat an empty array as valid.

When a message is missing, Frontbacked generates one from the field key and failed rule. For example, firstName becomes first name, so a missing required value becomes Please enter your first name., while min: 2 becomes First name must be at least 2 characters..

Loading Attributes

Frontbacked automatically locks the element that triggered an FQL event while a Frontbacked network request is pending. The locked element receives frontbacked-loading, and repeated events from that same element are ignored until the request returns.

For onsubmit state bindings, Frontbacked calls preventDefault() before your handler runs, so bound forms do not reload the page while async handlers are waiting.

<button
  type="submit"
  f-loading-text="Saving..."
  f-loading-attr-aria-busy="true"
  f-loading-attr-data-state="loading"
  f-loading-disabled="true"
>
  Save
</button>
AttributePurpose
f-loading-textTemporary innerText while the request is pending.
f-loading-attr-XTemporary attribute value while loading, where X is the HTML attribute name.
f-loading-disabled="true"Fully opts the trigger out of Frontbacked loading UI and re-entrancy locking. The request still runs.

Frontbacked restores the previous text, attributes, disabled state, and class after the request finishes.

Component Imports

Use imports to keep repeated HTML in small theme components without writing layout helper JavaScript.

<div f-insert="/components/header.html"></div>
<div f-replace="/components/footer.html"></div>

f-insert keeps the host element and places the imported HTML inside it. f-replace swaps the host element out completely.

Imported fragments can contain normal FQL markup:

<!-- /components/account-link.html -->
<a href="/dashboard" f="true" f-text="$state.accountLabel || 'Dashboard'">Dashboard</a>

Frontbacked resolves imports before the browser receives the page, so shared components are already in place. Imported f-text, f-attr-*, f-list, and event-bound state sources work like markup written directly on the page.

Import paths are same-theme HTML paths only. URLs, .., hidden files, backend, skins, uploads, and node_modules are blocked. Nested imports are resolved by Frontbacked in one request, with depth and size limits plus recursion detection.

Importing Full Pages

An imported file may be a full HTML document:

<!-- /components/profile-card.html -->
<!doctype html>
<html>
<head>
  <title>This title is not imported</title>
  <!-- {STATE}
    state = {
      "profileName": "$siteInfo.name || 'Investor'"
    }
  -->
</head>
<body>
  <article f="true" f-text="$state.profileName">Investor</article>
</body>
</html>

When a full page is imported, only the imported page's <body> content is inserted. The imported <head> is not inserted, so titles, meta tags, styles, and scripts from the imported page do not leak into the parent page.

The exception is the imported page's {STATE} comment. Frontbacked merges imported head state into the parent page's single {STATE} comment before serving the HTML:

<div f-replace="/components/profile-card.html"></div>

The final page sent to the browser has one head state declaration that contains both imported state and parent page state. Parent page state is added last, so a duplicate key on the parent page overrides the imported key.

If an import fails or a circular import is detected, the placeholder is replaced with a visible frontbacked-import-error block. Valid sibling imports still render.

Page Functions

function formatCurrency(value) {
  return "NGN " + Number(value || 0).toLocaleString();
}

function toggleTerms() {
  return !Frontbacked.getState("terms");
}

window.formatCurrency = formatCurrency;
window.toggleTerms = toggleTerms;

Use global functions by name in FQL:

{
  "amount": "#amount.oninput.target.value.Number()",
  "category": "#categories.onchange.target.value.load().post().parseResponse()",
  "formatted": "$state.amount.formatCurrency()",
  "terms(true)": "#terms.onchange.target.checked, #termsButton.onclick.target.toggleTerms()"
}

When multiple .functionName() segments are chained, Frontbacked pipes the value through them from left to right before setting state.

Publishing Checklist for FQL Pages

  1. State bindings use stable selectors that continue to match the intended elements.
  2. Every rendered element has f="true".
  3. Lists have an id and a first-child template.
  4. Forms prevent duplicate submits and show a submitting state.
  5. File inputs store File values in state before upload/update.
  6. Commas are used for multiple event sources, && is used for truthy gating, and || is used for fallbacks.
  7. Writes have matching FRL schemas and permission rules.