Docs
Get startedMirio quickstart
View as Markdown(opens in a new tab)

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

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:

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 for every field type and constraint. Notifications, allowed domains and triage are set in Mirio, not here (npx mirio destinations add email [email protected] --all-forms, mirio domains, mirio review).

3. Render it

// 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; 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) or compose it yourself (Components).

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.

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

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:

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.