Frontier — Frontend & Local Setup

Local dev guide for running Frontier (backend + frontend) on macOS.

Before you start

  • Prerequisite: Docker Desktop installed and running (the deps run in Docker).
  • Paths: frontier/ (repo root) and frontier/web (frontend workspace root) are fixed routes — adjust to your clone location.
  • Endpoints: remote-dev URLs use <remote-frontier-host> as a placeholder — replace it with your deployment's Frontier host.

Contents

  1. Run the backend
  2. Run the frontend (admin app)
  3. Run the client SDK (client-demo app)
  4. Gotchas
  5. Env file reference
  6. Ports & config reference

1. Run the backend

Bring up the dependency containers, then run the backend against them (config.yaml already points at the localhost ports). compose-up-dev and server start each stay running — use separate terminals.

# Terminal 1 — deps (leave running)
cd frontier
open -a Docker                             # Docker Desktop must be running
make compose-up-dev                        # docker-compose up --build pg pg2 spicedb-migration spicedb

# Terminal 2 — backend (leave running)
cd frontier
go run . server migrate -c config.yaml     # create/update the app DB schema
go run . server start -c config.yaml       # backend API on :8002

First run only: compose-up-dev pulls postgres:15 + spicedb:v1.34.0 and runs the SpiceDB migration. The pg container starts empty, so the migrate step is what creates the Frontier app schema — compose-up-dev migrates SpiceDB but not the app DB.

Health checks (another terminal):

  • docker psfrontier-pg-1, frontier-pg2-1, frontier-spicedb-1 all (healthy).
  • lsof -nP -iTCP:8002 -sTCP:LISTEN — backend listening.
  • docker exec frontier-pg-1 psql -U frontier -d frontier -c '\dt' | head — app tables exist.

2. Run the frontend (admin app)

The admin app talks to either backend. Switching is only an env change in frontier/web/apps/admin/.env, then a dev-server restart.

Run steps (same for both cases) — pnpm 10.19.0, workspace root frontier/web:

cd frontier/web
pnpm install
pnpm build --filter=@raystack/frontier      # build the SDK package
pnpm dev --filter=admin                      # admin dev server → :5173

Confirmed working: pnpm build --filter=@raystack/frontier builds the full SDK (@raystack/frontier@0.32.1 — root dist + client/dist + admin/dist + hooks/dist), and pnpm dev --filter=admin starts Vite 4.5.14 on http://localhost:5173. (Admin runs Vite 4; client-demo runs Vite 7.)

Set the endpoint in frontier/web/apps/admin/.env (see §5 for the full file):

  • Case A — no local backend (remote dev): shared backend, real data, nothing else to run.

    FRONTIER_API_URL=https://<remote-frontier-host>/frontier
    FRONTIER_CONNECTRPC_URL=https://<remote-frontier-host>/frontier-connect
  • Case B — with local backend: points at :8002; requires the backend running first (§1); isolated local data.

    FRONTIER_API_URL=http://localhost:8002/frontier
    FRONTIER_CONNECTRPC_URL=http://localhost:8002/

