draft v02026-10-20·baseline better-auth ^1.7.7

Conventions

The rules that let BYO packages work as one system instead of a pile of plugins.

One identity, one permission model, one way to run background work, one UI style. Part 1 is required today. Its rules need plain better-auth primitives plus two shared packages, @byo-oss/core and @byo-oss/mailer. Part 2 holds proposals that aren't rules yet.

[00] tiers

Plugin first. App when you must.

A package is an app only when it needs something a plugin can't give it: its own route tree or a separate database. Inbound webhooks stay plugins.

Plugin

default

A better-auth plugin. Tables come from the plugin schema, endpoints from createAuthEndpoint.

auth.ts
betterAuth({ plugins: [inbox()] })

App

none yet

A larger product with its own migrations and request handler, mapped to better-auth through an identify(request) adapter.

app.ts
identify(request) // → user, orgs, permissions
[01] part 1 · required

Eleven rules every package follows.

  1. 01Naming and packagingThe @byo-oss scope, one package per domain, subpath exports for client, react, providers and data a browser can load. Tables that depend on options are documented as such.
  2. 02Plugin shapeThe plugin id doubles as the endpoint prefix. Renamable schema, typed error codes, misconfiguration fails at boot, and queries that hold on every adapter, D1 included.
  3. 03IdentityPeople are better-auth users, tenants are organizations. No package keeps its own user table.
  4. 04Access and permissionsEvery endpoint is server-only, own-rows, permission-gated or source-authenticated (a webhook checks the sender, never acts as a user). Packages check permissions, never role names.
  5. 05Background workOne sweep endpoint per package, called from any scheduler. Leased claims, backoff, at-least-once.
  6. 06Data lifecycleDeleting a user, an organization or a membership removes what the package holds about them, on adapters with or without foreign keys.
  7. 07UIHeadless hooks first. Components on shadcn variables, keyboard-ready, every string overridable.
  8. 08Docs and testsOne README outline, an AGENTS.md for coding agents, code samples that compile, tests against a real better-auth instance, strict semver.
  9. 09Exit pathAn "Outgrowing this" section, a documented schema, a data export paged on a unique key, and uninstall steps.
  10. 10ProvidersOutside services plug in at the edges behind small interfaces. The package's own models stay the store of record.
  11. 11Shared packagesSweeps and cleanups declared under a byo key for @byo-oss/core. Email through a Mailer from @byo-oss/mailer. Typed events through onEvent, fired after the write commits.
[02] example · §4.2

Permissions, not role names.

A package exports its permission statements. Your app spreads them into its catalog and decides which roles get which actions.

access.ts
import { createAccessControl } from "better-auth/plugins/access";
import { defaultStatements } from "better-auth/plugins/organization/access";

export const statements = {
  ...defaultStatements,
  ...feedbackStatements, // exported by the package
  project: ["create", "update"],
} as const;

export const ac = createAccessControl(statements);
[03] part 2 · proposed

The glue, once two packages need it.

Proposals, not rules. Each one stays here until it's promoted, and nothing in Part 1 depends on them.

P2
Notify through the inbox
P3
@byo-oss/identity
P6
Agent-native operations
P7
Preferences
P8
Bridges for the move out

>_ keywords

MUST, SHOULD andMAY mean what RFC 2119 says. A rule marked (plugin) applies to plugins only; everything else applies to both tiers.