Getting Started

CLI Scaffolder

Generate a production-ready Nexa-connected school app with one command.

create-nexaed-app scaffolds a complete Next.js school app with routing, auth, a results dashboard, and payment support already wired up. It's the fastest way to go from zero to a working integration.

Info

Prefer a visual interface? Try the Stack Builder → — click to select your auth provider, features, email tier, and package manager, then copy the generated command.


Commands

npx create-nexaed-app                      # scaffold a new school app (default)
npx create-nexaed-app init my-school       # explicit init
npx create-nexaed-app doctor               # validate an existing app
npx create-nexaed-app upgrade              # upgrade @nexa-ed/* deps to @latest
npx create-nexaed-app skill                # install the Nexa-Ed Claude Code skill
npx create-nexaed-app telemetry on|off     # manage telemetry preference
npx create-nexaed-app help                 # print usage

Info

No npm/npx on your machine? Every command in this page also works with pnpm dlx create-nexaed-app <args> in place of npx create-nexaed-app <args>.

Warning

pnpm 11 ships a 24-hour supply-chain gate (minimumReleaseAge, default 1440 minutes) that hides any release published in the last day from @latest resolution. So a freshly published scaffolder is invisible to an unpinned pnpm create nexaed-app for a day — this is pnpm's default, not a cache and not your config. From 0.3.0-beta.8 the CLI works around it for you: it checks the registry before generating anything and silently re-runs your command against the newest release (exact versions bypass the gate). Only a copy older than that release is stuck, and it stops being stuck once the newest guard passes 24 hours. To force a specific version now: pnpm dlx [email protected] my-school, or --pin 1.2.3 inside a project that already has a newer copy. NEXAED_SKIP_UPDATE_CHECK=1 opts out of the hand-off entirely.


Run the scaffolder

# npx (no install required)
npx create-nexaed-app my-school
 
# pnpm
pnpm create nexaed-app my-school

That's the whole command. Everything it doesn't ask you about has a default, and every question you skip with a flag is one it would have asked interactively — so the shortest command and the fully-flagged one produce the same app.


Interactive prompts

The CLI asks only what your command didn't already say:

PromptOptions
School nameUsed as directory name and package.json#name
Auth providerWorkOS · Clerk · NextAuth · None
FeaturesFile processing, Payments, Convex integration, Student email provisioning
UI libraryNone (plain Tailwind) · shadcn/ui
Initialize git?Yes (default) · No
Email tier + domainOnly when student email provisioning is selected
Nexa API keyOptional — can be added to .env.local later

Non-interactive / CI mode

All prompts can be bypassed with flags. Use this in CI pipelines, Dockerfiles, or shell scripts:

npx create-nexaed-app my-school \
  --auth clerk \
  --features fileProcessing,payments \
  --ui shadcn \
  --email-tier tier-1-nexa \
  --api-key nxa_live_... \
  --pm pnpm
 
# or, without npm/npx installed:
pnpm dlx create-nexaed-app my-school \
  --auth clerk \
  --features fileProcessing,payments \
  --ui shadcn \
  --email-tier tier-1-nexa \
  --api-key nxa_live_... \
  --pm pnpm
FlagDescription
--authworkos · clerk · nextauth · none
--featuresComma-separated: fileProcessing, payments, convex, emailProvisioning
--uinone (default) · shadcn
--email-tiertier-1-nexa · tier-2-stalwart · tier-3-google
--email-domainYour school domain (required for tier 2/3)
--api-keyPre-fill the Nexa API key in .env.local
--pmpnpm · npm · yarn · bun
--no-installSkip dependency installation
--gitInitialize a git repository and create an initial commit
--no-gitSkip git initialization
--pin <version>Keep this exact scaffolder version instead of handing off to the newest release

When stdin is not a TTY, the non-interactive fast path activates automatically — no flags needed in most CI environments.

Environment variables

Every flag has an env-var equivalent, which is handy in Dockerfiles and CI YAML where a long command line is awkward. Flags win over env vars, and anything left unset falls back to the interactive prompt (or its default in CI):

Env varFlag
NEXAED_PROJECT_NAMEproject name argument
NEXAED_AUTH--auth
NEXAED_UI--ui
NEXAED_FEATURES--features
NEXAED_EMAIL_TIER--email-tier
NEXAED_EMAIL_DOMAIN--email-domain
NEXAED_API_KEY--api-key
NEXAED_PM--pm
NEXAED_GIT--git / --no-git (1 / 0)
NEXAED_NO_INSTALL--no-install
NEXAED_SKIP_UPDATE_CHECKopt out of the automatic hand-off to the newest release
NEXAED_AUTH=clerk NEXAED_FEATURES=convex,payments npx create-nexaed-app my-school

Mistakes don't abort the run

A typo'd value (--auth wokos, --features paymetns) prints what it didn't recognise, keeps the values it did, and continues with defaults — it never deletes or overwrites a non-empty project folder, and if the name you gave is taken it asks for a new one.


doctor — validate an existing app

Run doctor inside an existing Nexa-powered school app to check for common setup issues:

