Quickstart

bun install
bunx convex dev                                    # creates the project, writes .env.local
bunx convex env set BETTER_AUTH_SECRET "$(openssl rand -base64 32)"
bunx convex env set SITE_URL https://aussieauth.localhost
bun dev

bun dev runs vite through portless, which serves the app at https://aussieauth.localhost.

SITE_URL is the deployed origin, not the local one

SITE_URL points at the deployed origin, not the one you're developing on. It's the relying party id for passkeys and the base for emailed links, so it has to be the one real users are on. Local origins go in TRUSTED_ORIGINS instead.

What works immediately

That's enough for every method that doesn't need a third party: email/password, username, passkeys, Solana, anonymous, account numbers, demo, and agent keys.

Magic links and OTP codes work too — without RESEND_API_KEY the link or code is written to the Convex logs instead of an inbox, which is usually what you want while developing.

That fallback is local-only. It applies when SITE_URL points at localhost; off a real domain, a missing provider key is an error rather than a log line, because a magic link is the credential and "sent" that quietly means "logged" is the worst of both.

Environment variables are typed

Every variable the backend reads is declared in convex/convex.config.ts, so the functions get a typed env from ./_generated/server rather than process.env. A misspelled name is a type error, and Convex validates values when they're set. Adding a variable means declaring it there first.

BETTER_AUTH_SECRET is the only one declared required: leave it unset and the push is refused, rather than Better Auth quietly signing sessions with a fallback. Everything else is optional, because an unset variable is a method this deployment doesn't offer, not a broken deployment.

Third-party credentials

Each provider is registered only when its variables are set. Set them with bunx convex env set.

MethodVariablesCallback / redirect URL
GoogleGOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECREThttps://<deployment>.convex.site/api/auth/callback/google
GitHubGITHUB_CLIENT_ID, GITHUB_CLIENT_SECREThttps://<deployment>.convex.site/api/auth/callback/github
AppleAPPLE_CLIENT_ID, APPLE_TEAM_ID, APPLE_KEY_ID, APPLE_PRIVATE_KEYhttps://<deployment>.convex.site/api/auth/callback/apple
Email (links/OTP)RESEND_API_KEY, EMAIL_FROM
SMS (OTP)MOBILE_MESSAGE_API_USERNAME, MOBILE_MESSAGE_API_PASSWORD, MOBILE_MESSAGE_SENDER

Google and Apple each have a guided walkthrough: Setting up Google and Setting up Apple.

Tests

bun run test             # everything
bun run test:unit        # just the fast node ones
bun run test:component   # just the browser ones

Use bun run test, not bun test — the latter is Bun's own runner, not vitest.

Changing the auth schema

Adding or removing a Better Auth plugin can change the tables it needs:

bun run auth:schema     # regenerates convex/betterAuth/schema.ts from convex/auth.ts

This replaces the documented npx auth generate, whose published CLI still targets Better Auth 1.4.

Questions

Why bun run test rather than bun test? bun test is Bun's built-in runner and won't pick up the vitest projects, the browser provider, or the setup files. It will appear to run and find nothing.

Do I need RESEND_API_KEY to develop? No. With a localhost SITE_URL and no key, magic links and OTP codes are written to the Convex logs — faster to work with than a real inbox. On a deployment whose SITE_URL is a real domain, sending without a key throws instead, so a lapsed key surfaces as an error rather than as mail that never arrives.

Why won't SITE_URL accept my localhost origin? It will, but it shouldn't be one. Passkeys bind to SITE_URL's hostname as their relying party id, so pointing it at localhost creates passkeys that don't work in production. Put local origins in TRUSTED_ORIGINS.

Read this page as markdown: /docs/quickstart.md
Your apps talk to this server directly — no AussieAuth consent screen, ever.