Project Structure
How a superwall/ directory is laid out, the two files the CLI manages, and the rules that keep a project portable.
Everything Superwall-related in your app lives in one superwall/ directory — or the repo root, if you keep paywalls in a dedicated repo. It's a self-contained npm project: clone it, install, run superwall dev, and it works. Your host app needs no npm setup of its own.
Layout
superwall/
├── package.json depends on `superwall`, react, react-dom
├── tsconfig.json
├── superwall.d.ts generated — commit, never edit
├── superwall.lock dashboard bindings — commit
├── .gitignore
├── components/ components shared across paywalls
├── messages/ shared string catalogs (en.ts, de.ts, …)
├── assets/ shared images, video, fonts
├── paywalls/<id>/ one directory per paywall
│ ├── config.ts required — definePaywall({ name, products })
│ ├── app/ pages — index.tsx (required), layout.tsx, more pages
│ ├── components/ this paywall's own components
│ ├── messages/ this paywall's own strings
│ └── assets/ this paywall's own assets
└── funnels/<id>/ same shape, for funnelscomponents/, messages/, and assets/ work at both levels: shared at the root, local inside a paywall. @/… imports resolve from the superwall/ root:
import { Button } from "@/components/Button";The rules
A few conventions keep every project buildable, portable, and understandable at a glance:
app/holds pages and nothing else. Every.tsxfile inapp/is a page — lowercase-kebab filename, default-exported component.layout.tsxat the top level is the one reserved name; stylesheets may sit beside pages. Anything else belongs incomponents/. A stray file inapp/is a warning in dev and blocks a push.- Every paywall starts at
app/index.tsxand must have aconfig.ts. - The directory name is the identifier. It's the URL in dev and the dashboard binding on push — lowercase-kebab. The
nameinconfig.tsis only the human-readable label shown in the dashboard. - No build tooling. No vite config, no
index.html, no entry point — the framework owns the build end to end. - Never name the package
"superwall"inpackage.json. That would shadow the framework import.superwall createnames it after your app.
Commands work from your app root or from inside superwall/ alike, and a globally installed superwall always defers to the project's own installed version — so everyone on the team builds with the version the project pins.
Two files the CLI manages — commit both
superwall.d.ts
Regenerated on every dev and push. It's what makes router.push("plans") autocomplete and reject typos, gives getProduct and purchase their typed product references, and makes asset imports typecheck. Never edit it; never delete it.
superwall.lock
Binds each paywall directory to its paywall on the dashboard, and records which Superwall app the project pushes to. Committing it is what makes every machine — and CI — push to the same paywalls. Nothing about the dashboard ever appears in config.ts; the lock file is the only place bindings live.
Renaming a paywall directory is safe: the next push notices and asks whether it's a rename (keeping the live paywall attached) or a brand-new paywall. In CI, declare it with --rename old=new. See Push, promote & publish.
Keep imports inside the project
Import from within superwall/ or from packages listed in its package.json. An import that reaches outside — say ../../src/theme — still builds on your machine, but the pushed source can no longer be rebuilt anywhere else, so the dashboard disables remote editing for that paywall and the push warns, naming each offender.
Copy shared code into superwall/components/ instead. Duplication here is deliberate: it's what keeps the project self-contained.
.env
superwall/.env (with your app root's .env as a fallback) holds project credentials — SUPERWALL_API_KEY for CI pushes. It's gitignored and never leaves your machine: source pushes exclude .env*, node_modules/, .superwall/, and anything your .gitignore lists.
Funnels
Multi-step flows — onboarding quizzes, web funnels — use exactly the same layout as paywalls and live under superwall/funnels/<id>/. A funnel is one surface whose steps are pages, not a chain of separate paywalls. See Pages & navigation.
How is this guide?