cd my-school
npx create-nexaed-app doctor
# or: pnpm dlx create-nexaed-app doctor

Runs 5 checks:

CheckWhat it verifies
Env varsNEXA_API_KEY and NEXA_WEBHOOK_SECRET present in .env.local
PackagesRequired @nexa-ed/* packages listed in package.json
RouteCatch-all app/api/nexa/[...nexaed]/route.ts exists
SDK versionYour installed version vs latest on npm
API keyLive request to the Nexa platform confirms the key is valid

upgrade — update SDK packages

Updates all @nexa-ed/* dependencies in the current project to @latest:

cd my-school
npx create-nexaed-app upgrade
# or: pnpm dlx create-nexaed-app upgrade

The package manager is detected automatically from your lockfile.


skill — teach Claude Code the SDK

Every app scaffolded with init already gets this installed at .claude/skills/nexa-ed-sdk. If you're adding Nexa-Ed to an existing app instead of scaffolding a new one, install it explicitly:

cd my-existing-app
npx create-nexaed-app skill
# or: pnpm dlx create-nexaed-app skill

This drops a Claude Code skill into .claude/skills/nexa-ed-sdk/ — a SKILL.md plus reference files covering createNexa/createRouteHandler setup, every NexaClient module's real method signatures, the verified @nexa-ed/react component/hook list, and webhook/error handling. Claude Code picks it up automatically the next time you ask it to work with the SDK in that project — no MCP server or extra config required. It's kept in sync with the SDK source (not just the docs), and calls out a few places where these docs pages currently lag the source.


What gets generated

my-school/
├── .claude/
│   └── skills/nexa-ed-sdk/  ← Claude Code skill (SKILL.md + reference/)
├── app/
│   ├── api/nexa/[...nexaed]/
│   │   └── route.ts        ← single catch-all route handler
│   ├── callback/route.ts   ← WorkOS sign-in callback  [workos only]
│   ├── dashboard/
│   │   └── page.tsx        ← main dashboard
│   ├── layout.tsx
│   ├── page.tsx            ← landing page (Nexa brand colours)
│   ├── providers.tsx
│   └── globals.css
├── convex/                  ← [convex feature only]
│   ├── schema.ts           ← Nexa schema fragments (payments / files / emails)
│   └── nexa.ts             ← @nexa-ed/convex function mounts — this is what
│                             makes `api.nexa.*` appear once codegen runs
├── lib/
│   ├── nexa.ts             ← SDK instance (pre-wired to your auth provider)
│   └── utils.ts            ← cn() helper  [shadcn only]
├── middleware.ts            ← auth route protection
├── components.json         ← shadcn/ui config  [shadcn only]
├── tailwind.config.ts
├── pnpm-workspace.yaml     ← build-script approvals + always-fresh @nexa-ed installs
├── .env.example            ← all required vars with inline comments
├── .env.local              ← API key pre-filled if you provided it
├── package.json
└── tsconfig.json

The generated lib/nexa.ts varies by auth provider:

Clerk variant
import { createNexa } from "@nexa-ed/next";
import { auth } from "@clerk/nextjs/server";
 
export const nexa = createNexa({
  apiKey: process.env.NEXA_API_KEY!,
  webhookSecret: process.env.NEXA_WEBHOOK_SECRET!,
  getUser: async () => {
    const { userId } = await auth();
    if (!userId) throw new Error("Unauthorized");
    return { userId };
  },
});
NextAuth variant
import { createNexa } from "@nexa-ed/next";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
 
export const nexa = createNexa({
  apiKey: process.env.NEXA_API_KEY!,
  webhookSecret: process.env.NEXA_WEBHOOK_SECRET!,
  getUser: async () => {
    const session = await getServerSession(authOptions);
    if (!session?.user?.id) throw new Error("Unauthorized");
    return { userId: session.user.id };
  },
});

After scaffolding

Follow these steps to get the app running:

1. Install dependencies

cd my-school && pnpm install

2. Fill in environment variables

Open .env.local and set:

NEXA_API_KEY=nxa_live_...
NEXA_WEBHOOK_SECRET=nxa_live_...   # same value as NEXA_API_KEY

Both come from Nexa dashboard → API key — Nexa signs webhooks with your API key, so NEXA_WEBHOOK_SECRET is just a second copy of it. Update both whenever you rotate the key.

3. Connect Convex (only if you chose the convex feature)

npx convex dev

This logs you in, creates or attaches a deployment, and generates convex/_generated/api from the mounts in convex/nexa.ts. Until it has run once, api.nexa.* does not exist and pnpm check-types will fail — the CLI cannot do this step for you because it needs your Convex account.

4. Register webhook URLs

In the Nexa dashboard, add:

EventURL
file.completedhttps://your-domain.com/api/nexa/webhook
payment.completedhttps://your-domain.com/api/nexa/payments/forward

If you provision student emails, email.* events need a route you write yourself (there's no built-in path for them) — see Webhooks → Email events.

Use ngrok to expose localhost:3000 while developing.

5. Start the dev server

pnpm dev

Open http://localhost:3000/dashboard — your results viewer is live.


What's next

On this page