Theme Routing
backend/paths.json lets a theme serve clean, dynamic URLs while still keeping pages as plain HTML files.
Place it in the backend/ folder inside the theme root:
my-theme/
index.html
users/
info.html
files.html
file.html
backend/
index.rules
paths.json
The file is a JSON object. Each key is a public URL pattern, and each value is the HTML file that should handle that URL.
{
"/users/:username": "/users/info.html",
"/users/:username/files": "/users/files.html",
"/users/:username/files/:id": "/users/file.html"
}
With this config:
| Browser URL | Served file | $params |
|---|---|---|
/users/ada | /users/info.html | { "username": "ada" } |
/users/ada/files | /users/files.html | { "username": "ada" } |
/users/ada/files/report-1 | /users/file.html | { "username": "ada", "id": "report-1" } |
The browser URL stays clean. Frontbacked only rewrites the file used to serve the request.
Reading Params in FQL
When a route matches, Frontbacked injects the params into the page as window.$params. FQL reads them through $params.
<!-- users/info.html -->
<head>
<!-- {STATE}
state = {
"username": "$params.username",
"profile": "profiles{username:$params.username}",
"title": "$state.profile.displayName || $params.username || 'Profile'"
}
-->
</head>
<body>
<h1 f="true" f-text="$state.title">Profile</h1>
</body>
For /users/ada, $params.username is ada, and the page can fetch the matching profile without parsing the URL manually.
Blog Example
Use one detail page for every article slug:
{
"/blog/:slug": "/blog/detail.html"
}
<!-- blog/detail.html -->
<head>
<!-- {STATE}
state = {
"article": "articles{slug:$params.slug}",
"pageTitle": "$state.article.title || 'Article'"
}
-->
</head>
<body>
<article>
<h1 f="true" f-text="$state.pageTitle">Article</h1>
<p f="true" f-text="$state.article.summary || ''"></p>
</article>
</body>
Now /blog/market-outlook and /blog/risk-guide can both use /blog/detail.html.
Route Order
Frontbacked checks routes in the order they appear in paths.json. Put more specific routes before broader routes.
{
"/products/new": "/products/new.html",
"/products/:slug": "/products/detail.html"
}
This keeps /products/new from being treated as { "slug": "new" }.
What It Does Not Do
paths.json is for theme page routing. It does not create API routes, does not replace FRL permissions, and does not change how assets such as CSS, JS, images, or uploads should be referenced.
Use simple path segments and named params such as :slug, :username, or :id. Params match one URL segment at a time, so /blog/:slug matches /blog/market-outlook, not /blog/2026/market-outlook.
For query string values, use $query instead:
{
"plan": "$query.plan || 'starter'",
"slug": "$params.slug"
}
Local and Published Themes
The local frontbacked-server reads backend/paths.json from your theme folder. Published sites read the same file from the processed theme commit. That means the same route config works locally, in preview, and in production.