Frontbacked Docs

Data Fetching

FQL lets your page ask for the data it needs directly from the markup. When expressions reference settings, posts, transactions, wallet balances, aggregates, or lists, Frontbacked loads the missing data and re-renders the page state for you.

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 storefront currency context selected by the site owner or visitor.
$walletThe signed-in user's wallet balances, including $wallet.default for the platform currency.
$editModePage edit-mode status, including active, canEnable, and whether the browser has accepted the edit-mode warning.
$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

Frontbacked makes route params and public site info available to your page. 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",
    email: "hello@example.com",
    phone: "+15550100",
    businessAddress: "12 Market Street",
    whatsappContact: "+15550100",
    telegramContact: "@acme",
    socialProfiles: [
      { platform: "instagram", label: "Instagram", handle: "@acme", url: "https://instagram.com/acme" }
    ]
  };
</script>

Use them in FQL:

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

$siteInfo can include public contact fields selected by the site admin: email, phone, businessAddress, whatsappContact, telegramContact, and socialProfiles. Social profiles use stable platform keys such as instagram, facebook, x, tiktok, youtube, linkedin, whatsapp, telegram, and pinterest. Frontbacked normalizes handles and returns a generated url when the platform can safely build one. Theme code should show a social profile only when profile.url exists, and can map profile.platform to its preferred icon library.

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.data.title || 'Article'",
  "summary": "$state.article.data.summary || ''"
}

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

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

Another example:

<!-- {STATE}
  state = {
    "product": "products{id:$query.productId}",
    "productTitle": "$state.product.data.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. Theme-owned post values are nested under data. Do not include system fields in the post data you submit.

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

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

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

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

Read system fields at the root and theme-owned post fields under data:

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

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

{
  "profileName($posts.profile_updates{name:'Elijah'}.data.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 the root system 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 root system fields and place theme-owned values under data.

Reference Fields

Post and user reference fields are saved as IDs. When a lookup or list template asks for the reference field, Frontbacked expands that field in place:

<article f="true" f-list="reviews">
  <p f-text="$.reviewer.data.name"></p>
  <p f-text="$.product.data.name"></p>
  <p f-text="$.product.category.name"></p>
  <p f-text="$.body"></p>
</article>

Nested references use the same field path style. If review.product points to a product and product.category points to a category, read $.product.category.name.

FQL expands up to five reference hops in one requested path. A path like $.review.product.category.name uses two hops: review to product, then product to category. That gives normal product, booking, marketplace, directory, membership, and content sites plenty of room to pull rich connected data while keeping pages fast. For deeper tree-style screens, fetch the next level with another list or detail query.

If a page does not ask for reviewer, product, or another reference field, those fields remain the saved ID values in the returned 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 stores custom user data under $auth.data through Frontbacked.updateUserData().

{
  "isSignedIn": "$auth.exists",
  "userId": "$auth.id",
  "displayName": "$auth.name || 'Guest'",
  "email": "$auth.email",
  "phone": "$auth.data.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.data.phone, $auth.data.country, ...Custom user data fields from Frontbacked.updateUserData().

System fields are at the resource root and theme-owned values are under data: $state.profile.id, $.id, $auth.id, $auth.data.phone, and $.data.name.

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 completed 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 rows expose the stored status. Completed payments currently appear as successful in FQL transaction filters and rows. FRL payment events expose the same completion event as payment confirmed with $payment.status == "confirmed" so rule code reads like an event, while $payment.rawStatus keeps the stored transaction value available.

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'}}.$count">0</strong>
<strong f="true" f-text="$posts.investments.{$where: {status: 'active'}}.$count">0</strong>

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

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

Parentheses run registered page functions. They do not select aggregate fields.

$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). Function arguments are supported, but aggregate fields are selected as path segments after $sum.

Older themes may still use $counts, but new FQL should use $count.

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.

Money fields use a currency-aware comparison value. The comparison is made only against that currency's entry, so USD and NGN amounts are never mixed:

{
  "highValueAccounts": {
    "$list": "accounts",
    "$where": {
      "balance.$gte": { currency: "USD", amount: "1000.00" }
    },
    "$order": { "createdOn": "desc" },
    "$limit": 20
  }
}

Use balance: { currency: "USD", amount: "1000.00" } for equality. Money supports $gt, $gte, $lt, $lte, and $ne; text operators such as $contains do not apply. The query field is included in the theme's automatic index contract when the selector is processed, so theme developers do not need to declare an index manually.

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 system fields from $ and theme-owned fields from $.data.

<!-- {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" }
      ]
    }
  }
}

Efficient Updates

Frontbacked remembers which data is already available on the page. If a state change only affects local display values, it re-renders immediately without asking the server for the same data again.

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.data.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>