mastra-ai/mastra · error

MastraAuthBetterAuth needs a database to build its own bette

Error message

MastraAuthBetterAuth needs a database to build its own better-auth instance, but the host passed none. Use a storage backend that exposes an auth database, or pass your own configured `auth` instance.

What it means

In deferred mode (constructed with `secret`, no `auth`), init() builds a Better Auth instance on a database supplied by the host via AuthInitContext.database. If the host passes no database (ctx.database is undefined), Better Auth cannot be constructed, so init() throws. Bring-your-own instances return early and never hit this path.

Source

Thrown at auth/better-auth/src/index.ts:280

  // IAuthInit implementation
  // ============================================

  /**
   * One-time host initialization. In deferred instance mode (no `auth` in the
   * options) this builds the provider-owned `betterAuth()` instance on the
   * host's auth database; schema migrations then run lazily behind a
   * once-per-process latch on first use.
   *
   * Bring-your-own `auth` instances skip construction entirely — the caller
   * owns their database and migrations.
   */
  async init(ctx: AuthInitContext): Promise<void> {
    this.#crossSite = (ctx.allowedOrigins?.length ?? 0) > 0;
    if (this.#auth) return; // bring-your-own instance: nothing to build

    const authDb = ctx.database as TaggedAuthDatabase | undefined;
    if (!authDb) {
      throw new Error(
        'MastraAuthBetterAuth needs a database to build its own better-auth instance, but the host passed none. ' +
          'Use a storage backend that exposes an auth database, or pass your own configured `auth` instance.',
      );
    }
    // Map the host's tagged auth-database handle onto better-auth's `database`
    // option: pg pool directly, libsql via its kysely dialect, anything else
    // passed through as-is (the host owns its compatibility).
    const database: BetterAuthOptions['database'] =
      authDb.dialect === 'postgres'
        ? (authDb.pool as Extract<BetterAuthOptions['database'], { query: unknown }>)
        : authDb.dialect === 'libsql'
          ? {
              dialect: new LibsqlDialect({ client: authDb.client } as ConstructorParameters<typeof LibsqlDialect>[0]),
              type: 'sqlite' as const,
            }
          : (authDb.database as BetterAuthOptions['database']);
    const allowedOrigins = ctx.allowedOrigins ?? [];
    // Widen to BetterAuthOptions before calling betterAuth(): its return type

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Pass your own configured Better Auth instance so no host database is needed: new MastraAuthBetterAuth({ auth: betterAuth({...}) }).
  2. Configure a storage backend that exposes an auth database (e.g. Postgres/LibSQL backed storage) so ctx.database is populated.
  3. Check that your server storage config is actually applied to the auth provider's init context.

Example fix

// before
const auth = new MastraAuthBetterAuth({ secret: process.env.BETTER_AUTH_SECRET });
// server has no storage -> init() has no database

// after
const auth = new MastraAuthBetterAuth({
  auth: betterAuth({ database: pgPool, secret: process.env.BETTER_AUTH_SECRET }),
});
// or configure Postgres storage on the server so init() receives ctx.database
Defensive patterns

Strategy: validation

Validate before calling

if (!authDb) {
  throw new Error('Storage backend does not expose an auth database; pass auth: betterAuth({...}) to MastraAuthBetterAuth instead of secret-only mode');
}
await provider.init(ctx);

Type guard

function isTaggedAuthDatabase(db: unknown): db is TaggedAuthDatabase {
  return typeof db === 'object' && db !== null && ('pool' in db || 'url' in db || 'dialect' in db);
}

Try / catch

try {
  await provider.init(ctx);
} catch (e) {
  if (e instanceof Error && e.message.includes('needs a database')) {
    throw new Error('Configure Postgres/LibSQL storage on the server or supply your own betterAuth instance', { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: async init(ctx) runs with this.#auth unset and `ctx.database as TaggedAuthDatabase` undefined — i.e. host storage backend does not expose a tagged auth database while the provider was created with only a secret.

Common situations: Using a storage backend that doesn't expose an auth database (e.g. certain in-memory or non-SQL storage adapters); upgrading Mastra core where the host no longer passes ctx.database; forgetting the storage/database configuration entirely in server options.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/b2357f241747af47. Report an issue: GitHub.