Important

  • Only these two vars change between cases.
  • .env is read only at startup — after editing, fully stop and restart Vite (hot-reload won't pick it up).
  • The SDK does not need rebuilding for an endpoint change — the endpoint lives in vite.config.ts (proxy) + src/connect/transport.tsx, not the SDK.

3. Run the client SDK (client-demo app)

The client SDK (@raystack/frontier/client) is separate from the admin app. It is exercised by the client-demo app at frontier/web/apps/client-demo, which renders MembersView (invite-member dialog, roles, teams, etc.). The admin app has its own views and does not use these.

Run it

Pick a mode based on whether you're editing the SDK.

Mode A — run only (not editing the SDK) — build once, then start:

cd frontier/web
pnpm install
pnpm build --filter=@raystack/frontier      # build the SDK client/dist once
pnpm dev --filter=client-demo               # demo on :3000

Then open http://localhost:3000Settings → Members. (Confirmed working.)

Mode B — editing SDK source (live rebuild) — two processes:

# Terminal 1 — watch-rebuild the SDK into client/dist
cd frontier/web
pnpm dev --filter=@raystack/frontier         # tsup client/index.ts --watch
# Terminal 2 — run the demo
cd frontier/web
pnpm dev --filter=client-demo                # demo on :3000

Now editing frontier/web/sdk/client/... rebuilds dist and Vite HMR reloads.

Backend (optional)

Switched by env only in frontier/web/apps/client-demo/.env. It ships pointing at remote dev, so it runs with nothing else started; swap the comments to use a local backend on :8002. Note the different var names than the admin app (FRONTIER_ENDPOINT / FRONTIER_CONNECT_ENDPOINT).

# Remote dev (DEFAULT — no local backend needed)
FRONTIER_ENDPOINT=https://<remote-frontier-host>/frontier
FRONTIER_CONNECT_ENDPOINT=https://<remote-frontier-host>/frontier-connect

# Local backend (swap the comments to use :8002)
# FRONTIER_ENDPOINT=http://localhost:8002/frontier
# FRONTIER_CONNECT_ENDPOINT=http://localhost:8002/

How it works

  • Why SDK source edits don't show up: the demo imports the SDK's built output (frontier/web/sdk/client/dist) via a pnpm workspace symlink — not the source .tsx files, and there's no source alias in its vite.config.ts. So editing frontier/web/sdk/client/... does nothing until client/dist is rebuilt (hence Mode B).
  • Endpoint proxy (vite.config.ts) — the app calls /api (see src/config/frontier.ts) and /frontier-connect:
    • /apiFRONTIER_ENDPOINT (strips /api)
    • /frontier-connectFRONTIER_CONNECT_ENDPOINT (strips prefix)
    • Proxied requests are same-origin (:3000 → backend), so CORS isn't hit — cors.allowed_origins (:5173) needn't include :3000.
  • .env is read only at startup (same as admin) — restart after edits.
  • The component mounts only when its page/dialog is open, so a console.log in it won't fire until you navigate there.

4. Gotchas

SymptomCause / fix
:5173 "connection refused"Frontend dev server isn't running (separate from the backend).
Frontend still hitting remote after editing .envVite was started before the edit — Ctrl+C and restart.
401 unauthenticated after switching to localBrowser still holds a remote session cookie — log in fresh against local.
Local DB is emptyFreshly-migrated local DB has no orgs/data — seed or create locally.
zsh: unknown file attribute: i when pasting a commandInteractive zsh parses (...) in a trailing # comment as a glob qualifier. Paste without the inline comment, or run setopt interactive_comments first.

5. Env file reference

File: frontier/web/apps/admin/.env — both pairs live in the file; comment out one, uncomment the other to switch (then restart the dev server). Shown pointing at the local backend:

# Remote dev
# FRONTIER_API_URL=https://<remote-frontier-host>/frontier
# FRONTIER_CONNECTRPC_URL=https://<remote-frontier-host>/frontier-connect

# Local backend
FRONTIER_API_URL=http://localhost:8002/frontier
FRONTIER_CONNECTRPC_URL=http://localhost:8002/
VariableMeaning
FRONTIER_API_URLBase REST API URL used by the app.
FRONTIER_CONNECTRPC_URLConnect-RPC target; in dev the Vite proxy forwards /frontier-connect → this URL (frontier/web/apps/admin/vite.config.ts).

6. Ports & config reference

Ports at a glance:

PortService
8002Backend API — Connect / REST + gRPC (app.connect.port)
8100Backend admin UI (ui.port)
9000Backend metrics (app.metrics_port)
5432Postgres — frontier (pg)
5431Postgres — SpiceDB's backing DB (pg2)
50051SpiceDB — gRPC
7443SpiceDB — HTTP
5173Admin app dev server (Vite)
3000Client-demo app dev server (Vite)

config.yaml (repo root) — key values:

KeyValue
ui.port8100
app.host127.0.0.1
app.connect.port8002
app.metrics_port9000
db.driverpostgres
db.urlpostgres://frontier:@localhost:5432/frontier?sslmode=disable
spicedb.hostlocalhost
spicedb.port50051
spicedb.pre_shared_keyfrontier
cors.allowed_originshttp://localhost:5173