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
| Root | Purpose |
|---|---|
$query | Browser URL query string values. |
$params | Server-provided route params from window.$params. |
$siteInfo | Server-provided site information from window.$siteInfo. |
$auth | The current signed-in user, fetched when needed. |
$currency | The platform currency selected by the site owner. |
$wallet | The signed-in user's wallet balances, including $wallet.default for the platform currency. |
$settings | Site/theme settings fetched from Frontbacked. |
| bare post type | A single post lookup, such as articles{id:$query.id}. |
$posts or $post | A single post lookup with an explicit post root, such as $posts.articles{id:$query.id}. |
$transactions | Current user's site transaction records and aggregates. |
$list | A list query declared in state. |
$state | Current 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 key | Reads from |
|---|---|
slug, name, parent_id | The visible post data. These keys can match public post data, or the current author's private post data. |
$id, $type, $authorId, $status, $createdOn, $updatedOn | System 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:
| Field | Meaning |
|---|---|
$auth.exists | true when a user is signed in. |
$auth.$id | Current user id. |
$auth.name | Current user display name. |
$auth.email | Current user email. |
$auth.emailVerified | Email verification state. |
$auth.$createdOn | User creation timestamp. |
$auth.$updatedOn | User 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:
| Field | Meaning |
|---|---|
requestedAmount | Amount the site asked the user to pay in the platform currency. |
requestedCurrency | Platform currency for the requested amount, such as USD or NGN. |
paidAmount | Amount the user actually paid after any method conversion. |
paidCurrency | Currency or crypto asset the user paid with, such as USD, BTC, or ETH. |
status | Payment status, such as pending, failed, or successful. |
kind | Transaction direction, such as deposit. |
tag | Theme-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:
| Key | Example |
|---|---|
$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>