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 funnels

components/, 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 .tsx file in app/ is a page — lowercase-kebab filename, default-exported component. layout.tsx at the top level is the one reserved name; stylesheets may sit beside pages. Anything else belongs in components/. A stray file in app/ is a warning in dev and blocks a push.
  • Every paywall starts at app/index.tsx and must have a config.ts.
  • The directory name is the identifier. It's the URL in dev and the dashboard binding on push — lowercase-kebab. The name in config.ts is 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" in package.json. That would shadow the framework import. superwall create names 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?

On this page