Convex functions
The authz-first calling pattern, Convex 1.41 conventions, and a map of the key backend function modules.
Synthesis note: there is no single
convex-functions.mdsource doc. This page is assembled from the directory map and conventions indocs/ARCHITECTURE.mdplus the gate definitions indocs/auth-and-permissions.md. When in doubt, read the module underconvex/.
The Convex backend (1.41) holds the database, queries, mutations, actions, file storage, and HTTP actions. Stack details live in Architecture.
The authz-first pattern (non-negotiable)
Every website-scoped function takes the client-supplied id, then immediately
calls a require*Access helper that loads the row and verifies the current
verified user's membership before the id is used for anything. Client ids are
never trusted directly; identity always comes from ctx.auth.getUserIdentity(),
never from a function argument.
const { user, website, role } = await requireWebsiteAccess(ctx, args.websiteId);
// safe to proceed — identity verified, edit permission confirmedThe capability gates (convex/lib/authz.ts):
| Helper | Gate |
|---|---|
requireUser(ctx) | signed-in |
requireWebsiteRead(ctx, websiteId) | any member (viewer ok) — viewer-visible reads only |
requireWebsiteAccess(ctx, websiteId) | owner / editor — the default for reads + writes |
requireWebsiteOwner(ctx, websiteId) | workspace owner — delete site, manage people |
requirePageRead / requirePageAccess(ctx, pageId) | resolved via page.websiteId |
requireSectionRead / requireSectionAccess(ctx, sectionId) | resolved via section.websiteId |
The default is fail-closed: a forgotten gate denies viewers, never grants
access. Any new function that uses a client id without a require* gate is a bug.
Full detail in Auth & permissions.
Convex conventions
- Queries: use
.withIndex(...), never.filter(...). Use.take(n)/.paginate(...), never an unbounded.collect()on a growable table. - db API (1.41):
ctx.db.get("table", id),insert("table", v),patch("table", id, v),delete("table", id). - Content paths: mutate section
contentvialib/editor/contentPath.tssetPath/getPath(these reject__proto__/constructor/prototype). - Ordering:
sections.orderis a LexoRank string (lib/editor/fractionalIndex.ts); moves are single-row writes. - Undo: mutations that change content record an inverse op in
changeHistorysohistory.tscan replay it (undoLast/redoLast). - Cross-reference defense in depth: after the gate verifies the parent row, a
function re-checks that a referenced child belongs to the same website (e.g.
asset.websiteId === section.websiteIdinassets.tssetSectionImage). - Tests: put
*.test.tsinconvex/; inject identity witht.withIdentity({ tokenIdentifier, subject })and runbun run test.
Key function modules
| Module | Responsibility |
|---|---|
editor.ts | Reactive draft reads: getDraftSite, getPageSections |
sections.ts | Editor mutations: updateSectionContent, reorder, hide, duplicate, delete, changeSectionVariant, changeSectionTone, addSection, updateTheme |
history.ts | Linear undo / redo over the inverse-op log (undoLast, canUndo) |
publish.ts | publishWebsite / publishWebsiteCore, getPrepublishChecks (QA), getPublishedSnapshot, getSiteByHost |
assets.ts | Image upload + setSectionImage + getAssetUrls |
websites.ts | Settings: updateBusinessInfo, updateSlug, setStatus, deleteWebsite, route resolution |
posts.ts | Posts (pages with pageType:"post"): createPost, listPostsAdmin, updatePostMeta, setFeaturedImage |
services.ts | Phase S canonical offering CRUD (OCC rev) |
offers.ts / invoices.ts | Quotes and invoices (own number series, token pages) |
inbox.ts | Lead + booking management feed |
domains.ts | connectDomain / verifyDomain (DoH), getSlugByHost |
aiChat.ts | AI editor assistant: sendMessage (agent loop), applyStagedEdits, applyTool (the single tenant-safe executor) |
generate.ts + generation/* | Deterministic site generation + optional LLM polish |
onboarding.ts | Onboarding draft CRUD + getLatestDraft (resume) |
invites.ts / members.ts | Sharing: invite create/accept/revoke; member list/remove (owner-only) |
analytics.ts + http.ts | Public /track, /lead, /booking endpoints + summaries |
presence.ts | Live multiplayer editor presence (ephemeral) |
restorePoints.ts | Version-history timeline (normalized draft snapshots) |
maintenance.ts | Derived in-app QA nudges |
Public (no-account) endpoints
Published sites POST to Convex HTTP endpoints in convex/http.ts (/lead,
/booking, /booking-slots, /track). These are public by design but
rate-limited transactionally (convex/lib/rateLimit.ts, rateLimits table)
keyed by slug / IP, and they write only into runtime tables. They resolve the
site by slug server-side and re-validate against the published config — see
Routes and
Auth & permissions.