Skip to content

Record activity

The audit log records who was let in or removed and what changed about money, in every workspace. Record your own events for what people would want to look up later, such as deletions, not for reads or every click.

Already recorded: workspaces created and renamed, invitations, members joining, leaving, removed and changing role (through Better Auth, in apps/api/src/auth/auth.audit.ts), and every change to a subscription (from the billing webhook), and API keys created and revoked. With personal accounts the log starts empty: a person’s own workspace is created without an event.

  1. Add the action to AUDIT_ACTIONS in packages/contracts/src/audit.ts:

    'project.deleted',
  2. Add its sentence to packages/contracts/src/describe-event.ts. It finishes a sentence that starts with the person’s name, and TypeScript fails until you add it:

    'project.deleted': ({ target }) => `deleted the project ${target}`,
  3. Record it where it happens, inside the transaction of the change, so the event exists only if the change does:

    await this.database.transaction(async () => {
    await this.db.client
    .delete(projects)
    .where(and(eq(projects.id, project.id), eq(projects.organizationId, workspace.id)));
    await this.audit.record({
    workspaceId: workspace.id,
    actor: user,
    action: 'project.deleted',
    target: project.name,
    });
    });

The service gets user from its controller, which reads it with @CurrentUser() user: CurrentUser; pass @CurrentSession() too and record impersonatedBy: session.impersonatedBy to mark what an admin did while signed in as someone. Both decorators are in apps/api/src/auth/auth.decorators.ts.

Inject AuditService in any module. When the change cannot be undone, use recordAfterwards, which logs a failure instead of throwing.

Record deletions of what people made: they are what someone will come looking for.

Rows are only ever added: never update or delete an event. Nothing prunes the table; a scheduled job can.

Admins and owners of the workspace, under Settings → Audit log, on plans with the auditLog feature (Pro and Business as shipped). It is recorded on every plan, so nothing is missing after an upgrade. To read it while you build, add 'auditLog' to the free plan’s features.

In tests, give the plan the feature and read the log through the API:

const t = await createTestApp({ teams: true, plans: { free: { features: ['auditLog'] } } });
const log = auditLogSchema.parse(await (await owner.get(`/api/workspaces/${id}/audit-log`)).json());
expect(log.items[0]).toMatchObject({ action: 'project.deleted', target: 'Engine' });