diff --git a/.github/ISSUE_TEMPLATE/3_bug_adapter.yml b/.github/ISSUE_TEMPLATE/3_bug_adapter.yml
index 46696d17c2..ce3ed08f35 100644
--- a/.github/ISSUE_TEMPLATE/3_bug_adapter.yml
+++ b/.github/ISSUE_TEMPLATE/3_bug_adapter.yml
@@ -35,6 +35,7 @@ body:
- "@auth/mongodb-adapter"
- "@auth/neo4j-adapter"
- "@auth/pg-adapter"
+ - "@auth/pgjs-adapter"
- "@auth/pouchdb-adapter"
- "@auth/prisma-adapter"
- "@auth/sequelize-adapter"
diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml
index 89af700064..72a2d4f09c 100644
--- a/.github/pr-labeler.yml
+++ b/.github/pr-labeler.yml
@@ -19,6 +19,7 @@ mongodb: ["packages/adapter-mongodb/**/*"]
neo4j: ["packages/adapter-neo4j/**/*"]
next-auth: ["packages/next-auth/**/*"]
pg: ["packages/adapter-pg/**/*"]
+pgjs: ["packages/adapter-pgjs/**/*"]
neon: ["packages/adapter-neon/**/*"]
playgrounds: ["apps/playgrounds/**/*"]
pouchdb: ["packages/adapter-pouchdb/**/*"]
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1ee78afa19..8098d21899 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -36,6 +36,7 @@ on:
- "@auth/mongodb-adapter"
- "@auth/neo4j-adapter"
- "@auth/pg-adapter"
+ - "@auth/pgjs-adapter"
- "@auth/pouchdb-adapter"
- "@auth/prisma-adapter"
- "@auth/sequelize-adapter"
diff --git a/docs/pages/data/manifest.json b/docs/pages/data/manifest.json
index 7fef03e029..f32f109798 100644
--- a/docs/pages/data/manifest.json
+++ b/docs/pages/data/manifest.json
@@ -36,6 +36,7 @@
"mongodb": "MongoDB",
"neo4j": "Neo4j",
"pg": "pg",
+ "pgjs": "PostgresJS",
"pouchdb": "PouchDB",
"sequelize": "Sequelize",
"surrealdb": "SurrealDB",
diff --git a/docs/pages/getting-started/adapters/_meta.js b/docs/pages/getting-started/adapters/_meta.js
index 153672f783..f52ebd4f9f 100644
--- a/docs/pages/getting-started/adapters/_meta.js
+++ b/docs/pages/getting-started/adapters/_meta.js
@@ -13,7 +13,8 @@ export default {
mongodb: "MongoDB",
neo4j: "Neo4j",
neon: "Neon",
- pg: "PostgreSQL",
+ pg: "PostgreSQL - PG",
+ pgjs: "PostgreSQL - PostgresJS",
pouchdb: "PouchDB",
prisma: "Prisma",
sequelize: "Sequelize",
diff --git a/docs/pages/getting-started/adapters/pgjs.mdx b/docs/pages/getting-started/adapters/pgjs.mdx
new file mode 100644
index 0000000000..dfc2368c0d
--- /dev/null
+++ b/docs/pages/getting-started/adapters/pgjs.mdx
@@ -0,0 +1,320 @@
+import { Code } from "@/components/Code"
+import { Callout } from "nextra/components"
+
+
+
+# PostgresJS Adapter
+
+## Resources
+
+- [Official PostgreSQL Docs](https://www.postgresql.org/docs/)
+- [PostgresJS Client Docs](https://github.com/porsager/postgres)
+
+## Setup
+
+### Installation
+
+```bash npm2yarn
+npm install @auth/pgjs-adapter postgres
+```
+
+### Environment Variables
+
+```ts
+import postgres from "postgres"
+// Default connection
+const sql = postgres(process.env.PGSQL)
+
+// Connection instance + options. Refer to PostgresJS docs for full info
+const sql = postgres(process.env.PGSQL, {
+ host: "", // Postgres ip address[s] or domain name[s]
+ port: 5432, // Postgres server port[s]
+ database: "", // Name of database to connect to
+ username: "", // Username of database user
+ password: "", // Password
+ max: 20, // max connections
+})
+```
+
+### Configuration
+
+
+
+
+```ts filename="./auth.ts"
+import NextAuth from "next-auth"
+import PostgresJSAdapter from "@auth/pgjs-adapter"
+import postgres from "postgres"
+
+const sql = postgres(process.env.PGSQL, {
+ host: "", // Postgres ip address[s] or domain name[s]
+ port: 5432, // Postgres server port[s]
+ database: "", // Name of database to connect to
+ username: "", // Username of database user
+ password: "", // Password
+ max: 20, // max connections
+})
+
+export const { handlers, auth, signIn, signOut } = NextAuth({
+ adapter: PostgresJSAdapter(sql),
+ providers: [],
+})
+```
+
+
+
+
+```ts filename="/src/routes/plugin@auth.ts"
+import { QwikAuth$ } from "@auth/qwik"
+import PostgresJSAdapter from "@auth/pgjs-adapter"
+import postgres from "postgres"
+
+const sql = postgres(process.env.PGSQL, {
+ host: "", // Postgres ip address[s] or domain name[s]
+ port: 5432, // Postgres server port[s]
+ database: "", // Name of database to connect to
+ username: "", // Username of database user
+ password: "", // Password
+ max: 20, // max connections
+})
+
+export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
+ () => ({
+ providers: [],
+ adapter: PostgresJSAdapter(sql),
+ })
+)
+```
+
+
+
+
+```ts filename="./src/auth.ts"
+import { SvelteKitAuth } from "@auth/sveltekit"
+import PostgresJSAdapter from "@auth/pgjs-adapter"
+import postgres from "postgres"
+
+const sql = postgres(process.env.PGSQL, {
+ host: "", // Postgres ip address[s] or domain name[s]
+ port: 5432, // Postgres server port[s]
+ database: "", // Name of database to connect to
+ username: "", // Username of database user
+ password: "", // Password
+ max: 20, // max connections
+})
+
+export const { handle, signIn, signOut } = SvelteKitAuth({
+ adapter: PostgresJSAdapter(sql),
+ providers: [],
+})
+```
+
+
+
+
+```ts filename="./src/routes/auth.route.ts"
+import { ExpressAuth } from "@auth/express"
+import PostgresJSAdapter from "@auth/pgjs-adapter"
+import postgres from "postgres"
+
+const sql = postgres(process.env.PGSQL, {
+ host: "", // Postgres ip address[s] or domain name[s]
+ port: 5432, // Postgres server port[s]
+ database: "", // Name of database to connect to
+ username: "", // Username of database user
+ password: "", // Password
+ max: 20, // max connections
+})
+
+const app = express()
+
+app.set("trust proxy", true)
+app.use(
+ "/auth/*",
+ ExpressAuth({
+ providers: [],
+ adapter: PostgresJSAdapter(sql),
+ })
+)
+```
+
+
+
+
+### Schema
+
+The SQL schema for the tables used by this adapter is as follows. Learn more about the models at our
+doc page on [Database Models](/guides/creating-a-database-adapter).
+
+
+For `userId`, this schema uses [nanoid](https://github.com/viascom/nanoid-postgres) server-side.
+This is **entirely optional** and merely to showcase an alternative ID implementation.
+
+
+```sql filename="./schema.sql"
+--Credit [Supabase](https://supabase.com/docs/guides/database/postgres/dropping-all-tables-in-schema)
+do $$ declare
+ r record;
+begin
+ for r in (select tablename from pg_tables where schemaname = 'public') loop
+ execute 'drop table if exists ' || quote_ident(r.tablename) || ' cascade';
+ end loop;
+end $$;
+
+--Credit [Nikola Stanković](https://github.com/viascom/nanoid-postgres)
+CREATE EXTENSION IF NOT EXISTS pgcrypto;
+DROP FUNCTION IF EXISTS nanoid(int, text, float);
+CREATE OR REPLACE FUNCTION nanoid(
+ size int DEFAULT 7, -- The number of symbols in the NanoId String. Must be greater than 0.
+ alphabet text DEFAULT '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', -- The symbols used in the NanoId String. Must contain between 1 and 255 symbols.
+ additionalBytesFactor float DEFAULT 1.02 -- The additional bytes factor used for calculating the step size. Must be equal or greater then 1.
+)
+ RETURNS text -- A randomly generated NanoId String
+ LANGUAGE plpgsql
+ VOLATILE
+ PARALLEL SAFE
+ -- LEAKPROOF // This option requires superuser privileges
+
+AS
+$$
+DECLARE
+ alphabetArray text[];
+ alphabetLength int := 64;
+ mask int := 63;
+ step int := 34;
+BEGIN
+ IF size IS NULL OR size < 1 THEN
+ RAISE EXCEPTION 'The size must be defined and greater than 0!';
+ END IF;
+
+ IF alphabet IS NULL OR length(alphabet) = 0 OR length(alphabet) > 255 THEN
+ RAISE EXCEPTION 'The alphabet can''t be undefined, zero or bigger than 255 symbols!';
+ END IF;
+
+ IF additionalBytesFactor IS NULL OR additionalBytesFactor < 1 THEN
+ RAISE EXCEPTION 'The additional bytes factor can''t be less than 1!';
+ END IF;
+
+ alphabetArray := regexp_split_to_array(alphabet, '');
+ alphabetLength := array_length(alphabetArray, 1);
+ mask := (2 << cast(floor(log(alphabetLength - 1) / log(2)) as int)) - 1;
+ step := cast(ceil(additionalBytesFactor * mask * size / alphabetLength) AS int);
+
+ IF step > 1024 THEN
+ step := 1024; -- The step size % can''t be bigger then 1024!
+ END IF;
+
+ RETURN nanoid_optimized(size, alphabet, mask, step);
+END
+$$;
+
+-- Generates an optimized random string of a specified size using the given alphabet, mask, and step.
+-- This optimized version is designed for higher performance and lower memory overhead.
+-- No checks are performed! Use it only if you really know what you are doing.
+DROP FUNCTION IF EXISTS nanoid_optimized(int, text, int, int);
+CREATE OR REPLACE FUNCTION nanoid_optimized(
+ size int, -- The desired length of the generated string.
+ alphabet text, -- The set of characters to choose from for generating the string.
+ mask int, -- The mask used for mapping random bytes to alphabet indices. Should be `(2^n) - 1` where `n` is a power of 2 less than or equal to the alphabet size.
+ step int -- The number of random bytes to generate in each iteration. A larger value may speed up the function but increase memory usage.
+)
+ RETURNS text -- A randomly generated NanoId String
+ LANGUAGE plpgsql
+ VOLATILE
+ PARALLEL SAFE
+ -- LEAKPROOF // This option requires superuser privileges
+AS
+$$
+DECLARE
+ idBuilder text := '';
+ counter int := 0;
+ bytes bytea;
+ alphabetIndex int;
+ alphabetArray text[];
+ alphabetLength int := 64;
+BEGIN
+ alphabetArray := regexp_split_to_array(alphabet, '');
+ alphabetLength := array_length(alphabetArray, 1);
+
+ LOOP
+ bytes := gen_random_bytes(step);
+ FOR counter IN 0..step - 1
+ LOOP
+ alphabetIndex := (get_byte(bytes, counter) & mask) + 1;
+ IF alphabetIndex <= alphabetLength THEN
+ idBuilder := idBuilder || alphabetArray[alphabetIndex];
+ IF length(idBuilder) = size THEN
+ RETURN idBuilder;
+ END IF;
+ END IF;
+ END LOOP;
+ END LOOP;
+END
+$$;
+---
+
+CREATE TABLE users
+(
+ id TEXT NOT NULL DEFAULT nanoid(),
+ ---id TEXT NOT NULL, --- Auth.js default - UUID's generated client-side.
+ ---id SERIAL NOT NULL,--- Auto-Incrementing Ids - Dont forget to change "userId" ForeignKeys to INTEGER
+ name TEXT,
+ email TEXT,
+ "emailVerified" TIMESTAMPTZ,
+ image TEXT,
+
+ PRIMARY KEY(id)
+
+);
+
+CREATE TABLE verification_token
+(
+ identifier TEXT NOT NULL,
+ expires TIMESTAMPTZ NOT NULL,
+ token TEXT NOT NULL,
+
+ PRIMARY KEY (identifier, token)
+);
+
+CREATE TABLE accounts
+(
+ "userId" TEXT NOT NULL REFERENCES users(id) ON UPDATE no action ON DELETE cascade,
+ type TEXT NOT NULL,
+ provider TEXT NOT NULL,
+ "providerAccountId" TEXT NOT NULL,
+ refresh_token TEXT,
+ access_token TEXT,
+ expires_at INT,
+ id_token TEXT,
+ scope TEXT,
+ session_state TEXT,
+ token_type TEXT,
+
+ PRIMARY KEY (provider, "providerAccountId")
+);
+
+CREATE TABLE sessions
+(
+ "userId" TEXT NOT NULL REFERENCES users(id) ON UPDATE no action ON DELETE cascade,
+ expires TIMESTAMPTZ NOT NULL,
+ "sessionToken" TEXT NOT NULL,
+
+ PRIMARY KEY ("sessionToken")
+);
+
+create table authenticators
+(
+id text not null,
+"credentialID" text unique not null,
+"userId" TEXT NOT NULL REFERENCES users(id) ON UPDATE no action ON DELETE cascade,
+"providerAccountId" text not null,
+"credentialPublicKey" text not null,
+counter integer not null,
+"credentialDeviceType" text not null,
+"credentialBackedUp" boolean not null,
+transports text,
+
+primary key (id)
+
+);
+```
diff --git a/docs/public/img/adapters/pgjs.svg b/docs/public/img/adapters/pgjs.svg
new file mode 100644
index 0000000000..f4826dd71b
--- /dev/null
+++ b/docs/public/img/adapters/pgjs.svg
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/packages/adapter-pgjs/README.md b/packages/adapter-pgjs/README.md
new file mode 100755
index 0000000000..5952ae8713
--- /dev/null
+++ b/packages/adapter-pgjs/README.md
@@ -0,0 +1,28 @@
+
+
+
+---
+
+Check out the documentation at [authjs.dev](https://authjs.dev/reference/adapter/pgjs).
diff --git a/packages/adapter-pgjs/package.json b/packages/adapter-pgjs/package.json
new file mode 100755
index 0000000000..c186a43083
--- /dev/null
+++ b/packages/adapter-pgjs/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "@auth/pgjs-adapter",
+ "version": "2.10.0",
+ "description": "PostgresJS adapter for next-auth.",
+ "homepage": "https://authjs.dev/reference/adapter/pgjs",
+ "repository": "https://github.com/nextauthjs/next-auth",
+ "bugs": {"url": "https://github.com/nextauthjs/next-auth/issues"},
+ "author": "Mustaqim Arifin ",
+ "license": "ISC",
+ "keywords": [
+ "next-auth", "@auth" , "Auth.js" , "next.js" , "oauth" , "postgres"
+ ],
+ "type": "module",
+ "exports": { ".": {"types": "./index.d.ts", "import": "./index.js"} },
+ "files": ["*.d.ts*", "*.js", "src"],
+ "private": false,
+ "publishConfig": {"access": "public"},
+ "scripts": {"test": "./test/test.sh", "build": "tsc"},
+ "dependencies": {"@auth/core": "workspace:*"},
+ "peerDependencies": {"postgres": "^3"},
+ "devDependencies": {"postgres": "3.4.7"}
+}
diff --git a/packages/adapter-pgjs/schema.sql b/packages/adapter-pgjs/schema.sql
new file mode 100755
index 0000000000..ffb44397b3
--- /dev/null
+++ b/packages/adapter-pgjs/schema.sql
@@ -0,0 +1,162 @@
+\set ON_ERROR_STOP true
+
+--- Drop All Tables
+--- Credit [Supabase](https://supabase.com/docs/guides/database/postgres/dropping-all-tables-in-schema)
+
+do $$ declare
+ r record;
+begin
+ for r in (select tablename from pg_tables where schemaname = 'public') loop
+ execute 'drop table if exists ' || quote_ident(r.tablename) || ' cascade';
+ end loop;
+end $$;
+
+--- PG-NANOID [Optional ID generation server-side]
+--- Credit [Nikola Stanković](https://github.com/viascom/nanoid-postgres)
+
+CREATE EXTENSION IF NOT EXISTS pgcrypto;
+
+DROP FUNCTION IF EXISTS nanoid(int, text, float);
+
+CREATE OR REPLACE FUNCTION nanoid(
+ size int DEFAULT 7, -- The number of symbols in the NanoId String. Must be greater than 0.
+ alphabet text DEFAULT '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', -- The symbols used in the NanoId String. Must contain between 1 and 255 symbols.
+ additionalBytesFactor float DEFAULT 1.02 -- The additional bytes factor used for calculating the step size. Must be equal or greater then 1.
+)
+ RETURNS text -- A randomly generated NanoId String
+ LANGUAGE plpgsql
+ VOLATILE
+ PARALLEL SAFE
+--- Uncomment the following line if you have superuser privileges
+--- LEAKPROOF
+AS
+$$
+DECLARE
+ alphabetArray text[];
+ alphabetLength int := 64;
+ mask int := 63;
+ step int := 34;
+BEGIN
+ IF size IS NULL OR size < 1 THEN
+ RAISE EXCEPTION 'The size must be defined and greater than 0!';
+ END IF;
+
+ IF alphabet IS NULL OR length(alphabet) = 0 OR length(alphabet) > 255 THEN
+ RAISE EXCEPTION 'The alphabet can''t be undefined, zero or bigger than 255 symbols!';
+ END IF;
+
+ IF additionalBytesFactor IS NULL OR additionalBytesFactor < 1 THEN
+ RAISE EXCEPTION 'The additional bytes factor can''t be less than 1!';
+ END IF;
+
+ alphabetArray := regexp_split_to_array(alphabet, '');
+ alphabetLength := array_length(alphabetArray, 1);
+ mask := (2 << cast(floor(log(alphabetLength - 1) / log(2)) as int)) - 1;
+ step := cast(ceil(additionalBytesFactor * mask * size / alphabetLength) AS int);
+
+ IF step > 1024 THEN
+ step := 1024; -- The step size % can''t be bigger then 1024!
+ END IF;
+
+ RETURN nanoid_optimized(size, alphabet, mask, step);
+END
+$$;
+
+-- Generates an optimized random string of a specified size using the given alphabet, mask, and step.
+-- This optimized version is designed for higher performance and lower memory overhead.
+-- No checks are performed! Use it only if you really know what you are doing.
+DROP FUNCTION IF EXISTS nanoid_optimized (int, text, int, int);
+
+CREATE OR REPLACE FUNCTION nanoid_optimized(
+ size int, -- The desired length of the generated string.
+ alphabet text, -- The set of characters to choose from for generating the string.
+ mask int, -- The mask used for mapping random bytes to alphabet indices. Should be `(2^n) - 1` where `n` is a power of 2 less than or equal to the alphabet size.
+ step int -- The number of random bytes to generate in each iteration. A larger value may speed up the function but increase memory usage.
+)
+ RETURNS text -- A randomly generated NanoId String
+ LANGUAGE plpgsql
+ VOLATILE
+ PARALLEL SAFE
+ -- Uncomment the following line if you have superuser privileges
+ --LEAKPROOF
+AS
+$$
+DECLARE
+ idBuilder text := '';
+ counter int := 0;
+ bytes bytea;
+ alphabetIndex int;
+ alphabetArray text[];
+ alphabetLength int := 64;
+BEGIN
+ alphabetArray := regexp_split_to_array(alphabet, '');
+ alphabetLength := array_length(alphabetArray, 1);
+
+ LOOP
+ bytes := gen_random_bytes(step);
+ FOR counter IN 0..step - 1
+ LOOP
+ alphabetIndex := (get_byte(bytes, counter) & mask) + 1;
+ IF alphabetIndex <= alphabetLength THEN
+ idBuilder := idBuilder || alphabetArray[alphabetIndex];
+ IF length(idBuilder) = size THEN
+ RETURN idBuilder;
+ END IF;
+ END IF;
+ END LOOP;
+ END LOOP;
+END
+$$;
+
+-- SCHEMA
+
+CREATE TABLE users (
+ id TEXT NOT NULL DEFAULT nanoid (),
+ name TEXT,
+ email TEXT,
+ "emailVerified" TIMESTAMPTZ,
+ image TEXT,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE verification_token (
+ identifier TEXT NOT NULL,
+ expires TIMESTAMPTZ NOT NULL,
+ token TEXT NOT NULL,
+ PRIMARY KEY (identifier, token)
+);
+
+CREATE TABLE accounts (
+ "userId" TEXT NOT NULL REFERENCES users (id) ON DELETE cascade,
+ type TEXT NOT NULL,
+ provider TEXT NOT NULL,
+ "providerAccountId" TEXT NOT NULL,
+ refresh_token TEXT,
+ access_token TEXT,
+ expires_at INT,
+ id_token TEXT,
+ scope TEXT,
+ session_state TEXT,
+ token_type TEXT,
+ PRIMARY KEY (provider, "providerAccountId")
+);
+
+CREATE TABLE sessions (
+ "userId" TEXT NOT NULL REFERENCES users (id) ON DELETE cascade,
+ expires TIMESTAMPTZ NOT NULL,
+ "sessionToken" TEXT NOT NULL,
+ PRIMARY KEY ("sessionToken")
+);
+
+create table authenticators (
+ "credentialID" text unique not null,
+ "userId" TEXT NOT NULL,
+ "providerAccountId" text not null,
+ "credentialPublicKey" text not null,
+ counter integer not null,
+ "credentialDeviceType" text not null,
+ "credentialBackedUp" boolean not null,
+ transports text,
+ PRIMARY KEY ("userId", "credentialID"),
+ CONSTRAINT "auth_user_fkey" FOREIGN KEY ("userId") REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE
+);
\ No newline at end of file
diff --git a/packages/adapter-pgjs/src/index.ts b/packages/adapter-pgjs/src/index.ts
new file mode 100755
index 0000000000..3c13440208
--- /dev/null
+++ b/packages/adapter-pgjs/src/index.ts
@@ -0,0 +1,214 @@
+/**
+ *
+ *
An official PostgreSQL adapter for Auth.js / NextAuth.js.