---
title: "Activity log reference plugin"
image: "https://adonia.pages.dev/og.png"
version: "next"
---

> Documentation Index
> Fetch the complete documentation index at: https://adonia.pages.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Activity log reference plugin

`adonia-activity-log` reads the mandatory `adonia_action_events` store already written by core actions (and CRUD when enabled). It adds a read-only resource, exact audit filters, semantic diffs, retention tooling, and Plugin ABI metadata without creating a parallel audit store.

## Install

```sh
pnpm add adonia-activity-log
```

The configured Plugin ABI manifest registers the resource helper on the panel. Its migration and client-entry metadata are also available to Adonia tooling:

```ts
import { Panel } from '@adonia/core'
import { activityLogPlugin } from 'adonia-activity-log'
import User from '#models/user'

export const admin = Panel.make('admin')
  .path('/admin')
  .plugins([
activityLogPlugin({
  panel: 'admin',
  authorize: async (ctx) => ctx.auth.user instanceof User && ctx.auth.user.role === 'admin',
  tenantScope: false,
}),
  ])
```

`panel` is required and scopes reads to that panel id. `authorize(ctx, event?)` gates both list and detail. The generated resource denies create, edit, delete, restore, and force-delete. Optional `slug` defaults to `activity-log`, and `navigationGroup` defaults to `System`.

Pass `tenantScope` exactly as for a core resource: a column name, a compound scope callback, or `false` for a deliberately shared log. Omission preserves core's fail-closed behavior on a tenant request; the factory does not guess ownership. The stock core migration has no tenant column, so a string scope is appropriate only when the application adds that column.

For manual registration, `createActivityLogResource(options)` remains available. `activityLogManifest` exposes the unconfigured package metadata, including protocol version, migration directory, client entry, and resource helper.

Install the lazy browser half in the application registry:

```ts
import { activityLogUiPlugin } from 'adonia-activity-log/client'
import { adonia } from '@adonia/ui'

export default adonia({
  registry(registry) {
registry.use(activityLogUiPlugin)
  },
})
```

The client plugin registers the status cell, exact-value audit filter, and semantic changes renderer. Diff and filter code is dynamically imported so it does not enter the base UI bundle.

## Browsing and diffs

The detail schema exposes panel, resource, action, status, actor type and id, target id, batch id, both timestamps, scrubbed payload and changes, and safe exception state. Index filters support exact resource, action, actor id, and target id matches, plus status and occurrence range.

When `actorType` or `resource` is a slug in the active panel's resource registry, actor and target identities receive server-resolved detail URLs. Unregistered identities remain plain text; the browser never guesses panel mounts or resource routes.

Changes shaped as `{ before, after }` render as a field-level old-value → new-value table containing only changed keys. Legacy or malformed shapes fall back to escaped, pretty-printed JSON. Sensitive-field scrubbing still happens before persistence; applications must avoid secrets in custom action labels and exception messages.

## Retention

Prune from automation with an absolute ISO cutoff or a relative age:

```sh
node ace adonia:activity:prune --older-than=90d
node ace adonia:activity:prune --older-than=2026-01-01T00:00:00Z --panel=admin
```

Register the exported Ace command in `adonisrc.ts`:

```ts
import { defineConfig } from '@adonisjs/core/app'

export default defineConfig({
  commands: [() => import('adonia-activity-log/commands/activity_prune')],
})
```

The equivalent programmatic API is:

```ts
import { purgeActivityEvents } from 'adonia-activity-log'

const deleted = await purgeActivityEvents('90d', { panel: 'admin' })
```

Retention permanently deletes rows whose `created_at` is before the cutoff. Omitting `panel` prunes all panels, so use a scoped value when policies differ.

See [`adonia-activity-log` API](/reference/api/activity-log) for every symbol.

Source: https://adonia.pages.dev/reference/plugins/activity-log/index.mdx
