audit() function

Build the query extension that records every write to an audited model.

Signature:

export declare function audit(input: AuditOptions): (client: any) => import("@prisma/client/extension").PrismaClientExtends<import("@prisma/client/runtime/client").InternalArgs<{}, {}, {}, {}>>;

Parameters

Parameter

Type

Description

input

AuditOptions

The unextended client, the target config, the audited model names, and the actor resolver.

Returns:

(client: any) => import("@prisma/client/extension").PrismaClientExtends<import("@prisma/client/runtime/client").InternalArgs<{}, {}, {}, {}>>

A Prisma extension to pass to client.$extends().

Remarks

Rows are inserted into config.model under the names in config.columns, using raw SQL rather than a Prisma model — which is what lets the trail live in a table Prisma knows nothing about. The row id is generated by Postgres (gen_random_uuid()) because a Prisma-level @default(uuid()) on the target is client-side and never applies to a raw insert.

What gets captured depends on the operation. create, update and delete record the affected record itself, keyed by its id. updateMany and deleteMany cannot identify rows, so they record the query args and the affected count under the literal recordId of 'many'. A model absent from models is not recorded, and neither is a single-row write whose result has no id — every audited model is assumed to carry a surrogate id.

Auditing is fire-and-forget by design: the insert is not awaited, and a failure is swallowed rather than thrown or logged. A write therefore never fails because its audit row could not be stored — and equally, a broken audit configuration is silent. Verify it once against a real table rather than trusting that no error means it is working.

Ordering matters when this is combined with an extension that reroutes an operation to another client — softDelete() turning a delete into an update is exactly that. Prisma runs the first-added query hook outermost, so audit must be added FIRST or the rerouted operation never reaches it and vanishes from the trail.

Example

const base = new PrismaClient();
const client = base
    .$extends(audit({
        client: base,
        config: AUDIT_CONFIG,
        models: new Set(['User']),
        getPrincipal: () => context.get()?.user ?? null,
    }))
    .$extends(softDelete({ client: base, models: SOFT_DELETE_MODELS }));

Read this page as plain markdown — no HTML, no navigation. For pasting into an LLM, or for an agent to fetch.