This is Convex Auth, an authentication framework built on top of Convex. We’re currently bulding the v2 version of the system, where each sign-in method uses one or more Convex components.
This is a pnpm monorepo:
packages/core— the auth package, published as@convex-dev/auth. It owns three parts:- The core: sessions, accounts, and JWT minting. It knows no sign-in method.
- The optional components, which store the data of the sign-in methods:
username,password,email,passkey,totp,anonymous,oauth,google,apple, andgithub. Most of them key their data by an opaque user id.totpis a second factor, not part of a sign-in method, and the package does not export it for now. The package does not exportanonymousand the genericoauthcomponent for now either. - The sign-in methods, which the app wires up:
anonymous(not exported for now),username-password,email-password,username-passkey,oauth(only its shared React client is exported for now; the API for custom providers is not),google,apple, andgithub.
packages/password-hash-wasm— thepassword-hash-wasmpackage wrapping the argon2id WASM hasher used by the password component. It publishes compiled JavaScript plus declarations fromdist/, so consumers never typecheck its source. Itsprepackscript builds the Rust/WASM output and then compiles the TypeScript wrapper, which keeps the Rust toolchain a publish-time requirement —pnpm installand TypeScript-only work don't need it.packages/coredepends on the published package, not the workspace one, for the same reason.packages/doc-examples— a private Convex app that installs every component and sets up every sign-in method. The code examples in the doc comments ofpackages/core/srccompile against it (pnpm check:doc-examples). It is not an example to copy.packages/docs— the documentation site (private). The pages are indocs/.packages/passkey-test-authenticator— a software WebAuthn authenticator for the passkey tests of this repository (private).examples/— example apps, each an in-repo Convex backend run from its own directory:react-passwordwires up the core, password, and username components with theusernamePasswordmethod.react-email-passwordwires up the core, password, and email components with theemailPasswordmethod (sign-up with email validation, change password/email, password recovery; email through@convex-dev/resend).react-passkeywires up the core, passkey, and username components with theusernamePasskeymethod, and adds passkey management.react-passkey-basicisreact-passkeywithout passkey management. The passkey tutorial uses it as its first step.react-googlewires up the core and the google component with thegooglemethod.react-applewires up the core and the apple component with theapplemethod.react-githubwires up the core and the github component with thegithubmethod.nextjsis a Next.js (App Router) app with SSR sign-in. It wires up theusernamePasswordandgithubmethods.
component/— the core Convex component (auth): sessions, accounts, JWTs.components/<name>/— the optional Convex components (username,password,email,passkey,totp,anonymous,oauth,google,apple,github).methods/<method>/— the sign-in methods, which use one or more components:server.ts(Convex functions of the app),react.tsxorreact.ts(hooks), and the browser code. A method is published at@convex-dev/auth/methods/<method>/serverand@convex-dev/auth/methods/<method>/react. A method whose backend and clients share values (such asDEFAULT_MIN_PASSWORD_LENGTH) also has anindex.ts, published at@convex-dev/auth/methods/<method>, which any runtime can import.server/— the app-side code of the core (setupConvexAuth,getAuthUserId).ssr/,react/,browser/— the client and SSR code of the core.ssr/nextjs/holds the Next.js bindings.lib/— internal code that several layers share (lib/oauth/for the OAuth components and methods).testing/— theconvex-testhelpers, which ship as source.
The Convex CLI deploys every module in a component directory, and codegen
lists each one in _generated/api.ts. Thus a component directory holds only
the code of the component. Put the other code in the directory of its layer,
as the list above shows: the code of a sign-in method in methods/<method>/,
the app-side code of the core in server/, the client and SSR code of the
core in ssr/, react/, or browser/, and shared code in
lib/. ESLint rejects a component module that imports client code or
app-side code. The CLI skips a file name with two dots (x.test.ts,
x.fixture.ts): use the .fixture.ts suffix for a test-only module in a
component directory.
@convex-dev/auth ships compiled ESM plus declarations under dist/; its
exports map points there, never at src/ (password-hash-wasm does the same). This matters:
a package whose exports resolve to .ts gets typechecked by the consumer's
tsconfig.json, so our source would have to satisfy their lib, types,
and verbatimModuleSyntax settings. Two rules follow from it:
- Relative imports in
packages/core/srccarry the extension of the file on disk (./cookies.ts,./client.tsx).packages/core/tsconfig.jsonsetsmoduleResolution: NodeNext, so an import without an extension is a compile error (TS2835) in the editor and intsc --noEmit, andrewriteRelativeImportExtensionsturns each one into.jsin the emitted JavaScript, which is what Node's ESM resolver needs. Declarations keep the.tsspecifier; a consumer's TypeScript reads that as the sibling.d.ts, back to TypeScript 5.0. Files under_generated/keep the.jsextensions thatconvex codegenwrites, and both spellings compile. The examples and docs are free to stay extensionless. - Anything reached from a published entry point must compile.
dist/is built bytsconfig.build.json, which excludes tests and other in-repo-only modules (see itsexcludelist). - Each
exportsentry names one file. Do not use a*: a wildcard publishes every file of a directory, including the internal modules and the_generated/files of a component.pnpm check:declarationsrejects a wildcard and an entry whose file does not exist.
The one exception is the ./test and ./components/<name>/test entries, which ship as TypeScript from
src/. Those helpers call import.meta.glob, a Vite macro that has to be
transformed by the consumer's bundler — and Vitest externalizes plain .js
under node_modules (leaving the macro intact, so it throws at import) while it
cannot externalize .ts. Shipping source is what makes them work. They carry
their own /// <reference types="vite/client" /> so a consumer doesn't need
vite/client in their tsconfig. Their schema import uses the extension of
the file on disk, the same as the rest of src/. The consumer's tsc reads
these files, thus the consumer must set allowImportingTsExtensions; without
it, the .ts extension is an error (TS5097). Each example app that imports a
test helper sets it in its convex/tsconfig.json, and pnpm typecheck catches
a regression here.
Use pnpm install at the root. Common tasks:
pnpm build— compilepackages/coretodist/. Runs automatically viaprepareon install; run it by hand after changing its sources if you want the examples to pick the change up. (password-hash-wasmis consumed from npm, so it isn't built here — see itsprepack.)pnpm codegen— regenerate every Convex_generateddirectory. Can also generate the codegen for a single demo or component (e.g.pnpm codegen react-passkey)pnpm test— run tests across packages (Vitest).pnpm typecheck— typecheck across packages.pnpm check:doc-examples— typecheck thetsandtsxcode blocks in the doc comments ofpackages/core/srcas modules ofpackages/doc-examples(runpnpm buildfirst). When an example uses a function or a component that the app does not have, add it to the app. Seepackages/doc-examples/README.md.pnpm lint— ESLint, including the Convex ESLint plugin.pnpm format— Prettier.