Documentation
Everything the CLI does, explained.
Using modules in your own project is public and needs no account. Publishing your own is a separate flow that does. Plus what goes in a manifest, how naming stays conflict-free, and how versions and updates work.
01 · Quickstart
Using modules
Installing modules into your own project is completely public. Anyone can browse the registry and add modules to their project without an account, a token, or signing in anywhere.
0. Get the CLI
The CLI lives inside the NextAPI framework repo itself. Clone it and install dependencies once:
Then link it so the nextapi command is available everywhere:
Prefer not to install it globally? Every command below also works prefixed with npm run nextapi --, e.g. npm run nextapi -- add profile-page.
1. Add a module
Browse the /registry for something you want, then add it to your project. No login required, this just records what you want in your own project's lockfile:
2. Install it
Fetches and installs everything you've added, on any machine, even one that's never seen this module before:
If the module you're adding needs other modules, they're fetched and installed automatically. You don't need to add each one yourself.
That's it for using modules. Publishing your own is a separate flow, covered next, and does need an account.
02 · Quickstart
Publishing modules
Publishing is the one part of this workflow that needs an account. You'll need an access token to prove the CLI is acting on your behalf, it keeps your account secure without asking you to type your password every time.
1. Create a token
Go to /settings/tokens while logged in and create a token, then paste it into the CLI:
2. Publish a module
Run this from inside a folder containing a module.json. It bundles up the folder and uploads it as a new version.
The first time you publish a given id, it's registered to your account automatically. From then on, only you can publish new versions of it.
02 · Reference
CLI reference
nextapi loginPaste the client_id:client_secret token you created at /settings/tokens. It's saved to .nextapi/auth.json so you only do this once per machine.
nextapi publish [dir]Publishes the module in dir (defaults to the current folder) as a new version. The first time you publish a given id, it's registered automatically. Once a version is published it can't be changed or replaced, publish a new version number instead.
nextapi add <id | id@range | path>Adds a module to your project. Three ways to call it:
nextapi add profile-page: get the latest version.nextapi add profile-page@^1.0.0: get a specific version or range.nextapi add ./my-local-module: add a module from a folder on your own machine.
nextapi syncInstalls everything you've added. If a module needs other modules to work, those are fetched and installed for you too, so you won't need to track down dependencies by hand.
nextapi remove <id> [--force] [--purge-env]Removes a module from your project. If you've edited its files yourself, it'll ask you to confirm with --force so you don't lose changes by accident. Add --purge-env to also clean up any environment variables it added.
nextapi listShows everything installed, and flags anything that's been changed since it was set up.
03 · Schema
The module.json manifest
Every module is described by a single manifest file at its root. Here's a real one:
{
"id": "ai-notes",
"version": "1.0.0",
"nextapiVersion": ">=1.0.0 <2.0.0",
"displayName": "AI Notes",
"description": "Adds a protected /notes page...",
"category": "ai",
"author": { "name": "NextAPI", "url": "" },
"license": "MIT",
"requires": { "modules": ["profile-page@^1.0.0"] },
"env": {
"backend": [
{ "key": "ANTHROPIC_API_KEY", "required": true, "secret": true }
],
"frontend": []
},
"dependencies": {
"frontend": { "npm": { "react-markdown": "^9.0.0" } },
"backend": { "pypi": { "anthropic": "^0.40.0" } }
},
"frontend": {
"routes": [{ "path": "/notes", "protected": true, "label": "Notes" }]
},
"backend": {
"router": { "module": "backend/router.py", "attr": "router", "prefix": "/notes" },
"collection": "module_ai_notes"
},
"functions": {
"uses": ["auth.get_current_user", "profile.get_display_name"]
},
"postInstall": { "message": "Set ANTHROPIC_API_KEY, then run `nextapi sync`." },
"notes": "A full notes page with AI summarization, not just a text field."
}| Field | Type | Description |
|---|---|---|
| id* | string | The module's unique name. Plain names like "profile-page" are reserved for official modules, everyone else uses a scoped name like "@alice/profile-page". |
| version* | semver | A version number in MAJOR.MINOR.PATCH form, optionally with a -prerelease tag. Once published, a version can't be changed. |
| nextapiVersion* | range | Which app versions this module works with, e.g. ">=1.0.0 <2.0.0". |
| displayName | string | The name shown in the registry. |
| description | string | Shown in search results and on the module's page. |
| category | string | A grouping used for browsing, e.g. "core", "ai", "auth". |
| author.name / author.url | string | Who made it, shown on the module's page. |
| license | string | The license it's released under, e.g. "MIT". |
| requires.modules | string[] | Other modules this one needs, written as "id@range", e.g. "profile-page@^1.0.0". |
| env.backend / env.frontend | object[] | Environment variables the module needs ({ key, required, secret, description }), added for you when it's installed. |
| dependencies.frontend.npm | object | Any npm packages the module needs, added automatically on install. |
| dependencies.backend.pypi | object | Any Python packages the module needs, added automatically on install. |
| frontend.routes | object[] | The pages this module adds: path, source folder, whether it needs sign-in, and who can see it. |
| backend.router | object | Where the module's server-side code lives and which URL prefix it responds to. |
| backend.models | object | Where the module's database model lives. |
| backend.collection | string | The database collection this module stores its data in. |
| functions.uses / functions.provides | string[] | Functions this module calls from other modules, or makes available for others to call. |
| postInstall.message | string | A message shown after the module is installed, handy for any manual setup steps. |
| notes | string | Optional. A short note shown to visitors on the module's Details tab before they install it, e.g. what kind of experience it is or a dependency worth knowing about. Plain language, no marketing copy. |
* Required.
04 · Naming
Names never clash
Nothing stops two different people from wanting to call their module profile-page. To keep every name unique, plain names are reserved for official modules, and everyone else publishes under their own username.
profile-page
Reserved for official modules only.
@alice/profile-page
Everyone else publishes under their own username.
A unique name on its own isn't quite enough, since two people's modules could still both want the page /profile. So when a scoped module is installed, anything that could clash gets your username folded in automatically:
- Pages:
/profileinstalls at/alice/profile - Server-side code: kept under its own separate namespace per module
- Database storage: kept separate per module too, even if two authors used the same names
You can write your module as if it were the only one that will ever exist. The rest is handled for you when it's installed.
05 · Versions
Versions and updates
Version numbers
Every version needs a proper number in MAJOR.MINOR.PATCH form, optionally with a -prerelease tag. Anything else is rejected when you publish.
Once published, it's final
You can't change or replace 1.2.0 after it's out, publish a new version instead. You can still pull an old one from circulation (see yanking, below).
"Latest" always means latest
Based on version number, not publish order. If 1.5.0 comes out after 2.0.0, people installing "latest" still get 2.0.0.
Yanking
Pulls a version out of search and new installs without deleting it. Anyone already using it isn't affected.
When you need a specific version of a module, or a module depends on one, you can write it as:
| ^1.2.0 | Anything compatible with 1.2.0: same major version, same or newer. |
| >=1.0.0 <2.0.0 | Anything within a specific range. |
| (nothing) | Just get the latest version. |
Nothing to track down by hand
If a module you're adding or syncing needs a version that isn't on your machine, it's fetched automatically. If the requested version isn't available, the error message lists what is.
Keeping this free
The registry, the CLI, and the AI Builder all run on real infrastructure and real API costs.
Hosting the registry is the easy part. The AI Module Builder calls Claude on every message, every generated file, every screenshot you attach, that's a real, ongoing bill, not a one-time cost. None of it is paid for by ads, by selling your data, or by a "free tier that quietly gets worse." It's paid for directly, so it can stay that way.
Registry & hosting
Every module, every version, every tarball, served instantly, forever.
AI Builder usage
Claude API calls for every module the Builder scaffolds, on the house.
Staying open source
No paywalled core features, no rug pull. Sponsorship is what makes that sustainable.