Better Auth Credentials Plugin
The Better Auth Credentials Plugin provides a highly customizable mechanism for authenticating users against external systems like LDAP, custom APIs, or other credential-based services, integrating seamlessly with the Better Auth ecosystem. It is currently at version 0.5.2 and appears to have a relatively active release cadence, with several minor releases in recent months addressing bug fixes and compatibility. Key differentiators include full control over the authentication callback logic, optional auto sign-up, management of account linking and session creation, and flexible route customization with Zod schemas for validation and OpenAPI documentation. It's designed to complement, not replace, Better Auth's native email/password or username flows when integrating with third-party authentication sources. The plugin explicitly supports both server and client-side integration.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'internalAdapter')
cause This typically occurs when `better-auth-credentials-plugin` is used with an older version of `better-auth` that does not match the plugin's expected `internalAdapter` API signature.fixUpdate your `better-auth` package to version `^1.5.0` or higher to match the plugin's requirements. Run `npm install better-auth@latest`. -
Error: ZodError: [ { "code": "invalid_type", "expected": "string", "received": "undefined", "path": [ "email" ], "message": "Email is required" } ]cause The credentials `callback` did not return a required `email` field, or the `inputSchema` (if custom) was not satisfied by the incoming credentials.fixEnsure your `callback` function returns an object with at least a valid `email` property. If using a custom `inputSchema`, verify the incoming `parsed` object from the client matches its requirements. -
Module not found: Error: Can't resolve 'better-auth-credentials-plugin'
cause This error, particularly in a client-side bundle, often means the bundler is trying to import the server-side entrypoint or the subpath import is incorrect for the client.fixFor client-side code, ensure you are importing from the `/client` subpath: `import { credentialsClient } from 'better-auth-credentials-plugin/client';`. Also, check that `better-auth-credentials-plugin` is correctly installed. -
TypeError: require is not a function in ES module scope
cause Attempting to use CommonJS `require()` syntax in an ESM project, or a bundler configuration issue where `better-auth-credentials-plugin` is treated as CommonJS.fixEnsure your project is configured for ESM. Use `import { credentials } from 'better-auth-credentials-plugin';` instead of `const credentials = require('better-auth-credentials-plugin');`. If using a bundler, verify its configuration for resolving ESM packages.
Warnings
- breaking The `internalAdapter` signature within Better Auth changed in v1.4.0. Upgrading to `better-auth-credentials-plugin` v0.4.0 and higher requires `better-auth` >=1.4.0 to maintain compatibility.
- gotcha When using bundlers like Webpack, Turbopack, or Vite, the client-side module `credentialsClient` must be imported via the dedicated subpath `better-auth-credentials-plugin/client`. Failing to do so can result in server-side dependencies being bundled into client code, leading to errors or unnecessarily large bundles.
- gotcha The credentials callback *must* return an `email` field as part of the user data. This email is crucial for Better Auth to create or update the user in the database and link the account with the session. If `autoSignUp` is enabled and no `email` is returned, user creation will fail.
- gotcha This plugin is not intended for re-implementing standard email and password login by storing hashed passwords in your database. Using it this way would lead to redundant database lookups and is less efficient than Better Auth's native `emailAndPassword` or `username` plugins.
- gotcha When integrating with Better Auth's default `emailAndPassword` or `username` flows, accounts created via the credentials plugin will not automatically be able to log in with email/password (as no password is set). To allow users to log in with both methods, configure a custom `providerId` for this plugin and set `linkAccountIfExisting: true`.
Install
-
npm install better-auth-credentials-plugin -
yarn add better-auth-credentials-plugin -
pnpm add better-auth-credentials-plugin
Imports
- credentials
const credentials = require('better-auth-credentials-plugin');import { credentials } from 'better-auth-credentials-plugin'; - credentialsClient
import { credentialsClient } from 'better-auth-credentials-plugin';import { credentialsClient } from 'better-auth-credentials-plugin/client'; - defaultCredentialsSchema
import { defaultCredentialsSchema } from 'better-auth-credentials-plugin/client';
Quickstart
import { betterAuth } from "better-auth";
import { credentials } from "better-auth-credentials-plugin";
// --- Server-side configuration (e.g., in auth.ts) ---
export const auth = betterAuth({
// ... other Better Auth configurations ...
// Disable default email and password if this plugin replaces it for a specific flow
emailAndPassword: {
enabled: false,
},
plugins: [
credentials({
autoSignUp: true, // Automatically create a new user if not found
async callback(ctx, parsed) {
// 'parsed' contains the validated input credentials (e.g., username, password)
// Implement your external authentication logic here (e.g., LDAP, external API)
const { email, password } = parsed;
// Example: Authenticating against a hypothetical external API
const externalAuthServiceUrl = process.env.EXTERNAL_AUTH_SERVICE_URL ?? 'http://localhost:4000/login';
const response = await fetch(externalAuthServiceUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
// Handle authentication failure from the external system
const errorData = await response.json().catch(() => ({ message: 'Authentication failed' }));
throw new Error(errorData.message || 'Invalid credentials');
}
const { token, user: apiUser } = await response.json();
// Return user data, including a unique 'email' field, to Better Auth.
// This data will be used to create/update the user and link the account.
return {
// 'email' is mandatory for Better Auth user linking/creation
email: apiUser.email,
// Additional fields to store on the user or account (e.g., token, name)
name: apiUser.name,
externalAuthToken: token,
};
},
}),
],
});
// --- Client-side usage (e.g., in authClient.ts) ---
// Requires 'better-auth/client' and '/client' subpath for credentialsClient
// import { createAuthClient } from 'better-auth/client';
// import { credentialsClient } from 'better-auth-credentials-plugin/client';
// const authClient = createAuthClient({
// baseURL: "/api/auth",
// plugins: [
// // If you customized the path or schema, you'd pass generic types here
// credentialsClient(),
// ],
// });