Quickstart
Go from an empty folder to a Next.js app with sign-in, Postgres-style data and AI that passes mainsdk check, offline, adding one main.yaml key at a time.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
You'll build Pinecart, a small Next.js orders app: people sign in, see only their own orders, and get a one-line AI summary of an order. You start from a four-key main.yaml, add one key at a time, run it offline, and finish with a passing mainsdk check. Then you swap in real providers and the production settings.
You need Node.js 22+ and git. You don't need a MainSDK account or any provider keys.
Before you start. Three ideas are enough for this page:
- Interface: one standard way to do a job, such as
auth,dborai. Your code calls it, never the provider. - Driver: the provider behind an interface. In development it is a local one with no account or key.
- The check:
mainsdk checkfails on mistakes, such as a key in code, and says how to fix each one.
Anything else has a one-line entry in the glossary. The shortest route is the thirty-minute path.
1. Create the app
npx create-next-app@latest pinecart --ts --app
cd pinecart
git init2. Run init with the minimal preset
npx mainsdk init --preset minimalpnpm dlx mainsdk init --preset minimalbunx mainsdk init --preset minimalinit asks no questions and works offline. The default indie preset also writes the local auth, db and files drivers, logging and a deny-by-default network; this page uses minimal so the whole file fits in four lines. It writes a main.yaml, git-ignores .mainsdk/ (local events and local secrets live there), adds MainSDK rules to your agent files and registers the MCP server for your agent.
schema: 1
project: prj_01JC8Q4N7V3X9B2D5F6G8H0JKM
baseline: main27
overlays: { order: [env] }That is the whole file. schema is the file format, project names your project and is never a credential, baseline: main27 names the rules your app is checked against, and overlays.order says how per-environment files layer. The minimal preset only chose these keys; you can widen or tighten any of them later.
3. Add sign-in
npx mainsdk add authWhy: your orders need an owner, and an owner needs a signed-in user. add uses the local driver for auth, writes one example test, runs the check and changes nothing else. main.yaml gains one key:
auth: { driver: local }4. Add data
npx mainsdk add dbWhy: Pinecart stores orders. The local db driver is SQLite, so there is nothing to run.
db: { driver: sqlite }5. Add AI
npx mainsdk add aiWhy: the order summary calls a model. The local ai driver needs no key.
ai: { driver: local }6. Write a route and an action
A route that needs a signed-in user and returns only that user's orders:
import { withMainSDK } from "mainsdk/next";
import { auth, db } from "mainsdk";
export const GET = withMainSDK(async (req) => {
const session = await auth.require(req); // 401 with a fix if not signed in
const orders = await db.orders.for(session.user).list({ limit: 20 }); // no .for(), no compile
return Response.json(orders);
});An action that summarises one order. You write it once, and it becomes an HTTP endpoint, a server action, a CLI command and an audit event, all behind the same checks:
import { action, ai, db } from "mainsdk";
import { s } from "mainsdk/schema";
export const summariseOrder = action({
name: "orders.summarise",
intent: "Summarise an order the signed-in user owns",
input: s.object({ orderId: s.string() }),
risk: "read",
run: async ({ input, user }) => {
const order = await db.orders.for(user).get(input.orderId);
return ai.generate({
model: "anthropic/claude-sonnet-4-5",
prompt: `Summarise order ${order.id} in one sentence.`,
feature: "order-summary", // spend limits and audit per feature
user, // and per user
});
},
});Actions and MCP shows how to expose it to agents as well.
7. Run it offline
npx mainsdk dev -- npm run devmainsdk dev gives every interface a local driver: SQLite for db, secrets from .mainsdk/secrets.json, logs in your terminal. It makes no calls to GitHub, AWS or a database host.
8. Check
$ npx mainsdk check
mainsdk check · pinecart · env dev · Main 27
secrets config policy network data
all gates passed · exit 0You have an app that signs people in, scopes every query to its owner, calls AI through one interface and passes the check, with a main.yaml of four keys. Your first check breaks it on purpose and shows how to fix what the check finds.
What runs when
Nothing is published yet: MainSDK is pre-release and these commands are the planned design. Each row says the milestone that makes it run.
| Milestone | What runs | Spec |
|---|---|---|
| 0.2 | mainsdk init, check, sync, info, logs and dev. Local mode for config, secrets, policy and log, so dev runs offline with no account. | cli-core |
| 0.3 | init --preset, add, doctor and check --json --agent. Local drivers for auth, db and ai, and the other interfaces specified for 0.3, such as files and share. | fast-start and the interface specs |
| 0.4 | The testing kit: @mainsdk/testing with a fake of every interface. | testing-kit |
| 0.5 | Public beta, the migration skill and mainsdk adopt for an existing app. | public-beta, migration-skill |
Make it production-ready
Everything above runs on local drivers. npx mainsdk check --env prod fails with MAIN-CHECK-0026 local driver in production until each interface has a real driver. Do this when you are ready to ship, not before. Each key below has a reason and a page.
Point the drivers at real providers. Database URLs are secret: references, resolved on the server; Bedrock uses the cloud role your app runs with:
auth: { driver: github, methods: [passkeys] }
ai:
driver: bedrock
models: [anthropic/claude-*]
data: { training: never, retention: zero }
db:
driver: postgres
regions: { us: secret:DATABASE_URL_US, eu: secret:DATABASE_URL_EU }Then add the keys that make the app compliant for your users:
| Key | Why you add it | Read |
|---|---|---|
data.residency | Keep each user's records in their own region (us, eu) | Data and regions |
db.fields with label | Mark fields such as user.email as personal so they are handled and redacted correctly | Data and regions |
policy | Choose the policy engine and where decisions run | Policy |
network | Allow outbound hosts beyond what each driver declares; the default is deny | Security model |
users.audience and regions | Say who your users are and where, so the check applies the right rules | The main.yaml file |
overlays.order | Apply small per-market, segment or environment files in a fixed order | The main.yaml file |
For example, the data keys for Pinecart:
data: { store: postgres, residency: { default: us, by-market: { eu: eu } } }
db:
fields:
user.email: { label: personal }Run npx mainsdk check --env prod until it exits 0. Every key is in the main.yaml reference. To apply changes as a reviewed pull request, run npx mainsdk sync --plan, then npx mainsdk sync: it pins driver versions in main.lock, commits on a new branch, runs check and opens a pull request. With no git remote it leaves the branch and prints the plan; --no-pr skips the pull request.
Next
Was this page helpful?