Contributing
Thanks for helping! Bug reports, fixes, docs and features are all welcome. For something big, open an issue first, so we can agree on the approach before you write it.
Setting up
You need Node 22 or 24 and pnpm (the version in package.json's packageManager; corepack enable sets it up).
Clone and install
git clone https://github.com/treygrr/nuxt-better-auth-ui.git
cd nuxt-better-auth-ui
pnpm install
Configure the playground
cp playground/.env.example playground/.env
The defaults work as they are. Its comments explain the optional extras: GitHub sign-in, captcha keys, Spanish.
Prepare and start it
pnpm dev:prepare
pnpm dev
The playground runs on http://localhost:3001. Sign in as admin@example.com with password123.
The repository
It's a pnpm workspace:
| Folder | What it is |
|---|---|
src/ | The module: module.ts (its options and how it adds Better Auth's plugins), runtime/app (pages, components, words), runtime/server (its own routes and Better Auth plugins) |
playground/ | A Nuxt app using the module with every feature on, and mock data |
test/ | API tests, and browser tests in test/browser/ |
docs/ | This site |
The playground's data
On startup, the playground seeds an admin and 40 mock users from playground/.env: join dates over six months,
verified and unverified emails, more admins, bans (permanent, expiring, expired), a moderator
(lucas.haddad@example.com), linked GitHub, Google and Discord accounts, and sessions on different devices. Mock users
share the admin's password.
It only adds users that are missing, so delete playground/.data/auth.sqlite to start over.
Emails and texts don't leave the playground: they go to an outbox, at /api/_playground/outbox and
/api/_playground/sms.
Checks
Run these before opening a pull request; CI runs them too, on Node 22 and 24.
pnpm fmt # Prettier (pnpm fmt:fix to fix)
pnpm typecheck # the module, its server code and the playground
pnpm test # builds everything, then runs the API and browser tests
pnpm test builds the module and the playground, starts the playground once on port 3941 with a fresh database, and
runs every test file against it: the API tests first, then the browser tests.
Browser tests
The browser tests drive Chromium through the pages: signing up and in, links and codes, two-factor, passkeys (with Chromium's virtual authenticator), organizations, single sign-on and the admin. Any console error or warning fails them, so a hydration mismatch does.
They use CHROMIUM_PATH, a system Chromium or Chrome, or Playwright's own (npx playwright install chromium).
Without one, they're skipped.
getByText('Saved', { exact: true }). Nuxt UI repeats a toast's words
for screen readers, so a loose match finds two elements.Adding a feature
Most features are one of Better Auth's plugins. A feature is done when it has:
- A module option in
src/module.ts, off by default, documented with Better Auth's defaults, that adds the plugin to the server and client configs. - Pages that read the set-up at runtime, from the capabilities route, so they also work when an app adds the plugin itself.
- Words in English and Spanish, in
src/runtime/app/i18n/. - A row on the admin's configuration page.
- Tests: an API test of what the server does, and a browser test when there's UI.
- A docs page, or a section on one, in
docs/content/.
Ground rules
- Better Auth does the work. Use its endpoints wherever it has them. The module's own routes are only for what Better Auth has no endpoint for, and each checks the same Better Auth permission the action needs.
- Never change Better Auth's tables or replace its endpoints. The module's own data goes in its own tables, through Better Auth's migrations.
- Rely on documented behaviour. Check Better Auth's docs for the version in use. When something only works through undocumented code (like the OAuth Popup plugin today), say so in the docs.
- No patched dependencies. If a package needs patching to work, we replace it or work without it.
- Server-render pages. Fetch page data with
useFetch, and render dates with<NuxtTime>, so the server and the browser agree.
Commits and pull requests
- Branch from
main, and open your pull request against it. - Write commits as Conventional Commits:
feat(sso): sign in by organization slug,fix(admin): …,docs: …,test(browser): …. - Explain what changed and why in the pull request, and how you tested it.
The docs
pnpm docs:dev # http://localhost:3002
pnpm docs:generate # the static build, as GitHub Pages serves it
Pages are Markdown in docs/content/, with Nuxt UI's prose components. Options
use ::field{name type default}. Pushes to main that change docs/ publish the site.
Screenshots
The screenshots come from the playground, in light and dark. Retake them after changing a page that's pictured:
pnpm build && pnpm dev:build
pnpm docs:screenshots
The script (docs/scripts/screenshots.mjs) starts its own playground on port 3943 with a fresh database, so it never
touches your dev server. It sets up an example organization, then saves docs/public/screenshots/<name>-<light|dark>.webp.
Show one on a page with :screenshot{name="admin-home" alt="…"}.
License
By contributing, you agree that your contributions are licensed under the project's MIT license.