# Mirio quickstart

Mirio puts a form on a site that a coding agent builds, and handles everything after submit. This page is the whole happy path: about two minutes, and no human is needed until the claim at the end. There is no push step to remember: the framework plugin registers forms while you develop and when the site builds.

> **Started in the Mirio dashboard?** If you created the form under **New form** in Mirio, open the form's page and click **Copy prompt**. Give it to your coding agent: it starts with `npx @trymirio/mirio init --link <code>`, which connects this repo to that site (already claimed, no sandbox) and lists the form files to write. Then continue at step 2.

## 1. Set up the project

```bash
npx @trymirio/mirio init --agent <your name>
npm install @trymirio/mirio
```

Agents: name yourselves with `--agent` (for example `claude-code`, `cursor`, `codex`), so the site's owner can see which agent set it up.

**In a monorepo**, run `init` in the app's folder (the one with `next.config`, `vite.config` or `astro.config`); `mirio.json` lives there and every `mirio` command runs from there. Run it at a monorepo root and it stops, names the app folder, and leaves a link code unused.

`init` creates a sandbox project and sets up the repo:

- `mirio.json` (public project id), the secret key in `.mirio/secret` (gitignored), `mirio/client.ts`, `mirio/forms/`.
- **The framework plugin**: wraps `next.config.*` in `withMirio(...)`, or adds `mirio()` to `vite.config.*` / `astro.config.*`. It works alongside other config wrappers (`withSentryConfig`, `withMDX` and the like) in any order. If the config has a shape it can't edit safely, it prints the two lines to add.
- **The CSS**: next to `@import "tailwindcss"` in your global stylesheet it adds `@trymirio/mirio/theme.css` and the `@source` line, plus `@trymirio/mirio/themes/shadcn.css` on shadcn/ui sites that use shadcn's color variables (it checks `components.json` and that your CSS defines them; a site with its own tokens keeps Mirio's defaults). Without Tailwind it tells you to import `@trymirio/mirio/styles.css`.
- A short "Mirio forms" section in `AGENTS.md` or `CLAUDE.md`.

It prints a claim link. Keep it; the human needs it in step 5. Sandbox projects accept submissions from localhost only.

## 2. Write the schema

`mirio/forms/waitlist.ts`:

```ts
import { defineForm } from '@trymirio/mirio/schema';

export const waitlist = defineForm({
  slug: 'waitlist',
  name: 'Waitlist',
  pages: [
    {
      fields: [
        { key: 'email', type: 'email', label: 'Work email', autocomplete: 'email', required: true },
        { key: 'name', type: 'short_text', label: 'Name', autocomplete: 'name' },
        { key: 'role', type: 'dropdown', label: 'Role', options: ['Founder', 'Engineer', 'Designer', 'Other'] },
        { key: 'consent', type: 'consent', label: 'Email me when there is news', required: true },
      ],
    },
  ],
  settings: {
    success: { type: 'message', title: 'You are on the list', message: 'We will email you when your spot opens.' },
  },
});
```

See [Form schema](/docs/schema.md) for every field type and constraint. Notifications, allowed domains and triage are set in Mirio, not here (`npx mirio destinations add email you@company.com --all-forms`, `mirio domains`, `mirio review`).

## 3. Render it

```tsx
// components/waitlist-form.tsx
'use client';

import { Form } from '@trymirio/mirio';
import { mirio } from '@/mirio/client';
import { waitlist } from '@/mirio/forms/waitlist';

export function WaitlistForm() {
  return <Form schema={waitlist} client={mirio} />;
}
```

Then use `<WaitlistForm />` in any page. In Next.js (App Router) the form has to live in a client component like this one (`'use client'` at the top): `mirio`, the client, can't be passed from a server component. Pass it as `client`, or wrap the app once in [MirioProvider](/docs/components/provider.md); without either, `<Form>` throws and says so.

`<Form>` with no children renders every field, validation, the submit button and the success state; multi-page schemas render one step at a time. It inherits your site's font and follows its dark mode. Adjust it with `theme`, `size`, `layout` and `classNames` ([Theming](/docs/theming.md)) or compose it yourself ([Components](/docs/components.md)).

**Optional, so ask the site owner first:** `<MirioAttribution />` (from `@trymirio/mirio`), added once to the root layout, lets each response say which page on the site led to the form and where the visit started. It keeps a small record of the visit in the visitor's browser tab (`sessionStorage`). It takes no props, so a server layout can render it. See [Attribution and hidden fields](/docs/attribution.md).

If the site sets a Content Security Policy, add the Mirio API to `connect-src` (`https://api.trymirio.com`, and `http://localhost:4000` against a local API).

## 4. Run the dev server and test

```bash
npm run dev
npx mirio test waitlist
```

While the dev server runs, saving a file in `mirio/forms/` registers that form (one line in the terminal: `[mirio] waitlist: v2 registered (development)`, or the validation issues). `mirio test` sends a sample submission end to end and reads it back. Submit the form on localhost yourself too; `npx mirio submissions waitlist` shows what landed.

No dev server (a script, CI)? `npx mirio push` does the same registration by hand.

## 5. Claim, then give the host the key

Tell the human the form works locally and share the claim link (`npx mirio claim` prints it again). They sign in with Google, GitHub or a code sent to their email, accept the terms and name the production domains. Then, once:

```bash
npx mirio env vercel     # or: npx mirio env netlify, npx mirio env print
```

This puts `MIRIO_SECRET_KEY` into the host's build environment (on stdin, never echoed). From then on every deploy registers its forms during the build, tagged production or preview from the host's environment; production builds set the questions the dashboard shows.

Without the key a build still checks: it **fails** only if visitors would be rejected (the project is unclaimed, or a form was never registered) and **warns** when questions changed since the last registration (those responses are accepted and flagged). `npx mirio sync` runs the same step in any other CI.

## What happens after submit

- The server validates every submission against the registered schema and normalizes values (trimmed, lowercased emails, E.164 phone numbers).
- Spam is scored (honeypot, time to submit, disposable emails, link stuffing, optional Turnstile) and goes to a spam view. Nothing is discarded.
- Submissions are stored by stable field id, so renaming a field with `renamedFrom` keeps its data together.
- Completed submissions go to verified email addresses, Slack (connect a channel with "Add to Slack", or `mirio destinations add slack --form <slug>` for a link a person approves), your database and signed webhooks.
- Multi-step progress is saved at each step as a partial submission. Partials never trigger notifications.
