---
title: "Quickstart"
description: "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."
section: "getting-started"
status: "Planned for 0.2"
spec: "cli-core"
canonical: "https://mainsdk.dev/docs/getting-started/quickstart"
updated: "2026-10-28"
---

# 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: Planned for 0.2 · Canonical: https://mainsdk.dev/docs/getting-started/quickstart · 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.

> **Note:** Nothing is published yet: MainSDK is pre-release, and the commands below are the planned design. What runs from the repository today, and how to run it, is in the [README](https://github.com/hubxero/mainsdk#quick-start). The table under [What runs when](#what-runs-when) says which milestone makes each step run.

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](https://mainsdk.dev/docs/reference/glossary). The shortest route is the [thirty-minute path](https://mainsdk.dev/docs/getting-started/thirty-minutes).

## 1. Create the app

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

## 2. Run init with the minimal preset

npm:

```bash
npx mainsdk init --preset minimal
```

pnpm:

```bash
pnpm dlx mainsdk init --preset minimal
```

bun:

```bash
bunx 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.

```yaml title="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:

```yaml title="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.

```yaml title="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.

```yaml title="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:

```ts title="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:

```ts title="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](https://mainsdk.dev/docs/concepts/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](https://mainsdk.dev/docs/getting-started/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:

```yaml title="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:

| Key | Why you add it | Read |
| --- | --- | --- |
| `data.residency` | Keep each user's records in their own region (`us`, `eu`) | [Data and regions](https://mainsdk.dev/docs/concepts/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](https://mainsdk.dev/docs/concepts/data-and-regions) |
| `policy` | Choose the policy engine and where decisions run | [Policy](https://mainsdk.dev/docs/concepts/policy) |
| `network` | Allow outbound hosts beyond what each driver declares; the default is deny | [Security model](https://mainsdk.dev/docs/concepts/security-model) |
| `users.audience` and `regions` | Say who your users are and where, so the check applies the right rules | [The main.yaml file](https://mainsdk.dev/docs/concepts/main-yaml) |
| `overlays.order` | Apply small per-market, segment or environment files in a fixed order | [The main.yaml file](https://mainsdk.dev/docs/concepts/main-yaml) |

For example, the data keys for Pinecart:

```yaml title="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](https://mainsdk.dev/docs/reference/main-yaml). 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

- [Your first check](https://mainsdk.dev/docs/getting-started/first-check)
- [The main.yaml file](https://mainsdk.dev/docs/concepts/main-yaml)
- [Deploy on Vercel](https://mainsdk.dev/docs/guides/deploy-vercel)
