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