Data Fetching

FQL does not require you to manually build a fetch plan. When expressions reference settings, posts, transactions, wallet balances, aggregates, or lists, frontbacked.js collects the missing data and requests it from Frontbacked.

Data Roots

RootPurpose
$queryBrowser URL query string values.
$paramsServer-provided route params from window.$params.
$siteInfoServer-provided site information from window.$siteInfo.
$authThe current signed-in user, fetched when needed.
$currencyThe platform currency selected by the site owner.
$walletThe signed-in user's wallet balances, including $wallet.default for the platform currency.
$settingsSite/theme settings fetched from Frontbacked.
bare post typeA single post lookup, such as articles{id:$query.id}.
$posts or $postA single post lookup with an explicit post root, such as $posts.articles{id:$query.id}.
$transactionsCurrent user's site transaction records and aggregates.
$listA list query declared in state.
$stateCurrent browser state.
$Current item inside an f-list template.

Query String

For a URL like /signup?plan=pro&ref=partner, use $query.

{
  "selectedPlan": "$query.plan || 'starter'",
  "referral": "$query.ref || 'direct'"
}

Route Params and Site Info

The server exposes route params and site info on window. Route params usually come from backend/paths.json; see Theme Routing for the full routing file format.

<script>
  window.$params = { slug: "starter-plan" };
  window.$siteInfo = { name: "Acme Capital", domain: "acme.example" };
</script>

Use them in FQL:

{
  "slug": "$params.slug",
  "siteName": "$siteInfo.name || 'Website'"
}

Settings

Use $settings for site or theme settings.

{
  "brandName": "$settings.site.name || $siteInfo.name || 'Frontbacked Site'",
  "supportEmail": "$settings.contact.email || 'support@example.com'"
}

When a referenced setting path is missing locally, Frontbacked requests it.

Single Post Lookup

Use the post type name as the root, followed by a lookup projection.

{
  "article": "articles{slug:$params.slug}",
  "title": "$state.article.title || 'Article'",
  "summary": "$state.article.summary || ''"
}

You can also use $posts or $post before the post type:

{
  "article": "$posts.articles{slug:$params.slug}",
  "title": "$state.article.title || 'Article'"
}

Another example:

<!-- {STATE}
  state = {
    "product": "products{id:$query.productId}",
    "productTitle": "$state.product.title || 'Product'"
  }
-->

The lookup object selects one post of that type.

Single Post Shape

A single post lookup resolves to the visible data for the matched row.

$id, $type, $createdOn, and $updatedOn are system fields generated and managed by Frontbacked. They are created for posts submitted through Frontbacked.uploadPost() and preserved when posts are changed through Frontbacked.updatePost(). Do not include these fields in the post data you submit. Frontbacked adds them when the post is read back through single post lookups and list items.

If the current user is the post author, the post object is:

{
  ...publicPostFields,
  ...authorOnlyPostFields,
  $id: "post id",
  $type: "post type",
  $createdOn: "created timestamp",
  $updatedOn: "updated timestamp"
}

If the current user is not the post author, the post object is:

{
  ...publicPostFields,
  $id: "post id",
  $type: "post type",
  $createdOn: "created timestamp",
  $updatedOn: "updated timestamp"
}

Read normal post fields directly:

{
  "profile": "$posts.profile_updates{name:'Elijah'}",
  "profileName": "$state.profile.name",
  "profileId": "$state.profile.$id"
}

You can also read a field directly from the lookup expression:

{
  "profileName($posts.profile_updates{name:'Elijah'}.name)": "#profileName.oninput.target.value"
}

Post Lookup Fields

Post lookups and list selector $where objects can read two kinds of fields:

Field keyReads from
slug, name, parent_idThe visible post data. These keys can match public post data, or the current author's private post data.
$id, $type, $authorId, $status, $createdOn, $updatedOnSystem post fields.

Use plain keys for data your theme stores in the post body:

{
  "profile": "$posts.profile_updates{name:'Elijah'}"
}

Use a $ key when you mean a posts-table column:

{
  "myProfile": "$posts.profile_updates{$authorId:$auth.$id}"
}

The same rule applies to list selectors:

