MainSDK
Search⌘K
Docs

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.

Planned for 0.2

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.
Stability levels
Updated 2026-10-28

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, db or ai. 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 check fails 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

bash
npx create-next-app@latest pinecart --ts --app
cd pinecart
git init

2. Run init with the minimal preset

npx mainsdk init --preset minimal

init 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.

main.yaml
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

bash
npx mainsdk add auth

Why: 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:

main.yaml
auth: { driver: local }

4. Add data

bash
npx mainsdk add db

Why: Pinecart stores orders. The local db driver is SQLite, so there is nothing to run.

main.yaml
db: { driver: sqlite }

5. Add AI

bash
npx mainsdk add ai

Why: the order summary calls a model. The local ai driver needs no key.

main.yaml
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:

app/api/orders/route.ts
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:

app/actions/summarise-order.ts
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

bash
npx mainsdk dev -- npm run dev

mainsdk 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

text
$ npx mainsdk check
mainsdk check · pinecart · env dev · Main 27
 secrets   config   policy   network   data
all gates passed · exit 0

You 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.

MilestoneWhat runsSpec
0.2mainsdk 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.3init --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.4The testing kit: @mainsdk/testing with a fake of every interface.testing-kit
0.5Public 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:

main.env.prod.yaml
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:

KeyWhy you add itRead
data.residencyKeep each user's records in their own region (us, eu)Data and regions
db.fields with labelMark fields such as user.email as personal so they are handled and redacted correctlyData and regions
policyChoose the policy engine and where decisions runPolicy
networkAllow outbound hosts beyond what each driver declares; the default is denySecurity model
users.audience and regionsSay who your users are and where, so the check applies the right rulesThe main.yaml file
overlays.orderApply small per-market, segment or environment files in a fixed orderThe main.yaml file

For example, the data keys for Pinecart:

main.yaml
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?

↑↓ moveEnter openEsc close

Not decided yet