Skip to content
forma.
v3.0
☰
Browse documentation
Start here
Build interfaces
Native rendering
Tooling
Security and operations
Architecture
Application guides
API reference

Docs/Start here

FORMA 3.0

Create your first app

Scaffold a TypeScript frontend and Rust server with the coordinated Forma packages.

View source ↗Markdown ↓Source 753be2a
On this page

npm version CI License: MIT

Scaffold a new Forma application — Rust server + TypeScript frontend, fully wired.

Where this sits. One repository, seven coordinated packages:

text
TS/JSX → @getforma/compiler → FMIR 3 → forma-ir → forma-server → HTML
         @getforma/build joins the assets       @getforma/core adopts islands
         @getforma/create-app supplies the templates; forma-ir-derive types props

You are here: the front door. New to the stack? Read the stack architecture — it explains the pipeline. All seven packages live in getforma-dev/forma and publish as one set. Existing 0.6.x projects can follow the 3.0 template migration.

Usage#

These registry commands use the published, coordinated 3.0.0 set. Contributor builds of unpublished changes use the packed candidate workflow below. Release records report qualification of the published set.

bash
npx @getforma/create-app@3.0.0 my-app
cd my-app

Then:

bash
# Build the frontend
cd admin && npm ci && npm run build && cd ..

# Start the Rust server
cargo run

Open http://localhost:3000. Both templates bind to 127.0.0.1 by default; the dashboard can opt in to a wider bind with FORMA_HOST, and warns when you do, because its /api/* routes have no authentication.

Options#

bash
npx @getforma/create-app@3.0.0 my-app --template dashboard
npx @getforma/create-app@3.0.0 my-app --template minimal
npx @getforma/create-app@3.0.0 my-app --dry-run
npx @getforma/create-app@3.0.0 --help
npx @getforma/create-app@3.0.0 --version

Project names must start with a lowercase letter and use only lowercase letters, digits, hyphens and underscores, at most 64 characters. The name becomes the crate's name, so the CLI also refuses what cargo refuses for a new package (a Rust keyword such as fn, test, a build directory name such as build) and a Windows device name (con, nul); its message names the rule. It checks the name before writing anything, and refuses to write into a path that already exists.

Templates#

TemplateRenderingDescription
dashboardExplicit Browser renderingDevOps admin panel — 4 pages, 8 API routes, SVG chart, Tailwind CSS, Gruvbox theme, 16 Playwright tests
minimalBrowser, or native SSR with npm run build:ssrRust server + a Counter page and a static About page

The dashboard explicitly uses client rendering. The FMIR compiler walks source rather than executing it and accepts only the documented native subset. It refuses unsupported native source with a coded diagnostic; it does not emit an empty placeholder or silently change a native route to client rendering. The minimal template demonstrates native rendering with declared island state; see the admitted subset and the template rendering guide when choosing a mode for a new template.

What You Get#

  • Rust server (src/main.rs) — Axum + forma-server: page rendering with a bound PageHandle<Props> (or render_browser_shell for an explicit Browser route), each page a PageResponse carrying its per-request CSP nonce and Cache-Control: no-store, asset serving from rust-embed, no service worker, and a tracing subscriber that logs every render error by code and route
  • TypeScript frontend (admin/src/) — FormaJS with JSX, signals, components
  • Build pipeline (admin/build.ts) — @getforma/build: bundling, content hashing, Brotli/gzip, manifest, in strict mode, so a construct the server cannot render fails the build with its code and line
  • A .gitignore that covers build output, secrets and (for the dashboard) Playwright artifacts — and deliberately does not ignore Cargo.lock
  • Lockfiles (admin/package-lock.json and Cargo.lock) holding the exact versions this repo's scaffold gate built and served, so a new project starts from a tested set rather than whatever resolves that day

Development#

bash
npm test        # structural tests — builds the CLI explicitly first
npx tsc --noEmit

The candidate gate needs a network and a Rust toolchain. From the repository root, build and commit the source, then use a fresh artifact/work directory:

