From 59e3f207607cd99ff92192546c4fdbd29121a500 Mon Sep 17 00:00:00 2001 From: Taesu Date: Sun, 8 Feb 2026 21:39:10 +0900 Subject: [PATCH] fix: support all where operators in list filter endpoints --- docs/content/docs/plugins/admin.mdx | 6 +-- docs/content/docs/plugins/organization.mdx | 4 +- .../better-auth/src/plugins/admin/routes.ts | 4 +- .../src/plugins/organization/adapter.ts | 3 +- .../organization/routes/crud-members.test.ts | 45 +++++++++++++++++++ .../organization/routes/crud-members.ts | 4 +- packages/core/src/db/adapter/index.ts | 32 ++++++------- 7 files changed, 75 insertions(+), 23 deletions(-) diff --git a/docs/content/docs/plugins/admin.mdx b/docs/content/docs/plugins/admin.mdx index d13bedabd7..796cce22af 100644 --- a/docs/content/docs/plugins/admin.mdx +++ b/docs/content/docs/plugins/admin.mdx @@ -153,11 +153,11 @@ type listUsers = { /** * The value to filter by. */ - filterValue?: string | number | boolean = "hello@example.com" + filterValue?: string | number | boolean | string[] = "hello@example.com" /** - * The operator to use for the filter. + * The operator to use for the filter. */ - filterOperator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" = "eq" + filterOperator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" = "eq" } ``` diff --git a/docs/content/docs/plugins/organization.mdx b/docs/content/docs/plugins/organization.mdx index b224360328..fe8032ca45 100644 --- a/docs/content/docs/plugins/organization.mdx +++ b/docs/content/docs/plugins/organization.mdx @@ -1090,11 +1090,11 @@ type listMembers = { /** * The operator to filter by. */ - filterOperator?: "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "contains" = "eq" + filterOperator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "in" | "not_in" | "contains" | "starts_with" | "ends_with" = "eq" /** * The value to filter by. */ - filterValue?: string = "value" + filterValue?: string | number | boolean | string[] = "value" } ``` diff --git a/packages/better-auth/src/plugins/admin/routes.ts b/packages/better-auth/src/plugins/admin/routes.ts index 0a80635b1d..41a5d23c30 100644 --- a/packages/better-auth/src/plugins/admin/routes.ts +++ b/packages/better-auth/src/plugins/admin/routes.ts @@ -4,6 +4,7 @@ import { } from "@better-auth/core/api"; import type { Session } from "@better-auth/core/db"; import type { Where } from "@better-auth/core/db/adapter"; +import { whereOperators } from "@better-auth/core/db/adapter"; import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error"; import * as z from "zod"; import { getSessionFromCtx } from "../../api"; @@ -574,9 +575,10 @@ const listUsersQuerySchema = z.object({ }) .or(z.number()) .or(z.boolean()) + .or(z.array(z.string())) .optional(), filterOperator: z - .enum(["eq", "ne", "lt", "lte", "gt", "gte", "contains"]) + .enum(whereOperators) .meta({ description: "The operator to use for the filter", }) diff --git a/packages/better-auth/src/plugins/organization/adapter.ts b/packages/better-auth/src/plugins/organization/adapter.ts index bf042d5429..bd676c5dd6 100644 --- a/packages/better-auth/src/plugins/organization/adapter.ts +++ b/packages/better-auth/src/plugins/organization/adapter.ts @@ -1,5 +1,6 @@ import type { AuthContext, GenericEndpointContext } from "@better-auth/core"; import { getCurrentAdapter } from "@better-auth/core/context"; +import type { WhereOperator } from "@better-auth/core/db/adapter"; import { BetterAuthError } from "@better-auth/core/error"; import { filterOutputFields } from "@better-auth/core/utils/db"; import { parseJSON } from "../../client/parser"; @@ -134,7 +135,7 @@ export const getOrgAdapter = ( filter?: | { field: string; - operator?: "eq" | "ne" | "lt" | "lte" | "gt" | "gte" | "contains"; + operator?: WhereOperator; value: any; } | undefined; diff --git a/packages/better-auth/src/plugins/organization/routes/crud-members.test.ts b/packages/better-auth/src/plugins/organization/routes/crud-members.test.ts index aca71659be..beb6d6019b 100644 --- a/packages/better-auth/src/plugins/organization/routes/crud-members.test.ts +++ b/packages/better-auth/src/plugins/organization/routes/crud-members.test.ts @@ -144,6 +144,51 @@ describe("listMembers", async () => { expect(members.data?.total).toBe(10); }); + it("should filter the members with 'in' operator", async () => { + const members = await client.organization.listMembers({ + fetchOptions: { + headers, + }, + query: { + filterField: "role", + filterOperator: "in", + filterValue: ["member", "owner"], + }, + }); + expect(members.data?.members.length).toBe(11); + expect(members.data?.total).toBe(11); + }); + + it("should filter the members with 'not_in' operator", async () => { + const members = await client.organization.listMembers({ + fetchOptions: { + headers, + }, + query: { + filterField: "role", + filterOperator: "not_in", + filterValue: ["owner"], + }, + }); + expect(members.data?.members.length).toBe(10); + expect(members.data?.total).toBe(10); + }); + + it("should filter the members with 'starts_with' operator", async () => { + const members = await client.organization.listMembers({ + fetchOptions: { + headers, + }, + query: { + filterField: "role", + filterOperator: "starts_with", + filterValue: "mem", + }, + }); + expect(members.data?.members.length).toBe(10); + expect(members.data?.total).toBe(10); + }); + it("should sort the members", async () => { const defaultMembers = await client.organization.listMembers({ fetchOptions: { diff --git a/packages/better-auth/src/plugins/organization/routes/crud-members.ts b/packages/better-auth/src/plugins/organization/routes/crud-members.ts index c6dc9022e9..c787e5c881 100644 --- a/packages/better-auth/src/plugins/organization/routes/crud-members.ts +++ b/packages/better-auth/src/plugins/organization/routes/crud-members.ts @@ -1,5 +1,6 @@ import type { LiteralString } from "@better-auth/core"; import { createAuthEndpoint } from "@better-auth/core/api"; +import { whereOperators } from "@better-auth/core/db/adapter"; import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error"; import * as z from "zod"; import { getSessionFromCtx, sessionMiddleware } from "../../../api"; @@ -879,9 +880,10 @@ export const listMembers = (options: O) => }) .or(z.number()) .or(z.boolean()) + .or(z.array(z.string())) .optional(), filterOperator: z - .enum(["eq", "ne", "lt", "lte", "gt", "gte", "contains"]) + .enum(whereOperators) .meta({ description: "The operator to use for the filter", }) diff --git a/packages/core/src/db/adapter/index.ts b/packages/core/src/db/adapter/index.ts index c8d59383a6..159aae0dd5 100644 --- a/packages/core/src/db/adapter/index.ts +++ b/packages/core/src/db/adapter/index.ts @@ -301,25 +301,27 @@ export interface DBAdapterFactoryConfig< disableTransformJoin?: boolean | undefined; } +export const whereOperators = [ + "eq", + "ne", + "lt", + "lte", + "gt", + "gte", + "in", + "not_in", + "contains", + "starts_with", + "ends_with", +] as const; + +export type WhereOperator = (typeof whereOperators)[number]; + export type Where = { /** * @default eq */ - operator?: - | ( - | "eq" - | "ne" - | "lt" - | "lte" - | "gt" - | "gte" - | "in" - | "not_in" - | "contains" - | "starts_with" - | "ends_with" - ) - | undefined; + operator?: WhereOperator | undefined; value: string | number | boolean | string[] | number[] | Date | null; field: string; /**