{
  "categoryProducts": {
    "$list": "products",
    "$where": { "parent_id": "$state.categoryId" }
  },
  "myProducts": {
    "$list": "products",
    "$where": { "$authorId": "$auth.$id" }
  }
}

$authorId and $status are useful lookup/filter keys. Returned post objects expose $id, $type, $createdOn, and $updatedOn as generated fields. Use plain field names for your own post data.

Auth Shape

Use $auth for the current signed-in user.

The auth object is also system-managed. Frontbacked creates account values when a user signs up with Frontbacked.signUp(), refreshes them when the user signs in with Frontbacked.signIn(), and updates custom user data through Frontbacked.updateUserData(). $id, $createdOn, and $updatedOn are generated auth fields. Custom user data is flattened directly onto $auth.

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

Common $auth fields:

FieldMeaning
$auth.existstrue when a user is signed in.
$auth.$idCurrent user id.
$auth.nameCurrent user display name.
$auth.emailCurrent user email.
$auth.emailVerifiedEmail verification state.
$auth.$createdOnUser creation timestamp.
$auth.$updatedOnUser update timestamp.
$auth.phone, $auth.country, ...Custom user data fields from Frontbacked.updateUserData().

Post and auth generated fields use $ names, such as $state.profile.$id, $.$createdOn, $auth.$id, and $auth.$createdOn. Normal data fields do not start with $, such as $state.profile.name, $.name, and $auth.phone.

Currency

Use $currency for the site owner's platform currency.

{
  "currencyCode": "$currency.value || 'USD'"
}

$currency.value is a currency code such as USD or NGN. If the admin has not selected a currency, Frontbacked uses USD.

Wallet

Use $wallet for the signed-in user's wallet balances. It is auth-backed, so Frontbacked fetches the current user when a page references it.

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

$wallet.default resolves to the balance for $currency.value. Missing wallet balances resolve to 0, which keeps dashboards renderable before the user has made a successful payment.

Transactions

Use $transactions for the current user's site transactions. This is useful for dashboards, portfolios, and transaction history pages without writing a custom endpoint for every theme.

