Frontbacked Docs

FQL Overview

A Frontbacked theme is the reusable website package you ship: pages, styles, scripts, assets, optional skins, and optional rules for the data and actions the site supports.

Frontbacked Query Language (FQL) is the frontend language built into frontbacked.js. It lets that theme declare browser state, read URL/query/site values, load Frontbacked data, and render values into the DOM without writing a custom data layer for every page.

With FQL, plain HTML can become a signed-in dashboard, a marketplace listing page, an upload form, a site settings editor, a wallet-powered checkout, or an adaptive video experience while the theme stays easy to read and ship.

FQL appears in three places:

  1. A {STATE} comment inside <head>.
  2. The state option passed to Frontbacked.init().
  3. f-* attributes on HTML elements.

When both are present, Frontbacked.init({ state }) is used first. The {STATE} comment is the fallback when no state is passed to init.

Minimal Page

<!doctype html>
<html>
  <head>
    <!-- {STATE}
      state = {
        "name": "#name.oninput.target.value",
        "displayName": "$state.name || $query.name || 'Guest'"
      }
    -->
  </head>
  <body>
    <input id="name" name="name">
    <h1 f="true" f-text="$state.displayName">Guest</h1>

    <script src="https://cdn.frontbacked.com/versions/frontbacked-v37.0.105.js"></script>
    <script>
      Frontbacked.init({ endpoint: "/api" });
    </script>
  </body>
</html>

When the user types into #name, FQL updates $state.name, recomputes $state.displayName, and renders the new value into the h1.

Minimal Form

FQL selectors are normal CSS selectors. You can use ids, descendants, attributes, and spaces in the selector before the event segment.

<!doctype html>
<html>
  <head>
    <!-- {STATE}
      state = {
        "signup": {
          "email": "#signupForm input[type=email].oninput.target.value",
          "password": "#signupForm input[type=password].oninput.target.value",
          "terms(false)": "#signupForm input[name=terms].onchange.target.checked",
          "response": "#signupForm.onsubmit.handleSignup()"
        }
      }
    -->
  </head>
  <body>
    <form id="signupForm">
      <input type="email" name="email" placeholder="you@example.com">
      <input type="password" name="password" placeholder="Password">
      <label>
        <input type="checkbox" name="terms">
        I accept the terms
      </label>

      <button
        type="submit"
        f-loading-text="Creating account..."
        f-loading-attr-aria-busy="true"
      >
        Create account
      </button>
    </form>

    <script src="https://cdn.frontbacked.com/versions/frontbacked-v37.0.105.js"></script>
    <script>
      const signupSchema = {
        email: {
          type: "email",
          required: true,
          trim: true,
          messages: {
            required: "Enter the email address you want to use."
          }
        },
        password: {
          type: "string",
          required: true,
          min: 8,
          messages: {
            min: "Use at least 8 characters."
          }
        },
        terms: {
          type: "boolean",
          accepted: true
        }
      };

      async function handleSignup(event) {
        const result = Frontbacked.state.check("signup", signupSchema);
        if (!result.ok) return;

        const { email, password } = result.values;
        return await Frontbacked.signUp({
          email,
          password,
          name: email,
          emailVerification: {
            redirectTo: "/dashboard"
          }
        });
      }

      Frontbacked.functions.define({ handleSignup });
      Frontbacked.init({ endpoint: "/api" });
    </script>
  </body>
</html>

In this form, FQL keeps state in sync, prevents the default form reload for the onsubmit binding, Frontbacked.state.check() shows validation messages, and frontbacked.js locks the submit button while Frontbacked.signUp() is waiting for a response. The button gets frontbacked-loading, its text changes through f-loading-text, and repeated submits from the same button are ignored until the request finishes.

The messages object is optional and can define messages for specific failed checks. In the example, email.required and password.min use custom copy. Other failures are still handled: an invalid email gets a generated message such as Please enter a valid email address., an empty password gets Please enter your password., and unchecked terms gets Please accept terms. Frontbacked builds those fallback messages by turning field keys such as firstName or first_name into readable names.

What FQL Handles

FQL handles common frontend jobs:

