Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,14 @@ module.exports = {
// Allow async functions without await
// for consistency (esp. Convex `handler`s)
"@typescript-eslint/require-await": "off",

// Prevent accidental use of raw context (bypasses triggers)
"no-restricted-syntax": [
"error",
{
selector: "MemberExpression[object.name='_rawCtx']",
message: "Use 'ctx' (triggered context) instead of '_rawCtx' to ensure triggers fire.",
},
],
},
};
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.0.91

- Add triggers support to `convexAuth` config for auth table modifications. You
can now configure `onCreate`, `onUpdate`, and `onDelete` handlers for any
table defined in `authTables`.

## 0.0.90

- fix negative `shouldHandleCode` logic for client
Expand Down
110 changes: 110 additions & 0 deletions docs/pages/advanced.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,116 @@ export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
This is helpful when the default user creation implementation in the library
satisfies your app's needs.

## Triggers

Triggers allow you to run custom code when auth-related tables are modified.
This is useful for audit logging, history tracking, or other side effects that
need to run atomically with auth operations.

### Supported Tables

Triggers can be configured for any table defined in
[`authTables`](/api_reference/server#authtables).

### Trigger Types

Each table supports three trigger types:

- `onCreate` - Called after a new record is inserted
- `onUpdate` - Called after a record is updated (receives both old and new doc)
- `onDelete` - Called after a record is deleted (receives doc snapshot from before deletion)

### Usage

Configure triggers in your `convexAuth` setup:

```ts filename="convex/auth.ts"
import { convexAuth } from "@convex-dev/auth/server";
import type { AuthTriggers } from "@convex-dev/auth/server";
import Password from "@convex-dev/auth/providers/Password";

const triggers: AuthTriggers = {
users: {
onCreate: async (ctx, doc) => {
// Log new user creation
await ctx.db.insert("auditLog", {
action: "user_created",
userId: doc._id,
timestamp: Date.now(),
});
},
onUpdate: async (ctx, newDoc, oldDoc) => {
// Track user profile changes
await ctx.db.insert("userHistory", {
userId: newDoc._id,
before: oldDoc,
after: newDoc,
timestamp: Date.now(),
});
},
},
authAccounts: {
onCreate: async (ctx, doc) => {
console.log(`New auth account created: ${doc.provider}`);
},
onUpdate: async (ctx, newDoc, oldDoc) => {
// Audit password changes, etc.
console.log(`Auth account updated: ${newDoc._id}`);
},
},
authSessions: {
onDelete: async (ctx, sessionId, doc) => {
// Log session termination with user info from pre-deletion snapshot
await ctx.db.insert("auditLog", {
action: "session_ended",
sessionId,
userId: doc?.userId,
timestamp: Date.now(),
});
},
},
};

export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
providers: [Password],
triggers,
});
```

### Type Safety

The trigger configuration is fully type-safe. The `AuthTriggers` type is derived
from `authTables`, so TypeScript will enforce correct table names and document
types:

```ts
import type {
AuthTableName,
AuthTriggers,
TableTriggers,
OnCreateTrigger, // (ctx, doc?) => Promise<void>
OnUpdateTrigger, // (ctx, newDoc?, oldDoc?) => Promise<void>
OnDeleteTrigger, // (ctx, id, doc?) => Promise<void>
} from "@convex-dev/auth/server";
```

### Important Notes

- Triggers run within the same transaction as the auth operation
- If a trigger throws an error, the entire operation will be rolled back
- Triggers receive a full `MutationCtx` so you can perform any database operations
- Not all operations fire all triggers - only the triggers for operations that
actually occur will be called

Comment thread
coderabbitai[bot] marked this conversation as resolved.
**Parameter optimization:** The implementation uses `function.length` to skip
unnecessary database reads. Triggers must explicitly declare parameters to
receive them—default parameters (`doc = null`) and destructuring
(`{ _id }`) break this detection and may result in `undefined` values.

**No nested triggers:** Database operations within a trigger use the original
mutation context, so they won't fire additional triggers. This prevents infinite
loops but means trigger-initiated writes to auth tables are not tracked.

## Session validity

Convex Auth issues JWTs which allow your client to authenticate.
Expand Down
195 changes: 195 additions & 0 deletions docs/pages/api_reference/server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,16 @@ The `shouldLink` argument passed to `createAccount`.

`Promise`\<`void`\>

#### triggers?

> `optional` **triggers**: [`AuthTriggers`](server.mdx#authtriggers)

Database triggers for auth tables.
Triggers run in the same transaction as the auth operation,
allowing for atomic audit logging, history tracking, etc.

See [Triggers](/advanced#triggers) for usage examples.

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Defined in</h3>

[src/server/types.ts:22](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L22)
Expand Down Expand Up @@ -2324,3 +2334,188 @@ Materialized Auth.js provider config.
<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Defined in</h3>

[src/server/types.ts:372](https://github.com/get-convex/convex-auth/blob/main/src/server/types.ts#L372)

***

## AuthTableName


Table names managed by the auth library, derived from [authTables](server.mdx#authtables).
Use these for type-safe trigger configurations.

```ts
type AuthTableName = "users" | "authAccounts" | "authSessions" |
"authRefreshTokens" | "authVerificationCodes" | "authVerifiers" | "authRateLimits"
```

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Defined in</h3>

[src/server/implementation/types.ts:165](https://github.com/get-convex/convex-auth/blob/main/src/server/implementation/types.ts#L165)

***

## AuthTriggers


Configuration for auth table triggers.
Triggers run in the same transaction as the auth operation,
allowing for atomic audit logging, history tracking, etc.

```ts
type AuthTriggers = {
[K in AuthTableName]?: TableTriggers<K>;
}
```

See [Triggers](/advanced#triggers) for usage examples.

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Defined in</h3>

[src/server/implementation/types.ts:231](https://github.com/get-convex/convex-auth/blob/main/src/server/implementation/types.ts#L231)

***

## TableTriggers\<TableName\>


Trigger configuration for a single table.

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Type Parameters</h3>

<table className="api_reference_table"><tbody>
<tr>
<th>Type Parameter</th>
</tr>
<tr>
<td>

`TableName` *extends* [`AuthTableName`](server.mdx#authtablename)

</td>
</tr>
</tbody></table>

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Type declaration</h3>

#### onCreate?

> `optional` **onCreate**: [`OnCreateTrigger`](server.mdx#oncreatetriggertablename)\<`TableName`\>

Called after a new record is inserted.

#### onUpdate?

> `optional` **onUpdate**: [`OnUpdateTrigger`](server.mdx#onupdatetriggertablename)\<`TableName`\>

Called after a record is updated. Receives both the new and old document.

#### onDelete?

> `optional` **onDelete**: [`OnDeleteTrigger`](server.mdx#ondeletetriggertablename)\<`TableName`\>

Called after a record is deleted.

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Defined in</h3>

[src/server/implementation/types.ts:196](https://github.com/get-convex/convex-auth/blob/main/src/server/implementation/types.ts#L196)

***

## OnCreateTrigger\<TableName\>


Trigger handler called when a document is created.
Omit the doc parameter to skip the read.

```ts
type OnCreateTrigger<TableName> =
| ((ctx: MutationCtx) => Promise<void>)
| ((ctx: MutationCtx, doc: Doc<TableName>) => Promise<void>)
```

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Type Parameters</h3>

<table className="api_reference_table"><tbody>
<tr>
<th>Type Parameter</th>
</tr>
<tr>
<td>

`TableName` *extends* [`AuthTableName`](server.mdx#authtablename)

</td>
</tr>
</tbody></table>

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Defined in</h3>

[src/server/implementation/types.ts:170](https://github.com/get-convex/convex-auth/blob/main/src/server/implementation/types.ts#L170)

***

## OnUpdateTrigger\<TableName\>


Trigger handler called when a document is updated.
Omit oldDoc to skip reading the old document.
Omit both to skip all reads.

```ts
type OnUpdateTrigger<TableName> =
| ((ctx: MutationCtx) => Promise<void>)
| ((ctx: MutationCtx, newDoc: Doc<TableName>) => Promise<void>)
| ((ctx: MutationCtx, newDoc: Doc<TableName>, oldDoc: Doc<TableName>) => Promise<void>)
```

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Type Parameters</h3>

<table className="api_reference_table"><tbody>
<tr>
<th>Type Parameter</th>
</tr>
<tr>
<td>

`TableName` *extends* [`AuthTableName`](server.mdx#authtablename)

</td>
</tr>
</tbody></table>

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Defined in</h3>

[src/server/implementation/types.ts:179](https://github.com/get-convex/convex-auth/blob/main/src/server/implementation/types.ts#L179)

***

## OnDeleteTrigger\<TableName\>


Trigger handler called when a document is deleted.
Omit doc to skip reading the document before deletion.

```ts
type OnDeleteTrigger<TableName> =
| ((ctx: MutationCtx, id: GenericId<TableName>) => Promise<void>)
| ((ctx: MutationCtx, id: GenericId<TableName>, doc: Doc<TableName> | null) => Promise<void>)
```

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Type Parameters</h3>

<table className="api_reference_table"><tbody>
<tr>
<th>Type Parameter</th>
</tr>
<tr>
<td>

`TableName` *extends* [`AuthTableName`](server.mdx#authtablename)

</td>
</tr>
</tbody></table>

<h3 className="nx-font-semibold nx-tracking-tight nx-text-slate-900 dark:nx-text-slate-100 nx-mt-8 nx-text-2xl">Defined in</h3>

[src/server/implementation/types.ts:188](https://github.com/get-convex/convex-auth/blob/main/src/server/implementation/types.ts#L188)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@convex-dev/auth",
"version": "0.0.90",
"version": "0.0.91",
"description": "Authentication for Convex",
"keywords": [
"authentication",
Expand Down
Loading