create
Scaffold a wired Base IdP starter for your stack — the right minimal env, the right SDK glue, the right redirect setup.
base-idp create is the command that turns "I want to integrate Base IdP into
my app" into "here is a folder with the code already wired." It picks the
right SDK for your stack, generates the env block your role actually needs,
and writes a BASE_IDP_SETUP.md with the remaining manual steps.
There are nine supported stacks. Each one is a minimal, idiomatic starter that you can drop into your project and adapt.
The basics
Run it interactively for the full prompt experience:
npx base-idp createOr skip the prompts by passing everything as flags:
npx base-idp create \
--stack flutter \
--client-id sq_live_yourapp \
--redirect-uri "myapp://auth/callback"For a confidential server you add the secret. Type it into the command — do not save it in a script that lives in version control.
npx base-idp create \
--stack nextjs-fullstack \
--client-id sq_live_web \
--client-secret sqk_your_secretListing stacks
npx base-idp create --listAvailable stacks:
flutter Flutter (mobile, public)
react-native React Native / Expo (mobile, public)
swiftui SwiftUI (iOS, public)
nextjs-fullstack Next.js fullstack (server login, confidential)
nextjs Next.js frontend (talks to a separate API, public)
nestjs NestJS (confidential server)
express Express / Node API (verify-only)
go Go API (verify-only)
rust Rust API (verify-only)What each stack generates
The output is small on purpose. The CLI is not trying to be your project template. It is trying to remove the moment where you sit and wonder which SDK function to call and which env variable to read.
Flutter
base-idp-flutter/
├── BASE_IDP_SETUP.md
├── lib/
│ └── auth/
│ └── base_idp_auth.dart
└── run.shbase_idp_auth.dart reads the client id from String.fromEnvironment, so the
value can be passed at build time:
flutter run --dart-define=BASE_IDP_CLIENT_ID=sq_live_yourappThis is exactly how a public client should pass a client id — it is in the binary at build time but never in the source repo.
React Native / Expo
base-idp-react-native/
├── BASE_IDP_SETUP.md
├── .env
└── auth/
└── baseIdp.tsBASE_IDP_CLIENT_ID=sq_live_yourappThe .env here is the Expo convention — EXPO_PUBLIC_* variables are exposed
to the bundle, which is fine for a client id.
SwiftUI
base-idp-swiftui/
├── BASE_IDP_SETUP.md
└── BaseIdPAuth.swiftThe Swift file uses ASWebAuthenticationSession and CryptoKit for PKCE.
The starter expects you to add a URL Type in your target's Info to register
the callback scheme.
Next.js (fullstack)
base-idp-nextjs-fullstack/
├── BASE_IDP_SETUP.md
├── .env.local
└── app/
└── api/
└── auth/
├── login/route.ts
└── callback/route.tsTwo route handlers: /api/auth/login builds the authorize URL and redirects,
/api/auth/callback exchanges the code and hands you a verified principal.
Next.js (frontend only)
base-idp-nextjs/
├── BASE_IDP_SETUP.md
├── .env.local
└── lib/
└── auth.tsBrowser-only setup — for a Next.js app that talks to a separate backend
API. The env variable is named NEXT_PUBLIC_BASE_IDP_CLIENT_ID because the
client id is exposed to the bundle.
NestJS
base-idp-nestjs/
├── BASE_IDP_SETUP.md
├── .env
└── src/
└── auth/
└── base-idp.guard.tsA BaseIdpGuard that you apply with @UseGuards(BaseIdpGuard). The verified
principal lands on request.principal.
Express, Go, Rust (verify-only)
base-idp-express/
├── BASE_IDP_SETUP.md
├── .env # placeholder, nothing required
└── middleware/
└── baseIdp.tsThe .env is a commented placeholder. Verify-only services need nothing in
env to function, only optionally the client id to pin the audience.
Every option
| Flag | Description |
|---|---|
--stack <id> | The stack to scaffold. See the list above. |
--list | Print every stack and exit. |
--client-id <id> | Your OAuth client id. Public. Safe to ship in frontend bundles. |
--client-secret <secret> | Server secret. Only used for confidential / fullstack stacks. |
--dir <path> | Output directory. Default is base-idp-<stack>. |
--redirect-uri <uri> | Redirect URI, e.g. myapp://auth/callback. |
--scheme <scheme> | Mobile URL scheme. Derived from --redirect-uri if omitted. |
--force | Write into a non-empty directory. |
Role rules the CLI enforces
The CLI does not let you make a mistake about which env goes where. The rules that scaffolded code follows:
- Public stacks never get a
BASE_IDP_CLIENT_SECRETin their generated files, even if you pass--client-secreton the command line. The flag is ignored for these stacks. - Verify-only stacks generate a
.envthat has the Base IdP env commented out. Nothing is required to run. - Confidential / fullstack stacks get
BASE_IDP_CLIENT_IDplusBASE_IDP_CLIENT_SECRETin a server-side file (.env, not.env.localunless the framework expects it).
This means you cannot accidentally ship a secret to a mobile bundle through the scaffolder. The role rules are not a convention; they are mechanically enforced.
After the CLI is done
The last thing create prints is a one-line "next" pointing at the
BASE_IDP_SETUP.md it generated. Open it. The setup steps differ by stack
but always come down to:
- Install the SDK.
- Set the env variable to your real client id.
- Register the redirect URI in the platform that needs it.
The first time you do this for a new framework, it takes about ten minutes. The second time it takes about two.