JobExample
State from DOM events"email": "#email.oninput.target.value"
Form validationFrontbacked.state.check("signup", signupSchema)
Loading/re-entry guardsf-loading-text, f-loading-attr-*, frontbacked-loading
Page access gatesf-access="auth", f-redirect-to="/signin"
Provider sign-inf-auth-provider="google" or f-auth-provider="github", f-auth-return-to="/dashboard"
Admin controlsf-admin-link, f-edit-mode-enable, Frontbacked.editMode.enable()
Component importsf-insert="/components/header.html", f-replace="/components/footer.html"
Derived state`"title": "$state.product.data.title
Live saved data"messages.live": { "$list": "messages" }
URL and server values"$query.plan", "$params.slug", "$siteInfo.name"
Frontbacked data requests"$settings.site.name", "products{id:$query.id}.title"
DOM renderingf-text, f-attr-*, f-list
Conditional visibilityf-show="$auth.exists", f-hide="$auth.exists"
File uploadsFrontbacked.createPost({ type, post }) with files anywhere in post
Upload recoveryFrontbacked.uploads.resume({ post })
Image variantsFrontbacked.image.resize(post.image, { width: 640 })
Adaptive videoFrontbacked.video.attach(video, post.video, options)
Programmatic playbackplayer.seekBy(10), player.appendVideo(nextVideo)
Billing flowsFrontbacked.endpoints.call("create_product_payment", { body }) followed by Frontbacked.billing.showPayment(...)
Theme skinsFrontbacked.setSkinMode("dark"), Frontbacked.skins.setSiteSkin("pink_atelier")
Support launcherf-support-launcher, Frontbacked.supportLauncher.open()
Same-site navigationFrontbacked.navigate("/dashboard")
Custom theme endpointsFrontbacked.endpoints.call("endpointName", { body })

Initialization

Load frontbacked.js from the Frontbacked CDN, then call Frontbacked.init().

<script src="https://cdn.frontbacked.com/versions/frontbacked-v37.0.105.js"></script>
<script>
  Frontbacked.init({
    endpoint: "/api",
    debug: false,
    state: {
      "form": {
        "email": "#email.oninput.target.value"
      },
      "emailLabel": "$state.form.email || 'No email yet'"
    }
  });
</script>

If you load the CDN script with defer, run your initialization from a deferred page script or a DOMContentLoaded handler so Frontbacked is available first.

Options:

OptionPurpose
endpointBase API path used for query, auth, and post requests. Defaults to /api.
debugShows extra FQL details in the browser console while you build.
stateOptional state declaration object. If provided, it is used before the {STATE} HTML comment.

Register every page function that an FQL expression may call before initialization:

function formatHeadline(value) {
  return String(value || "").trim().toUpperCase();
}

Frontbacked.functions.define({ formatHeadline });

FQL Lifecycle

When initialized, frontbacked.js reads the page's state declaration, binds the declared DOM events, loads the settings, posts, lists, and account values the page asks for, then renders matching f-* elements. When state changes, derived values and bound DOM elements update again.

State keys ending in .live continue to follow saved data changes after the first render. Frontbacked keeps one live connection for the page, refreshes affected selections through the same FRL permissions, recomputes dependent state, and updates the DOM.

For conditional visibility, f-hide elements stay visible until their expression resolves truthy, while f-show elements are hidden as soon as Frontbacked starts and stay hidden until their expression resolves truthy. This makes f-show the safer choice for links or panels that must not flash before auth or requested data is known.

Frontbacked also handles smooth same-site page navigation. When a visitor clicks an internal link, frontbacked.js loads the next HTML page, applies the new page, re-runs FQL, and updates browser history without a full reload.

Add a custom loading view with f-loader:

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

Frontbacked waits for the next page's new stylesheets before swapping the body, so visitors do not see the next page before its CSS is ready. It also prefetches same-site links on hover, focus, and touch start so navigation feels quick.

Use f-nav="reload" when a link or whole page should keep normal browser reload behavior:

<a href="/checkout" f-nav="reload">Open checkout</a>
<body f-nav="reload">

Programmatic navigation is available through Frontbacked.navigate("/packages"). By default it pushes history and scrolls to the top. Use { history: "replace" } for redirects, { scroll: "preserve" } when the current scroll should stay in place, and { minLoadTime: 1000 } when you want to preview a loader for at least one second.

The same minimum loading time can be declared in HTML:

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

Component Imports

Use f-insert and f-replace for shared page parts:

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

The imported HTML is loaded from the same theme before the page is served. Nested imports are resolved by Frontbacked, and FQL bindings inside imported fragments work as if the HTML was written directly on the page.

Imported files can be fragments or complete HTML pages. When the imported file is a complete page with <head> and <body>, Frontbacked inserts only the content inside that imported page's <body>. The imported page's <head> is not inserted into the page, except for its FQL {STATE} declaration.

If imported files contain {STATE} comments in their <head>, Frontbacked merges those declarations into one {STATE} comment in the main page head before the page is sent to the browser. Imported state is placed first, then the main page state, so the main page can override a duplicate state key.

<!-- /components/account-summary.html -->
<!doctype html>
<html>
<head>
  <!-- {STATE}
    state = {
      "accountLabel": "$siteInfo.name || 'Account'"
    }
  -->
</head>
<body>
  <a href="/dashboard" f="true" f-text="$state.accountLabel">Account</a>
</body>
</html>
<!-- /index.html -->
<head>
  <!-- Frontbacked merges the imported state into this page head. -->
</head>
<body>
  <div f-replace="/components/account-summary.html"></div>
</body>

If an import cannot be resolved, is blocked, is too large, too deep, or creates a circular import, Frontbacked replaces that import placeholder with a visible frontbacked-import-error block so the issue is obvious during theme development.

Provider buttons behave the same way for unsupported values: f-auth-provider elements stay hidden when a known provider is disabled, but Frontbacked renders a visible FQL-style error when the site does not return the requested provider id. Google and GitHub are available to themes when they are configured and enabled.

FQL and FRL Together

FQL is not a security layer. It improves the frontend developer experience by declaring state and requests. FRL protects saved data by validating writes and enforcing permissions.

Use FQL to submit a signup form:

await Frontbacked.signUp({
  email: Frontbacked.state.get("form.email"),
  password: Frontbacked.state.get("form.password"),
  name: Frontbacked.state.get("form.name"),
  emailVerification: {
    redirectTo: "/dashboard"
  }
});

Use FRL to validate any post data that the frontend creates or updates.

A Practical Theme Page

<head>
  <!-- {STATE}
    state = {
      "article": "articles{slug:$params.slug}",
      "title": "$state.article.data.title || 'Article'",
      "author": "$state.article.data.authorName || $siteInfo.name || 'Frontbacked'"
    }
  -->
</head>
<body>
  <article>
    <h1 f="true" f-text="$state.title">Article</h1>
    <p f="true" f-text="$state.author">Frontbacked</p>
  </article>
</body>

This page fetches an article by route param, derives display state, and renders it into the page.