adminClient() causes build failure due to dns module when used in client-side code #1618

Closed
opened 2026-03-13 08:51:44 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @Bruh-Codes on GitHub (Aug 1, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

visit the docs and use the admin plugin with the configurations https://www.better-auth.com/docs/plugins/admin

Current vs. Expected behavior

When using adminClient() from better-auth/client/plugins as shown in the [official documentation], a build error occurs in Next.js when the authClient is used in client-side code.

This happens even when using better-auth/client/plugins, which suggests it's intended for browser usage.

Module not found: Can't resolve 'dns'

./node_modules/pg/lib/connection-parameters.js (3:13)

Module not found: Can't resolve 'dns'
  1 | 'use strict'
  2 |
> 3 | const dns = require('dns')
    |             ^^^^^^^^^^^^^^
  4 |
  5 | const defaults = require('./defaults')

What version of Better Auth are you using?

^1.2.12

Provide environment information

- OS:Windows 11,
- Browser: Chrome, *

Which area(s) are affected? (Select all that apply)

Client, Package, Other

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
});import EmailTemplate from "@/components/email-template";
import { betterAuth, string } from "better-auth";
import { magicLink } from "better-auth/plugins/magic-link";
import { Pool } from "pg";
import { Resend } from "resend";
import { admin } from "better-auth/plugins";
import { ac, roles } from "@/lib/permissions";
const resend = new Resend(process.env.RESEND_API_KEY);

if (!process.env.RESEND_API_KEY) {
	throw new Error("RESEND_API_KEY is not set");
}

export enum UserRoles {
	admin = "admin",
	partner = "partner",
	creator = "creators",
	user = "user",
}

export const auth = betterAuth({
	user: {
		additionalFields: {
			OCId: {
				type: "string",
				input: false,
			},
			ethAddress: {
				type: "string",
				input: false,
			},
			role: {
				type: ["admin", "partner", "creator", "user"],
				input: false,
			},
		},
	},

	session: {
		additionalFields: {
			OCId: {
				type: "string",
			},
			ethAddress: {
				type: "string",
			},
			role: {
				type: "string",
			},
		},
	},
	account: {
		accountLinking: {
			enabled: true,
			trustedProviders: ["google", "twitter"],
		},
	},
	rateLimit: {
		enabled: true,
		window: 60, // 1 minute
		max: 3, // Max 3 requests per minute
		storage: "database",
		customRules: {
			"/sign-in/magic-link": {
				window: 60,
				max: 3,
			},
		},
	},
	trustedOrigins: ["http://localhost:3000"],
	socialProviders: {
		google: {
			clientId: process.env.GOOGLE_CLIENT_ID!,
			clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
		},
		twitter: {
			clientId: process.env.TWITTER_CLIENT_ID!,
			clientSecret: process.env.TWITTER_CLIENT_SECRET!,
		},
	},
	advanced: {
		ipAddress: {
			ipAddressHeaders: ["x-forwarded-for", "x-real-ip"],
			disableIpTracking: false,
		},
	},
	plugins: [
		magicLink({
			async sendMagicLink({ email, url }) {
				const { error } = await resend.emails.send({
					//
				});
				if (error) console.log(error);
			},
		}),
		admin({
			defaultRole: UserRoles.user,
			adminRoles: [UserRoles.admin, UserRoles.creator, UserRoles.partner],
			ac,
			roles,
		}),
	],
	/** if no database is provided, the user data will be stored in memory.
	 * Make sure to provide a database to persist user data **/
	database: new Pool({
		connectionString: process.env.DATABASE_URL,
	}),
});

Additional context

this is my role configuration

import { adminAc, defaultStatements } from "better-auth/plugins/admin/access";
import { createAccessControl } from "better-auth/plugins/access";
import { UserRoles } from "./auth";

/**
 * make sure to use `as const` so typescript can infer the type correctly
 */
const statement = {
	...defaultStatements,
	course: ["create", "read", "update", "delete", "update:own", "delete:own"],
} as const;

export const ac = createAccessControl(statement);

export const roles = {
	[UserRoles?.user]: ac.newRole({
		course: ["read"],
	}),

	[UserRoles?.partner]: ac.newRole({
		course: ["read", "create", "delete:own", "update:own"],
	}),
	[UserRoles?.creator]: ac.newRole({
		course: ["read", "create", "delete:own", "update:own"],
	}),
	[UserRoles?.admin]: ac.newRole({
		...adminAc.statements,
		course: [...statement.course],
	}),
};

when i pass "ac and roles" configurations to the adminClient that is when i get the error

