Skip to content

App Builder

Build custom web applications that run on the Bifrost runtime — standalone React (v2) and the legacy inline model (v1).

App Builder lets you ship custom web applications backed by Bifrost workflows, tables, and auth. An app is a React + TypeScript project that mounts at /apps/{slug} and talks to the platform through the bifrost SDK.

There are two app runtimes. New apps use v2 (standalone_v2).

v2 — standalone_v2 (current default)v1 — inline_v1 (legacy)
What it isA real, self-contained React projectAn inline bundle rendered by the platform
RenderingOwns its own createRoot + <BrowserRouter>Platform renders your pages inline
SDK accessimport … from "bifrost" (a real npm package)Everything injected via globalThis.__bifrost_platform
Where deps come fromNormal package.json (Vite, React, shadcn, lucide)A single "bifrost" import + up to 20 esm.sh packages
How it shipsScaffolded and deployed inside a SolutionCreated directly, files edited in place

Applications List

A standalone_v2 app is an ordinary React project. It owns its own root and router, and imports the Bifrost SDK as a package served from /api/sdk/download. This is the model new apps default to, and the model Solutions ship.

bifrost solution scaffold-app <slug> → develop locally → bifrost solution deploy

Scaffold an app inside a Solution workspace, build it like any Vite + React app (bifrost solution start runs the dev server and your workflows behind one origin), then deploy it as part of the Solution. The platform serves the built bundle at /apps/{slug}.

my-solution/
apps/my-app/ # what `bifrost solution scaffold-app` writes
package.json # vite + react + your deps
vite.config.ts # Tailwind v4
index.html # loads src/main.tsx
src/
main.tsx # createRoot + <BifrostProvider> — keep as scaffolded
App.tsx # <BrowserRouter> + <Routes>
components/ # your components (incl. shadcn ui/*)
pages/ # your route pages
functions/hello.py # @workflow functions live at the solution root
bifrost.solution.yaml

In a v2 app, only the SDK hooks and providers come from "bifrost". Everything else has its real home:

import { BifrostProvider, BifrostHeader, useWorkflowQuery, useWorkflowMutation, useTable, tables } from "bifrost";
import { Button } from "@/components/ui/button"; // shadcn — NOT from "bifrost"
import { useState } from "react"; // React — NOT from "bifrost"
import { Link, useNavigate } from "react-router-dom"; // router — NOT from "bifrost"
import { Phone } from "lucide-react"; // icons

Run workflows by portable path::function ref (or UUID — bare names aren’t unique):

import { useWorkflowQuery, useWorkflowMutation } from "bifrost";
const { data, isLoading, refetch } = useWorkflowQuery("functions/list_clients.py::main");
const { execute, isLoading: saving } = useWorkflowMutation("functions/save.py::main");
await execute({ customer_id: "123" });

The inline_v1 model predates the standalone runtime. The platform renders your pages inline and injects React, the shadcn component set, react-router, and the workflow hooks through globalThis.__bifrost_platform, so a v1 app imports everything from a single "bifrost" specifier.

A v1 app is a set of files edited in place:

app.yaml # metadata (name, description, esm.sh dependencies)
_layout.tsx # root layout — must use <Outlet />, not {children}
pages/
index.tsx # /
clients/[id].tsx # /clients/:id
components/

Everything imports from "bifrost":

import { Button, Card, useState, useWorkflowQuery, Outlet, cn, toast } from "bifrost";

Two rules that bite v1 authors: use workflow UUIDs (not names), and layouts must use <Outlet /> (not {children}).

Control who can open an app with access_level:

  • everyone — any signed-in user, including external/portal users
  • authenticated — signed-in users except external users
  • role_based — specific roles only (set role_ids)

Inside an app, gate UI on the current user’s roles with useUser() / hasRole() (and RequireRole in v1). See the SDK reference for the exact shapes.