NotusBase

Rules for writing a system on NotusBase

One page for whoever writes a system on NotusBase, human or AI. It ships inside the binary and says what that version does; the pages of the guide cited along the way explain each part at length.

NotusBase runs functions in the Convex format (convex/server, convex/values and the convex client, unchanged) on PocketBase and SQLite, in a single binary. What differs from Convex is in 2. Migrating from Convex.

Project

Modules

Besides components, NotusBase has modules that ship inside the binary. There is no npm package to install: the import turns one on. notusbase init, codegen, dev and deploy write them to node_modules/notusbase-modules/<name>/, each with its README.md and the comments on every function, for tsc, the editor and you to read. npm install deletes that folder, and the project's postinstall (notusbase codegen) brings it back.

ModuleImportFor
queuenotusbase-modules/queueWork queue: parallelism limit, retries with backoff, notice on completion
emailnotusbase-modules/emailTransactional email through the queue (needs queue enabled)
certificatesnotusbase-modules/certificatesClient certificate (A1): fetch with mutual TLS and XML signature. A library: the import is enough

Rate limits, Stripe and data migrations come from npm, as Convex components: @convex-dev/rate-limiter, @convex-dev/stripe and @convex-dev/migrations, installed with app.use as each package's README shows. Until 0.3 they were modules here (ratelimit, stripe, migrations); an import of one stops codegen and deploy naming the package to install.

A module with tables is enabled in two files, and then imported wherever it is used. With queue; email takes the same shape, with its own name:

// convex/schema.ts
import { queueTables } from "notusbase-modules/queue/schema";
export default defineSchema({ ...queueTables, /* yours */ });

// convex/queue.ts: the file takes the module's name
export * from "notusbase-modules/queue";

// convex/messages.ts, or any other file
import { Queue } from "notusbase-modules/queue";

There is no other path: notusbase/queue, @notusbase/queue, convex/queue and ./queue without the file above do not exist, and codegen and deploy stop saying so. What each module exports, with examples, is in node_modules/notusbase-modules/<name>/README.md.

Modeling

Access and login

Errors

Secrets

Email

Limits

Convex's, per call: 1 s wall clock in query and mutation, 1 s of CPU per stretch in an action and 10 min in total, 32,000 documents and 16 MiB read, 16,000 writes and 16 MiB written, 1 MiB documents, 1,000 scheduled jobs per mutation, 64 MiB of heap. Read and write errors can be caught; time and memory cannot. Bulk writes go in batches chained by ctx.scheduler.runAfter(0, ...). Full table in 4. Limits.

Data and server

How to test

AI with the NotusBase MCP

notusbase mcp is a development MCP server that ships inside the binary and runs on Node, with no dependencies. It lists the functions, public and internal, with their arguments and what they return, shows the schema, reads logs, runs a function, runs read-only SQL queries, lists variable names (never the values), shows the job and cron schedule, runs a cron or a pending job now, publishes (or only shows the plan), goes back to a saved version and, with docs, prints the same reference as notusbase help. It only talks to 127.0.0.1, localhost or [::1], and uses the deploy key from the local pb_data. Never point it at production.

notusbase init writes .mcp.json in the project, with the command notusbase mcp: Claude Code reads it from the project folder and asks once whether to use it, and claude mcp list shows whether it started. In a project from before that file, create it:

{ "mcpServers": { "notusbase": { "command": "notusbase", "args": ["mcp"] } } }

The address and pb_data come from notusbase.json; NOTUSBASE_URL, NOTUSBASE_DIR and NOTUSBASE_DEPLOY_KEY override them, and NOTUSBASE_PROJECT is the folder with convex/ when the server does not start from it. The tools need notusbase dev up, except docs. rollback changes what is published: it only runs with confirm: true, and without it returns the saved versions.