adminClient({
   ac,
   roles,
}),
Originally created by @Bruh-Codes on GitHub (Aug 1, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce visit the docs and use the admin plugin with the configurations https://www.better-auth.com/docs/plugins/admin ### Current vs. Expected behavior When using adminClient() from better-auth/client/plugins as shown in the [[official documentation](https://www.better-auth.com/docs/plugins/admin)], a build error occurs in Next.js when the authClient is used in client-side code. This happens even when using better-auth/client/plugins, which suggests it's intended for browser usage. ```ts Module not found: Can't resolve 'dns' ./node_modules/pg/lib/connection-parameters.js (3:13) Module not found: Can't resolve 'dns' 1 | 'use strict' 2 | > 3 | const dns = require('dns') | ^^^^^^^^^^^^^^ 4 | 5 | const defaults = require('./defaults') ``` ### What version of Better Auth are you using? ^1.2.12 ### Provide environment information ```bash - OS:Windows 11, - Browser: Chrome, * ``` ### Which area(s) are affected? (Select all that apply) Client, Package, Other ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, });import EmailTemplate from "@/components/email-template"; import { betterAuth, string } from "better-auth"; import { magicLink } from "better-auth/plugins/magic-link"; import { Pool } from "pg"; import { Resend } from "resend"; import { admin } from "better-auth/plugins"; import { ac, roles } from "@/lib/permissions"; const resend = new Resend(process.env.RESEND_API_KEY); if (!process.env.RESEND_API_KEY) { throw new Error("RESEND_API_KEY is not set"); } export enum UserRoles { admin = "admin", partner = "partner", creator = "creators", user = "user", } export const auth = betterAuth({ user: { additionalFields: { OCId: { type: "string", input: false, }, ethAddress: { type: "string", input: false, }, role: { type: ["admin", "partner", "creator", "user"], input: false, }, }, }, session: { additionalFields: { OCId: { type: "string", }, ethAddress: { type: "string", }, role: { type: "string", }, }, }, account: { accountLinking: { enabled: true, trustedProviders: ["google", "twitter"], }, }, rateLimit: { enabled: true, window: 60, // 1 minute max: 3, // Max 3 requests per minute storage: "database", customRules: { "/sign-in/magic-link": { window: 60, max: 3, }, }, }, trustedOrigins: ["http://localhost:3000"], socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }, twitter: { clientId: process.env.TWITTER_CLIENT_ID!, clientSecret: process.env.TWITTER_CLIENT_SECRET!, }, }, advanced: { ipAddress: { ipAddressHeaders: ["x-forwarded-for", "x-real-ip"], disableIpTracking: false, }, }, plugins: [ magicLink({ async sendMagicLink({ email, url }) { const { error } = await resend.emails.send({ // }); if (error) console.log(error); }, }), admin({ defaultRole: UserRoles.user, adminRoles: [UserRoles.admin, UserRoles.creator, UserRoles.partner], ac, roles, }), ], /** if no database is provided, the user data will be stored in memory. * Make sure to provide a database to persist user data **/ database: new Pool({ connectionString: process.env.DATABASE_URL, }), }); ``` ### Additional context this is my role configuration ```ts import { adminAc, defaultStatements } from "better-auth/plugins/admin/access"; import { createAccessControl } from "better-auth/plugins/access"; import { UserRoles } from "./auth"; /** * make sure to use `as const` so typescript can infer the type correctly */ const statement = { ...defaultStatements, course: ["create", "read", "update", "delete", "update:own", "delete:own"], } as const; export const ac = createAccessControl(statement); export const roles = { [UserRoles?.user]: ac.newRole({ course: ["read"], }), [UserRoles?.partner]: ac.newRole({ course: ["read", "create", "delete:own", "update:own"], }), [UserRoles?.creator]: ac.newRole({ course: ["read", "create", "delete:own", "update:own"], }), [UserRoles?.admin]: ac.newRole({ ...adminAc.statements, course: [...statement.course], }), }; ``` when i pass **"ac and roles"** configurations to the adminClient that is when i get the error ```ts adminClient({ ac, roles, }), ```
GiteaMirror added the plugin label 2026-03-13 08:51:44 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Aug 1, 2025):

The build error happens because server-only modules like pg (which depends on Node.js's dns module) are being imported into your client-side bundle. This usually occurs when objects like ac or roles—which are defined or imported alongside server-only code (such as your database config)—are passed to adminClient() in client code. Even if you use better-auth/client/plugins, if the objects you pass reference server-only modules, the build will fail in Next.js because those modules aren't available in the browser [source].

To fix this, make sure that any code or objects you use in client-side files (including ac and roles for adminClient) are defined in files that do not import or reference server-only modules like pg, Pool, or your database config. Keep all database and server-only logic in server-only files (for example, in /lib/server or /api), and only import client-safe code in your client components or pages [source].

If you need to share types or permission structures between client and server, put them in a separate file that does not import any server-only code. The Better Auth Next.js demo shows this separation in practice.

In summary: never import or reference anything that touches your database or Node.js-only modules in client-side code, even indirectly through objects like ac or roles. This will resolve the build error.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Aug 1, 2025): <!-- Answer --> The build error happens because server-only modules like `pg` (which depends on Node.js's `dns` module) are being imported into your client-side bundle. This usually occurs when objects like `ac` or `roles`—which are defined or imported alongside server-only code (such as your database config)—are passed to `adminClient()` in client code. Even if you use `better-auth/client/plugins`, if the objects you pass reference server-only modules, the build will fail in Next.js because those modules aren't available in the browser [[source]](https://github.com/better-auth/better-auth/issues/3136). To fix this, make sure that any code or objects you use in client-side files (including `ac` and `roles` for `adminClient`) are defined in files that do not import or reference server-only modules like `pg`, `Pool`, or your database config. Keep all database and server-only logic in server-only files (for example, in `/lib/server` or `/api`), and only import client-safe code in your client components or pages [[source]](https://github.com/better-auth/better-auth/issues/3498). If you need to share types or permission structures between client and server, put them in a separate file that does not import any server-only code. The [Better Auth Next.js demo](https://github.com/better-auth/better-auth/blob/2fe1be0e5f023be77b07206dd960f9fd38abba8c/demo/nextjs/app/api/auth/[...all]/route.ts) shows this separation in practice. In summary: never import or reference anything that touches your database or Node.js-only modules in client-side code, even indirectly through objects like `ac` or `roles`. This will resolve the build error. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/7eebe819-c6ba-43f3-9864-f400747081a4?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/7eebe819-c6ba-43f3-9864-f400747081a4?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/7eebe819-c6ba-43f3-9864-f400747081a4?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/7eebe819-c6ba-43f3-9864-f400747081a4?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/7eebe819-c6ba-43f3-9864-f400747081a4?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/7eebe819-c6ba-43f3-9864-f400747081a4?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/7eebe819-c6ba-43f3-9864-f400747081a4?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/3735)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1618