diff --git a/apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx b/apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx
index 1f1bb5317..5976d5261 100644
--- a/apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx
+++ b/apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx
@@ -131,7 +131,7 @@ export default function AdminDocumentsPage() {
setTerm(e.target.value)}
/>
diff --git a/packages/lib/constants/database.ts b/packages/lib/constants/database.ts
new file mode 100644
index 000000000..947d1b0af
--- /dev/null
+++ b/packages/lib/constants/database.ts
@@ -0,0 +1,6 @@
+/**
+ * The maximum value of a Postgres `INT4` (32-bit signed integer) column, which
+ * is what Prisma `Int` ID columns are stored as. Filtering an `Int` column by
+ * a larger value makes Prisma throw a conversion error at runtime.
+ */
+export const MAX_POSTGRES_INT = 2147483647;
diff --git a/packages/lib/server-only/admin/admin-find-documents.ts b/packages/lib/server-only/admin/admin-find-documents.ts
index 994422ba7..b13b58e9c 100644
--- a/packages/lib/server-only/admin/admin-find-documents.ts
+++ b/packages/lib/server-only/admin/admin-find-documents.ts
@@ -2,6 +2,7 @@ import { prisma } from '@documenso/prisma';
import { EnvelopeType, type Prisma } from '@prisma/client';
import { z } from 'zod';
+import { MAX_POSTGRES_INT } from '../../constants/database';
import type { FindResultResponse } from '../../types/search-params';
export interface AdminFindDocumentsOptions {
@@ -10,7 +11,7 @@ export interface AdminFindDocumentsOptions {
perPage?: number;
}
-const ZPositiveIntegerSchema = z.coerce.number().int().positive();
+const ZPositiveIntegerSchema = z.coerce.number().int().positive().max(MAX_POSTGRES_INT);
const emptyResponse = {
data: [],
@@ -58,6 +59,40 @@ export const adminFindDocuments = async ({ query, page = 1, perPage = 10 }: Admi
}
}
+ if (query?.startsWith('recipient:')) {
+ const recipientQuery = query.slice('recipient:'.length).trim();
+
+ if (!recipientQuery) {
+ return emptyResponse;
+ }
+
+ // Match admin-global-search semantics: only bare digit strings are ID
+ // lookups, so numeric-looking email fragments (1e3, 0x10, +40) stay text.
+ const isRecipientIdLookup = /^\d+$/.test(recipientQuery);
+
+ const parsedRecipientId = ZPositiveIntegerSchema.safeParse(recipientQuery);
+
+ termFilters =
+ isRecipientIdLookup && parsedRecipientId.success
+ ? {
+ recipients: {
+ some: {
+ id: parsedRecipientId.data,
+ },
+ },
+ }
+ : {
+ recipients: {
+ some: {
+ email: {
+ contains: recipientQuery,
+ mode: 'insensitive',
+ },
+ },
+ },
+ };
+ }
+
if (query && query?.startsWith('envelope_')) {
termFilters = {
id: {
diff --git a/packages/lib/server-only/admin/admin-global-search.ts b/packages/lib/server-only/admin/admin-global-search.ts
index 3a6e5fd40..b9f7b140a 100644
--- a/packages/lib/server-only/admin/admin-global-search.ts
+++ b/packages/lib/server-only/admin/admin-global-search.ts
@@ -1,9 +1,9 @@
import { prisma } from '@documenso/prisma';
import { EnvelopeType } from '@prisma/client';
-export const ADMIN_SEARCH_RESULTS_PER_TYPE = 5;
+import { MAX_POSTGRES_INT } from '../../constants/database';
-const MAX_POSTGRES_INT = 2147483647;
+export const ADMIN_SEARCH_RESULTS_PER_TYPE = 5;
const GROUP_ORDER = ['document', 'user', 'organisation', 'team', 'recipient', 'subscription'] as const;