bash
npm run build
node scripts/candidate-scaffold.mjs pack /absolute/path/to/candidate-artifacts
# Use the record SHA-256 printed by pack and this checkout's full Git commit.
node scripts/candidate-scaffold.mjs run /absolute/path/to/candidate-artifacts RECORD_SHA256 COMMIT /absolute/path/to/candidate-work /absolute/path/to/report.json

CI always runs the full candidate suite on Linux and Windows. The separate registry lane runs the actual published CLI when all seven coordinated versions exist. Missing or skipped candidate jobs fail the release gate. The source lock seeds are never passed directly to an unpublished registry installation.

That scaffolds each template into a temp directory, installs from the template's lockfile with npm ci, builds the frontend, runs cargo check --locked, then starts each compiled server and reads its responses: the minimal template must serve real server-rendered markup after build:ssr, the dashboard must serve each page as its declared browser shell whose CSP nonce matches the script tag, and every page of both must answer Cache-Control: no-store with a fresh nonce on each fetch, register no service worker, and leave /sw.js to forma-server's retiring worker. It also runs npm audit signatures over each template's install, fails on any use of deprecated warning from the template's Rust, checks that a construct the server cannot render fails the minimal template's strict build, and that the minimal server refuses to start on a truncated module and answers a failed render with its own 500, carrying neither the error nor an empty shell. A distinct handler value of 7 must appear in raw SSR HTML; missing/wrong props and unbound routes must stop before a listener opens. The packed native-island check adopts the server value without initial writes and checks that the static route requests no code in all three engines. The dashboard tooltip check keeps API values as text under enforced Trusted Types. The gate requires all 19 heavy controls plus the structural suite, with no skips.

DocumentWhat it is
CONTRIBUTING.mdSetup, the gates, and what a reviewer will ask
docs/TEMPLATES.mdTemplate anatomy, what the scaffolder rewrites, how to add one
docs/RENDERING-PHASES.mdExplicit Browser and native SSR modes, and the admitted compiler subset
Stack architectureThe whole pipeline and its reasoning. Start here if you are new.
Testing policyThe standard every test in this stack is held to

Part of the Forma Stack#

Frontend (TypeScript)#

PackageDescription
@getforma/coreReactive DOM library — signals, h(), islands, SSR hydration
@getforma/compilerFMIR generation, coded diagnostics and esbuild SSR integration
@getforma/buildProduction pipeline — bundling, hashing, compression, manifest

Backend (Rust)#

PackageDescription
forma-irFMIR binary format, structural validation and native Rust walker
forma-serverAxum middleware: SSR, asset serving, CSP headers

Full Framework#

PackageDescription
@getforma/create-appThis package — scaffolds the full stack

License#

MIT

Source templates and published artifacts#

The checked-in templates use the declared-props and finalized Pages APIs. Minimal declares a Counter island over its static home markup and a separate empty-props About page. The native handler starts at 7 while explicit Browser mode starts at 0. The published 3.0.0 set includes integrated island adoption and static-route checks; later candidates require their own packed qualification.

Source template locks are explicitly identified seeds at the coordinated package version. They retain every external registry checksum and omit only the known Forma candidate checksums, which cannot exist before the archives are packed. Release packing seals those entries from actual archive bytes, validates the shipped locks, and restores all ten source files. The source-only template-lock-seeds.json marker is never shipped. Use the packed candidate scaffold workflow for local end-to-end development; copying source templates by hand does not produce an installable unpublished candidate set.

The scaffolder writes a separate application identity into admin/build.ts. Valid worker names stay unchanged; accepted project names with underscores or 64 characters use a readable prefix plus a deterministic SHA-256 suffix. This preserves accepted CLI names without merging distinct cache namespaces. The server's enabled worker must use this app value with sw::configure_for_manifest(worker, &manifest) before binding; templates leave workers off. See compatibility and migration.

Verified by: tests/scaffold.test.ts, scripts/release-template-lock.test.mjs at the repository root, and the exact-artifact P17 template run.

Find your next answer.

Search Forma 3.0 documentation, guides and examples.