Starter Examples
These examples are small enough to copy, but shaped like real themes. Start with the page, add the rule, then change the fields to match the product you are building.
Folder Shape
my-theme/
index.html
dashboard.html
css/
app.css
js/
app.js
backend/
index.rules
examples.json
paths.json
Use HTML for pages, CSS for the look, and normal JavaScript for interactions. FQL keeps the page connected to state and saved data. FRL decides what can be saved, read, changed, or exposed.
Starter Content for New Sites
Add an optional backend/examples.json file when a theme is easier to understand with a few realistic records already in place. Frontbacked creates those records once when a site is created from the theme. Site owners can edit, keep, or delete them like any other post.
{
"version": 1,
"assets": {
"cover": { "path": "assets/cover.webp" }
},
"posts": {
"categories": [
{ "key": "featured", "data": { "name": "Featured" } }
],
"products": [
{
"key": "starter",
"data": {
"name": "Starter product",
"price": "49.00",
"category": { "$examplePost": "categories/featured" },
"image": { "$themeAsset": "cover" },
"owner": { "$postAuthor": true }
}
}
]
}
}
The post types and fields must already be declared in backend/index.rules. Money examples contain only an amount; the new site's base currency is applied automatically. Use $examplePost for another example post, $postAuthor for the generated starter-post author, $siteOwner when the site owner is also a tenant user, and $themeAsset for a file declared in assets. Theme assets stay shared and are referenced by content hash, so creating many sites does not duplicate their bytes.
Public Waitlist
This page collects a lead, validates it in the browser, and stores it through a guarded endpoint.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Launch List</title>
<link rel="stylesheet" href="/css/app.css">
<!-- {STATE}
state = {
"lead": {
"name": "#waitlistForm input[name=name].oninput.target.value",
"email": "#waitlistForm input[name=email].oninput.target.value",
"idea": "#waitlistForm textarea[name=idea].oninput.target.value",
"response": "#waitlistForm.onsubmit.joinWaitlist()"
}
}
-->
</head>
<body>
<main class="page">
<section class="intro">
<p class="eyebrow">Private beta</p>
<h1>Join the first builders using Launch List.</h1>
<p>Tell us what you want to build. We will send the first invites to people with clear use cases.</p>
</section>
<form id="waitlistForm" class="panel">
<label>
Name
<input name="name" autocomplete="name" required>
</label>
<label>
Email
<input name="email" type="email" autocomplete="email" required>
</label>
<label>
Startup idea
<textarea name="idea" rows="4"></textarea>
</label>
<button type="submit" f-loading-text="Joining...">Join waitlist</button>
<p f="true" f-show="$state.lead.response.ok" f-text="$state.lead.response.message"></p>
</form>
</main>
<script src="https://cdn.frontbacked.com/versions/frontbacked-v37.0.105.js"></script>
<script src="/js/app.js"></script>
</body>
</html>
const leadSchema = {
name: { type: "string", required: true, trim: true, min: 2, max: 120 },
email: { type: "email", required: true, trim: true },
idea: { type: "string", trim: true, max: 600 }
};
async function joinWaitlist() {
const result = Frontbacked.state.check("lead", leadSchema);
if (!result.ok) return;
const response = await Frontbacked.endpoints.call("joinWaitlist", {
allowGuest: true,
body: result.values
});
if (response.ok) {
Frontbacked.state.set("lead", { name: "", email: "", idea: "", response });
}
return response;
}
Frontbacked.functions.define({ joinWaitlist });
Frontbacked.init({ endpoint: "/api" });
version 2;
post leads as "Waitlist Leads" {
before create {
$post.pending.data.name = str.trim($post.pending.data.name);
$post.pending.data.email = str.lower(str.trim($post.pending.data.email));
$post.pending.data.status = "new";
}
schema {
name as "Name": { type: "string", min: 2, max: 120, required: true }
email as "Email": { type: "email", required: true, immutable: true }
idea as "Startup idea": { type: "string", max: 600 }
status as "Status": { type: "enum", enums: ["new", "contacted", "invited"], default: "new" }
}
allow create: $admin.canCreate("$this")
allow get: $admin.canRead("$this")
allow list(limit=20, max=100): $admin.canRead("$this")
allow edit: $admin.canEdit("$this")
allow delete: $admin.canDelete("$this")
}
endpoint.post joinWaitlist("/waitlist") as "Join Waitlist" {
must($request.body.email, "Email is required", 422);
$context.email = str.lower(str.trim($request.body.email));
dedupe `waitlist:${$context.email}`;
post.createAsSystem("leads", {
name: str.trim($request.body.name),
email: $context.email,
idea: str.trim($request.body.idea),
status: "new"
});
return {
ok: true,
message: "You are on the list."
};
}
Read next: FQL State, DOM Bindings, FRL Schemas and Validation, and Endpoints and Dedupe.
Protected Dashboard
Use f-access on pages that require a signed-in visitor or an admin. The dashboard can read live leads and update them with the same post API.
<head>
<!-- {STATE}
state = {
"recentLeads.live": {
"$list": "leads",
"$order": { "createdOn": "desc" },
"$limit": 20
}
}
-->
</head>
<body f-access="admin" f-redirect-to="/signin">
<main class="dashboard">
<header>
<h1>Waitlist</h1>
<a href="/" f-admin-link>Open site admin</a>
</header>
<section f-list="$state.recentLeads">
<article f-key="$.id" class="row">
<div>
<strong f="true" f-text="$.name">Name</strong>
<span f="true" f-text="$.email">email@example.com</span>
</div>
<button
type="button"
f="true"
f-attr-data-id="$.id"
onclick="markInvited(this.dataset.id)"
f-loading-text="Saving..."
>
Mark invited
</button>
</article>
</section>
</main>
</body>
async function markInvited(id) {
return Frontbacked.updatePost({
id,
type: "leads",
post: { status: "invited" },
merge: true
});
}
Frontbacked.functions.define({ markInvited });
Frontbacked.init({ endpoint: "/api" });
Read next: FQL Reference for page access, Auth and Posts API, and FRL Permissions.
Store Card
A product card can stay mostly HTML. FQL reads the post's money field and hands checkout to a guarded FRL payment endpoint.
{
"products": {
"$list": "products",
"$where": { "status": "published" },
"$order": { "createdOn": "desc" },
"$limit": 12
}
}
<article f-list="$state.products">
<div f-key="$.id" class="product">
<img f="true" f-attr-src="$.image.url" f-attr-alt="$.title">
<h2 f="true" f-text="$.title">Product title</h2>
<strong f="true" f-text="$.price.formatMoney()">USD 49.00</strong>
<button type="button" f="true" f-attr-data-id="$.id" onclick="buyProduct(this)">
Buy now
</button>
</div>
</article>
async function buyProduct(button) {
const id = button.dataset.id;
const products = Frontbacked.state.get("products.items") || Frontbacked.state.get("products") || [];
const product = Array.isArray(products) ? products.find((item) => item.id === id) : null;
const response = await Frontbacked.endpoints.call("create_product_payment", {
body: { productId: id }
});
if (!response?.ok) return response;
return Frontbacked.billing.showPayment({
paymentId: response.payment.id,
requireAuth: true
});
}
Frontbacked.functions.define({ buyProduct });
Read next: FQL Data Fetching, Image Resizing, and Billing Helpers in the FQL reference.
Starter Choices
| You want to build | Start with |
|---|---|
| Waitlist, contact form, support request, beta signup | createPost or a small FRL endpoint with dedupe |
| Blog, directory, jobs board, listings page | $list, $where, $order, f-list, and route params |
| Client portal or account dashboard | f-access, $auth, Frontbacked.updateUserData(), and FRL profile |
| Store, paid course, membership | type: "money", a server payment endpoint, $transactions, and billing helpers |
| Media gallery, course library, product catalog | file fields, image resizing, adaptive video, and upload resume |
| Multi-look theme marketplace item | skins/manifest.json, Frontbacked.setSkinMode(), and admin skin helpers |
Building With AI
Open /llms.txt and give it to your AI coding tool before asking it to generate a Frontbacked theme. It contains the public FQL/FRL syntax, helper names, naming rules, and patterns that keep generated code aligned with Frontbacked.