NotusBase modules
These modules came before NotusBase ran Convex components from npm (app.use, since 2026-11-11). They ship inside the binary, so they follow the NotusBase version, with no npm package. They only enter a project that enables them.
In 0.3 the modules that a component from npm does the same as left the binary: ratelimit (use @convex-dev/rate-limiter), stripe (@convex-dev/stripe) and migrations (@convex-dev/migrations). An import of one stops codegen and deploy naming the package. What stays is what has no component that does the same: email sends through queue, and certificates is a library of NotusBase's own.
| Module | What it does | Came from |
|---|---|---|
queue | Queue with a parallelism limit, retries with growing backoff and a notification on completion | @convex-dev/workpool, @convex-dev/action-retrier |
email | Transactional email written in the mutation's transaction and sent through the queue, by the panel's SMTP or Resend; under notusbase dev it is captured in the table. Templates with typed variables, a shared layout and a preview in the panel | the idea of @convex-dev/resend (no code) |
certificates | Client certificate (A1) for banks and government APIs: fetch with mutual TLS and XML signatures. A library: no tables, no functions | Original |
Enabling
Three steps, all in the project. The example uses a module x:
Tables in
convex/schema.ts, all with the module's prefix:import { xTables } from "notusbase-modules/x/schema"; export default defineSchema({ ...xTables, /* yours */ });A file
convex/x.ts, named after the module:export * from "notusbase-modules/x";The module calls its own functions through that path (
x:name), so the file cannot have another name or live in a subfolder.The route in
convex/http.ts, if the module has one.
Then notusbase deploy or notusbase dev. A module that is not enabled does not enter the bundle or create a table.
Disabling
Remove the file from convex/, the tables from schema.ts and the route, and publish. The code leaves the bundle and the data stays in the database, because NotusBase never drops a table. Enabling it again finds everything as it was.
How it works
notusbase-modules/*is resolved by the bundler from the copy embedded in the binary (modules.go).convex/serverandconvex/valuesfollow the usual rules, so a module carries no dependencies of its own.- For
tscand the editor,notusbase codegen,devanddeploycopy the sources tonode_modules/notusbase-moduleswhen the project imports a module. That copy only serves the types: the bundle always uses the one in the binary. - A module's functions are
internal*. The client cannot reach them. Whoever wants to expose something writes their own function, with their own authorization. - A module keeps no state in a module variable.
modules_test.gochecks this, and that is whynew Queue(...)at the top of a file does not count as state in the deploy warning. - A module is not a component, so there is no isolation: the tables belong to the project, and
ctx.dbcan see them. The prefix is the only separation.
Creating a module
A folder modules/<name> with index.ts (functions and client), schema.ts (<name>Tables), README.md and, if it reuses code from another project, NOTICE with license and attribution. A module that reads process.env or needs another module says so in module.json, next to the code: { "env": { "required": [], "production": [], "when": { "NAME=value": [] }, "optional": [] }, "requires": [] }. production is required except under notusbase dev, and when only while the server has that value. The deploy, the start and the Environment screen warn from it, and needs_test.go checks it against the code. The test lives in app/modules/<name>, with a real project, and goes into the bench/smoke.sh list. A library without tables or functions, such as certificates, has no schema.ts, and its test can be a Go test that deploys a small project.