Skip to content

Develop locally

The SDK package includes a local Maypop host for Vite, Rsbuild and Next.js. Your dev server wraps the app the way Maypop does, so maypop.ready(), maypop.kv, maypop.drive and the rest work without signing in or publishing anything.

Terminal window
pnpm add @basilica-digital/maypop-sdk

Your app code needs no special branch for local development:

import { maypop } from "@basilica-digital/maypop-sdk";
await maypop.ready();
const records = await maypop.kv.list({ prefix: "record/" });

Vite — vite.config.ts:

import { maypop } from "@basilica-digital/maypop-sdk/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [maypop()],
});

Rsbuild — rsbuild.config.ts:

import { defineConfig } from "@rsbuild/core";
import { maypop } from "@basilica-digital/maypop-sdk/rsbuild";
export default defineConfig({
plugins: [maypop()],
});

Next.js — next.config.ts:

import { withMaypop } from "@basilica-digital/maypop-sdk/next";
export default withMaypop({
output: "export",
});

The host only runs in development; production builds are untouched. Start your usual dev command and open its usual URL.

You are signed in as an admin named Developer. Everything that belongs to your machine lives in .maypop/local/ and survives restarts:

Path Holds
.maypop/local/kv.json KV data
.maypop/local/drive/ Drive files and their metadata
.maypop/local/notifications.json notifications your app sent
.maypop/local/dev.json who you are testing as (below)
.maypop/local/mcp.json mock MCP servers (below)

The dev server gives .maypop/local/ its own .gitignore, so none of it reaches Git. Your saved-data policies, .maypop/kv-policy.json and .maypop/drive-policy.json, sit next to it and are committed: Maypop applies them with each published version.

To try a second person or a separate dataset, give each dev server its own directory (one directory can’t be used by two servers at once):

maypop({ username: "Alice", dataDirectory: ".maypop/alice" });
withMaypop(nextConfig, { username: "Alice", dataDirectory: ".maypop/alice" });

.maypop/local/dev.json describes who is looking at the app:

{
"mode": "sandbox",
"viewer": {
"username": "Guest",
"role": "reader",
"anonymous": true,
"signInGrantsWrite": true,
"signedInUsername": "Alice",
"signedInRole": "writer"
},
"members": [{ "username": "Bob", "role": "editor", "connected": true }],
"guestCount": 2,
"strictStorage": true
}
  • With signInGrantsWrite, calling maypop.signIn() switches the viewer to the signed-in identity and reloads — a way to test the “Sign in to save” path.
  • Writes always enforce production size limits. strictStorage also applies your committed .maypop/kv-policy.json.
  • Open /_maypop/notifications to see who each maypop.notify() call addressed, with its text and deep link. Nothing is delivered.
  • The local Share card marks its link as unpublished.
  • maypop.link.to() and notification paths open inside the local host the way Maypop opens them, so routing you test here works once published.

.maypop/local/mcp.json lets you test maypop.mcp and agent tools without calling a real service:

{
"schemaVersion": 1,
"servers": [
{
"id": "search",
"name": "Fixture search",
"url": "mock://search",
"account": "developer@example.com",
"tools": [
{
"name": "lookup",
"description": "Return a fixed search fixture.",
"inputSchema": {
"type": "object",
"properties": { "query": { "type": "string" } }
},
"result": {
"content": [{ "type": "text", "text": "Fixture result" }]
}
}
]
}
]
}

A tool without a result echoes its arguments back.

Two more modes reach Maypop through the account you signed in with maypop auth. Add "profile": "<name>" to pick a profile. The dev server mints a short-lived app session itself and never hands the CLI’s credential to the page.

Hybrid keeps KV and Drive local and forwards the capabilities you list:

{
"mode": "hybrid",
"remoteCapabilities": ["ai", "members", "link", "mcp", "multiplayer"],
"notifications": "inspect"
}

ai spends your real AI allowance. mcp uses the integrations linked to the app (see Connect external tools) — don’t combine it with .maypop/local/mcp.json mocks.

Connected talks to the real app — its data, files and integrations. The repository must be connected with maypop init:

{
"mode": "connected",
"notifications": "inspect"
}

Set "notifications": "live" in connected mode to really deliver notifications, or "disabled" to turn them off.

Opening another app with maypop.apps.open() and opening group settings need the surrounding Maypop shell, so they are unavailable locally. Check them in Maypop before release.

The local host is a development tool, not a security boundary: keep the dev server on a trusted network.