Coming from Convex
onebase uses Convex's programming model and most of its API. If you know Convex, you already know onebase.
#What's the same
query,mutation,actionand theirinternal*versions, withargsandreturnsvalidatorsv.*validators,defineSchema,defineTable,.index()ctx.db.get / query / insert / patch / replace / delete / normalizeId,withIndex,filter,order,paginate,paginationOptsValidator, including the table-first formsctx.db.get("messages", id)andctx.db.patch("messages", id, value)ctx.scheduler.runAfter / runAt / cancel,cronJobs()ctx.storage.generateUploadUrl / getUrl / delete / store / get,v.id("_storage")ctx.auth.getUserIdentity()and theauth.config.tsprovider format- Built-in sign-in shaped like Convex Auth:
authTables,getAuthUserId,signIn("password" | "github" | …),signOut(),useAuthActions(),<Authenticated>(configured inauth.config.tsinstead of a separate package) httpRouter/httpActionapi/internalreferences,ConvexError.searchIndex()andwithSearchIndex(...)full-text search- Optimistic updates:
useMutation(ref).withOptimisticUpdate(...)andclient.mutation(ref, args, { optimisticUpdate }) - React:
useQuery,useMutation,useAction,usePaginatedQuery, plus theConvexProvider/ConvexReactClientaliases
#What's different
| Convex | onebase | |
|---|---|---|
| Hosting | Managed cloud or self-hosted backend | One binary you run anywhere |
| Runtime | V8 isolates; Node only in "use node" actions | Bun everywhere; any npm package in any function |
| Database | Convex's own | SQLite (default), Postgres, MySQL or Turso |
.filter() | Builder only; never uses indexes | Builder or { field: value }; uses matching indexes automatically |
| Mutations | Optimistic concurrency | Serial per process; serializable across instances |
| Imports | convex/server, convex/values | onebase/server, onebase/values (builders still come from ./_generated/server) |
| Functions folder | convex/ | onebase/ (a convex/ folder also works) |
| Full-text search | ✓ | ✓ (searchIndex, withSearchIndex) |
| Vector search | ✓ | ✓ (vectorIndex, ctx.vectorSearch) |
| Components | convex.config.ts, ctx.runMutation(components.x.lib.fn) | components.ts, ctx.components.x.fn(); Convex component packages don't run unmodified yet |
| Function handles | createFunctionHandle | ✓ (signed, so they can't be forged) |
| Dashboard | ✓ | ✓ (built in, at /_admin) |
#Porting a project
- Rename
convex/toonebase/, or keep the name: onebase picks it up. - Replace
from "convex/values"withfrom "onebase/values", andfrom "convex/server"withfrom "onebase/server".onebase doctorlists every place to change. - Remove
"use node"lines. - Run
onebase devto regenerate_generated/. - In the frontend, import from
onebase/reactinstead ofconvex/react.
Data doesn't migrate automatically. Export it from Convex as JSON and insert it with a mutation.
#Components
onebase has its own components and doesn't run @convex-dev/* packages. The common ones have built-in equivalents:
| Convex | onebase |
|---|---|
@convex-dev/rate-limiter | Rate limiter |
@convex-dev/aggregate | Aggregate; it follows its table, so the triggers go away |
@convex-dev/sharded-counter | Sharded counter |
@convex-dev/workpool | Work queue |
Other components can be ported into local onebase components; their source ships in the npm package. onebase doctor reports a leftover convex.config.ts.
#Migrating with an AI agent
onebase ships a skill that walks an agent such as Claude Code through the whole migration: the inventory, imports, each component's exact before/after mapping, porting the rest, and the checks to run. To install it into a project for Claude Code:
mkdir -p .claude/skills && cp -r node_modules/onebase/skills/convex-to-onebase .claude/skills/Then ask: "migrate this project from Convex to onebase". Other agents can be pointed at node_modules/onebase/skills/convex-to-onebase/SKILL.md, which links to the rest.