[GH-ISSUE #2496] banExpires incorrectly specified in documentation #26550

Closed
opened 2026-04-17 17:10:58 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @WorldOfMaze on GitHub (Apr 30, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/2496

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Modify the user table using drizzle by adding the required columns
role: text('role').default('user'),
banned: boolean('banned').default(false),
banReason: text('ban_reason'),
banExpires: numeric('ban_expires'),
  1. Call ban user using the example in the documentation.
const bannedUser = await authClient.admin.banUser({
  userId: "user_id_here",
  banReason: "Spamming", // Optional (if not provided, the default ban reason will be used - No reason)
  banExpiresIn: 60 * 60 * 24 * 7, // Optional (if not provided, the ban will never expire)
});

Current vs. Expected behavior

Current Results

# SERVER_ERROR:  [Error [NeonDbError]: invalid input syntax for type numeric: "2025-05-07T12:05:37.384-07:00"] {
  severity: 'ERROR',
  code: '22P02',
  detail: undefined,
  hint: undefined,
  position: undefined,
  internalPosition: undefined,
  internalQuery: undefined,
  where: "unnamed portal parameter $3 = '...'",
  schema: undefined,
  table: undefined,
  column: undefined,
  dataType: undefined,
  constraint: undefined,
  file: 'numeric.c',
  line: '800',
  routine: 'numeric_in',
  sourceError: undefined
}
 POST /api/auth/admin/ban-user 500 in 529ms

Expected Results

Database updated

Solution

Change the Expires column type to test

What version of Better Auth are you using?

1.2.7

Provide environment information

- OS: Windows 11
- Browser: Vivaldi

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

Documentation

Auth config (if applicable)

import { db } from '@/db/lib'; // Your Drizzle ORM instance
import * as schema from '@/db/schema';
import { sendEmail } from '@/lib//email';
import { type BetterAuthOptions, betterAuth } from 'better-auth';
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { admin, openAPI } from 'better-auth/plugins';
import siteSettings from 'config/site.config';

type Session = Awaited<ReturnType<typeof auth.api.getSession>>;

const auth = betterAuth({
	database: drizzleAdapter(db, {
		provider: 'pg',
		schema,
	}),
	session: {
		expiresIn: 60 * 60 * 24 * siteSettings.authSettings.sessionExpiresIn || 1,
		updateAge:
			60 * 60 * siteSettings.authSettings.sessionExpirationUpdateFrequency || 1,
		cookieCache: {
			enabled: true,
			maxAge: siteSettings.authSettings.cookieCacheMaxAge || 1,
		},
	},
	socialProviders: {
		google: {
			enabled: true,
			clientId: process.env.GOOGLE_CLIENT_ID as string,
			clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
		},
	},
	plugins: [
		openAPI(),
		admin({
			impersonationSessionDuration:
				siteSettings.authSettings.impersonationSessionDuration || 60 * 60,
			defaultBanReason: 'No reason provided',
			bannedUserMessage:
				'You are not able to use the World of Maze at this time.  Please contact Steve for more information.',
		}),
	], // api/auth/reference
	emailAndPassword: {
		enabled: true,
		requireEmailVerification: true,
		sendResetPassword: async ({ user, url, token }, request) => {
			// TODO: remove console logs after enabling email capabilities
			console.info(`Password Reset URL: ${url}`);
			console.info({ token });

			await sendEmail({
				to: user.email,
				subject: 'Reset your password',
				text: `Please click the following link to reset your password: ${url}`,
			});
		},
	},
	trustedOrigins: ['http://localhost:3000', 'http://192.168.1.215:3000'],
	emailVerification: {
		sendOnSignUp: true,
		autoSignInAfterVerification: true,
		sendVerificationEmail: async ({ user, token }) => {
			const verificationUrl = `${process.env.BETTER_AUTH_URL}/api/auth/verify-email?token=${token}&callback=${process.env.BETTER_AUTH_EMAIL_VERIFICATION_CALLBACK_URL}`;
			await sendEmail({
				to: user.email,
				subject: 'Please verify your email address',
				text: `Please click the following link to verify your email address: ${verificationUrl}`,
			});
		},
	},
} satisfies BetterAuthOptions);

export { auth, type Session };

Additional context

No response

Originally created by @WorldOfMaze on GitHub (Apr 30, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/2496 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Modify the user table using drizzle by adding the required columns ``` role: text('role').default('user'), banned: boolean('banned').default(false), banReason: text('ban_reason'), banExpires: numeric('ban_expires'), ``` 2. Call ban user using the example in the documentation. ``` const bannedUser = await authClient.admin.banUser({ userId: "user_id_here", banReason: "Spamming", // Optional (if not provided, the default ban reason will be used - No reason) banExpiresIn: 60 * 60 * 24 * 7, // Optional (if not provided, the ban will never expire) }); ``` ### Current vs. Expected behavior # Current Results # ``` # SERVER_ERROR: [Error [NeonDbError]: invalid input syntax for type numeric: "2025-05-07T12:05:37.384-07:00"] { severity: 'ERROR', code: '22P02', detail: undefined, hint: undefined, position: undefined, internalPosition: undefined, internalQuery: undefined, where: "unnamed portal parameter $3 = '...'", schema: undefined, table: undefined, column: undefined, dataType: undefined, constraint: undefined, file: 'numeric.c', line: '800', routine: 'numeric_in', sourceError: undefined } POST /api/auth/admin/ban-user 500 in 529ms ``` # Expected Results # Database updated # Solution # Change the Expires column type to `test` ### What version of Better Auth are you using? 1.2.7 ### Provide environment information ```bash - OS: Windows 11 - Browser: Vivaldi ``` ### Which area(s) are affected? (Select all that apply) Documentation ### Auth config (if applicable) ```typescript import { db } from '@/db/lib'; // Your Drizzle ORM instance import * as schema from '@/db/schema'; import { sendEmail } from '@/lib//email'; import { type BetterAuthOptions, betterAuth } from 'better-auth'; import { drizzleAdapter } from 'better-auth/adapters/drizzle'; import { admin, openAPI } from 'better-auth/plugins'; import siteSettings from 'config/site.config'; type Session = Awaited<ReturnType<typeof auth.api.getSession>>; const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg', schema, }), session: { expiresIn: 60 * 60 * 24 * siteSettings.authSettings.sessionExpiresIn || 1, updateAge: 60 * 60 * siteSettings.authSettings.sessionExpirationUpdateFrequency || 1, cookieCache: { enabled: true, maxAge: siteSettings.authSettings.cookieCacheMaxAge || 1, }, }, socialProviders: { google: { enabled: true, clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, }, }, plugins: [ openAPI(), admin({ impersonationSessionDuration: siteSettings.authSettings.impersonationSessionDuration || 60 * 60, defaultBanReason: 'No reason provided', bannedUserMessage: 'You are not able to use the World of Maze at this time. Please contact Steve for more information.', }), ], // api/auth/reference emailAndPassword: { enabled: true, requireEmailVerification: true, sendResetPassword: async ({ user, url, token }, request) => { // TODO: remove console logs after enabling email capabilities console.info(`Password Reset URL: ${url}`); console.info({ token }); await sendEmail({ to: user.email, subject: 'Reset your password', text: `Please click the following link to reset your password: ${url}`, }); }, }, trustedOrigins: ['http://localhost:3000', 'http://192.168.1.215:3000'], emailVerification: { sendOnSignUp: true, autoSignInAfterVerification: true, sendVerificationEmail: async ({ user, token }) => { const verificationUrl = `${process.env.BETTER_AUTH_URL}/api/auth/verify-email?token=${token}&callback=${process.env.BETTER_AUTH_EMAIL_VERIFICATION_CALLBACK_URL}`; await sendEmail({ to: user.email, subject: 'Please verify your email address', text: `Please click the following link to verify your email address: ${verificationUrl}`, }); }, }, } satisfies BetterAuthOptions); export { auth, type Session }; ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-17 17:10:58 -05:00
Author
Owner

@Kinfe123 commented on GitHub (Apr 30, 2025):

banExpires is the field with the date type, while banExpiresIn is a number representing the duration before it expires.

<!-- gh-comment-id:2843603604 --> @Kinfe123 commented on GitHub (Apr 30, 2025): `banExpires` is the field with the date type, while `banExpiresIn` is a number representing the duration before it expires.
Author
Owner

@WorldOfMaze commented on GitHub (May 1, 2025):

The documentation for the Amin plugin specific that the banExpires column should be a number which is incorrect.

Image

The documentation should be updated so that the column type is correct.

<!-- gh-comment-id:2843974442 --> @WorldOfMaze commented on GitHub (May 1, 2025): The [documentation](https://www.better-auth.com/docs/plugins/admin#sche) for the Amin plugin specific that the `banExpires` column should be a `number` which is incorrect. ![Image](https://github.com/user-attachments/assets/0e263e03-8028-4e85-bdcf-52bc59d00e80) The documentation should be updated so that the column type is correct.
Author
Owner

@Kinfe123 commented on GitHub (May 1, 2025):

it is fixed by now!

<!-- gh-comment-id:2844424481 --> @Kinfe123 commented on GitHub (May 1, 2025): it is fixed by now!
Author
Owner

@WorldOfMaze commented on GitHub (May 1, 2025):

Thanks for the quick turn around, @Kinfe123

<!-- gh-comment-id:2845172597 --> @WorldOfMaze commented on GitHub (May 1, 2025): Thanks for the quick turn around, @Kinfe123
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#26550