<!-- {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="$.kind">deposit</td>
    <td f="true" f-text="$.tag">investment</td>
    <td f="true" f-text="$.requestedAmount">500</td>
    <td f="true" f-text="$.status">successful</td>
  </tr>
</tbody>

Transaction rows include requested payment values and actual paid values:

FieldMeaning
requestedAmountAmount the site asked the user to pay in the platform currency.
requestedCurrencyPlatform currency for the requested amount, such as USD or NGN.
paidAmountAmount the user actually paid after any method conversion.
paidCurrencyCurrency or crypto asset the user paid with, such as USD, BTC, or ETH.
statusPayment status, such as pending, failed, or successful.
kindTransaction direction, such as deposit.
tagTheme-provided label for what the payment is for.

Transaction selectors in state use special keys such as $list, $where, $order, $limit, and $page. This keeps selector metadata separate from normal transaction fields.

Aggregates

FQL can ask Frontbacked for counts and sums for $transactions and post sources. Frontbacked returns only the computed value.

<strong f="true" f-text="$transactions.{$where: {status: 'successful'}}.$sum.requestedAmount">0</strong>
<strong f="true" f-text="$transactions.{$where: {status: 'pending'}}.$counts">0</strong>
<strong f="true" f-text="$posts.investments.{$where: {status: 'active'}}.$counts">0</strong>

For sums, the field to sum is a normal path segment after $sum:

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

Parentheses always run local browser functions. They do not pass arguments to aggregate selectors.

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

In that expression, Frontbacked sums requestedAmount, then the browser passes the returned value into formatMoney.

Do not use $sum(requestedAmount). FQL function calls do not accept arguments, and aggregate fields are selected as path segments after $sum.

When summing paidAmount, filter to one currency or billing method so you do not add mixed units together:

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

In raw FQL attributes, dynamic roots can be written with or without quotes when the whole quoted value is a FQL special root/path:

<strong f="true" f-text="$transactions.{$where: {status: 'pending', 'expiresOn.$gt': $now}}.$counts">0</strong>
<strong f="true" f-text="$transactions.{$where: {status: 'pending', 'expiresOn.$gt': '$now'}}.$counts">0</strong>

Both forms above 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. Use the transaction field name expiresOn, not expires.

Lists in State

Define list selectors in state. The same object controls source, filters, ordering, and limits.

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

$list can be a post type, $posts.type, $transactions, or $settings.path.to.array. The $where object supports equality keys plus suffixes like price.$gte, tag.$contains, and tag.$startsWith.

Supported list selector keys:

KeyExample
$list"$list": "products"
$where"$where": { "status": "published", "price.$gte": 1000 }
$order"$order": { "createdAt": "desc" }
$limit"$limit": 6
$page"$page": 1

Plain keys such as source, where, order, limit, and page are ordinary data fields. They are not list selector controls.

After the list renders, Frontbacked exposes paging metadata and items through $lists.<listId>:

{
  "firstProductName": "$lists.publishedProducts.items[0].name",
  "firstProductId": "$lists.publishedProducts.items[0].$id"
}

When pagination loads a new page from the server, Frontbacked replaces the list items with the new page instead of appending them.

Each list item has this shape:

{
  ...visiblePostData,
  $id: "post id",
  $type: "post type",
  $createdOn: "created timestamp",
  $updatedOn: "updated timestamp"
}

The $id, $type, $createdOn, and $updatedOn values are the same system-generated post fields available on single post lookups. They are added by Frontbacked when posts are read into list items.

Inside list templates, $ is the current item. Read visible post fields directly from $.

<!-- {STATE}
  state = {
    "profiles": {
      "$list": "profile_updates",
      "$where": { "name": "Elijah" }
    }
  }
-->
<ul id="profiles" f="true" f-list="$state.profiles">
  <li>
    <span f="true" f-text="$.name"></span>
    <small f="true" f-text="$.$createdOn"></small>
  </li>
</ul>

Lists in the DOM

Point list root elements to selector state values.

<!-- {STATE}
  state = {
    "articles": {
      "$list": "articles",
      "$where": { "status": "published" },
      "$order": { "createdAt": "desc" },
      "$limit": 10
    }
  }
-->
<ul
  id="articles"
  f="true"
  f-list="$state.articles"
>
  <li>
    <a f="true" f-attr-href="$.slug">
      <span f="true" f-text="$.title"></span>
    </a>
  </li>
</ul>

Selector Comparison Operators

Plain keys inside $where are equality checks. Use these suffixes for other comparisons:

| Suffix | Meaning | Example | | --- | --- | | $gt | Greater than | { "expiresOn.$gt": "$now" } | | $gte | Greater than or equal | { "amount.$gte": 5000 } | | $lt | Less than | { "createdOn.$lt": "2026-06-05T09:00:00.000Z" } | | $lte | Less than or equal | { "amount.$lte": 5000 } | | $ne | Not equal | { "status.$ne": "failed" } |

Use $and and $or with arrays for grouped logic:

{
  "actionablePayments": {
    "$list": "$transactions",
    "$where": {
      "$or": [
        { "status": "pending", "expiresOn.$gt": "$now" },
        { "billingMethodType": "manual", "status": "pending_review" }
      ]
    }
  }
}

Request Efficiency

Frontbacked keeps track of the last request plan. If state changes do not require new settings, posts, transactions, aggregates, or lists, it skips the network request and only re-renders local state.

This means you can freely derive display values from $state without causing unnecessary network requests.

Full Data Page Example

<head>
  <!-- {STATE}
    state = {
      "siteName": "$settings.site.name || $siteInfo.name || 'My Site'",
      "article": "articles{slug:$params.slug}",
      "articleTitle": "$state.article.title || 'Article'",
      "related": {
        "$list": "articles",
        "$where": { "status": "published" },
        "$order": { "createdAt": "desc" },
        "$limit": 3
      }
    }
  -->
</head>
<body>
  <h1 f="true" f-text="$state.articleTitle">Article</h1>
  <p f="true" f-text="$state.siteName">My Site</p>

  <ul id="relatedArticles" f="true" f-list="$state.related">
    <li>
      <a f="true" f-attr-href="$.slug">
        <span f="true" f-text="$.title || 'Untitled'">Untitled</span>
      </a>
    </li>
  </ul>
</body>