Skip to main content

Creating a database adapter

Auth.js adapters allow you to integrate with any (even multiple) database/back-end service, even if we don't have an official package available yet. (We welcome PRs for new adapters! See the checklist below.)

Auth.js adapters are very flexible, and you can implement only the methods you need, and only create the database tables/columns that are actually going to be used.

An Auth.js adapter is a function that receives an ORM/database client and returns an object with methods (based on the Adapter interface) that interact with the database. The same database Adapter will be compatible with any Auth.js library.

User management

Auth.js differentiates between users and accounts. A user can have multiple accounts. An account is created for each provider type the user signs in with for the first time. For example, if a user signs in with Google and then with Facebook, they will have two accounts, one for each provider. The first provider the user signs in with will also be used to create the user object. See the profile() provider method.

Methods and models

See also: User and Account models.

note

Although Auth.js doesn't require it, for basic display purposes, we recommend adding the following columns to the User table as well: name, email, image. You can configure the columns via the profile() provider method. If you don't need to save these properties, create an empty profile() {} method.

note

Although Auth.js doesn't require it, the Account table typically saves tokens retrieved from the provider. You can configure the columns via the account() provider method. If you don't need to save tokens, create an empty account() {} method.

Database session management

Auth.js can manage sessions in two ways. Learn about them and their advantages and disadvantages at Concepts: Session strategies.

Methods and models

If you want to use database sessions, you will need to implement the following methods:

To add database session management, you will need to expand your database tables/columns as follows:

See also: Session models.

Verification tokens

When you want to support email/passwordless login, Auth.js uses a database to store temporary verification tokens that are tied to a user's email address.

Methods and models

See also: Verification Token models.

Official adapter checklist

info

When all of the below steps are done, you are ready to submit a PR to our repository.

If you created an adapter and want us to distribute it as an official package, please make sure it meets the following requirements. Check out this existing adapter to learn about the package structure, required files, test setup, config, etc.

  1. The Adapter must implement all methods of the Adapter interface

  2. Adapter tests must be included and must pass. Docker is favored over services, to make CI resilient to network errors and to reduce the number of GitHub Action Secrets (which also lets us run these tests in fork PRs)

  3. The Adapter must follow these coding styles

    • Written in TypeScript
    • Passes the linting rules of the monorepo
    • Does not include polyfills
    • Configured as an ES module (ESM)
    • Documented via JSDoc comments
    • Have at least one named export exported from its main module. (For example export function MyAdapter(): Adapter {})
  4. Configure the monorepo to help us maintain the package

  5. The Adapter must be able to handle any property coming from the user

    ORMs/database clients might have their own data types, but Auth.js expects these to be normalized as plain JavaScript objects for consistency. If your ORM/database client does not convert automatically, you need to convert the values when reading/writing from/to the database.

    You might be tempted to check the name of a property and convert it based on that, but this is not scalable (eg.: a User object might have more than one Date property, not only emailVerified).

    Instead, we recommend creating util functions that convert the values. Below is an example of how to convert dates (if your ORM/database client uses other data types, remember to convert them too, not only dates). It checks if the value can be parsed as a date, and if so, it converts it to a Date object. Otherwise, it leaves the original value as is.:

// https://github.com/honeinc/is-iso-date/blob/8831e79b5b5ee615920dcb350a355ffc5cbf7aed/index.js#L5
const isoDateRE =
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/

const isDate = (val: any): val is ConstructorParameters<typeof Date>[0] =>
!!(val && isoDateRE.test(val) && !isNaN(Date.parse(val)))

export const format = {
/** Takes an object that's coming from a database and converts it to plain JavaScript. */
from<T>(object: Record<string, any> = {}): T {
const newObject: Record<string, unknown> = {}
for (const [key, value] of Object.entries(object))
if (isDate(value)) newObject[key] = new Date(value)
else newObject[key] = value
return newObject as T
},
/** Takes an object that's coming from Auth.js and prepares it to be written to the database. */
to<T>(object: Record<string, any>): T {
const newObject: Record<string, unknown> = {}
for (const [key, value] of Object.entries(object))
if (value instanceof Date) newObject[key] = value.toISOString()
else newObject[key] = value
return newObject as T
},
}

Useful resources