---
title: onAction
description: Runs once when a public call settles, on success and on failure - the constructor hook for audit logs, activity feeds, and per-action metrics.
---

A constructor [`hook`](/usage#hooks) that runs once when a public call settles, on success and on failure - `status` says which. Single-key operations report `key`; the array form reports the caller's `keys` and emits one event for the whole call, carrying the aggregated `result` (any per-item failures live in that result's `errors`). `copy` and `move` report `from` / `to`. Reach for it for audit logs, activity feeds, and per-action metrics.

```ts lineNumbers
const files = new Files({
  adapter: s3({ bucket: "uploads" }),
  hooks: {
    onAction(event) {
      logger.info("files", {
        action: event.type,
        status: event.status,
        target: event.keys ?? event.from ?? event.key,
        ms: event.durationMs,
      });
    },
  },
});
```

## Identifying the call

`type` is the method name - `"upload"`, `"download"`, `"copy"`, and so on. Which key field is set depends on the shape of that call:

- **Single-key calls** (`upload(key, …)`, `download(key)`, `delete(key)`, …) set `key`.
- **Array calls** (`upload([…])`, `delete([…])`, …) set `keys` and fire one event for the whole batch - the per-item failures live in `result.errors`, not here.
- **`copy`** and **`move`** set `from` / `to` instead of `key`.

On success, `result` is the call's resolved value (an `UploadResult`, a `StoredFile`, a `ListResult`, …); on failure, `error` is the same [`FilesError`](/api/errors) delivered to [`onError`](/api/onerror) and then thrown. Keys are always the ones you passed - the client [`prefix`](/prefixes) is never leaked.

```ts lineNumbers
import type { UploadResult } from "files-sdk";

hooks: {
  onAction(event) {
    if (event.type === "upload" && event.status === "success" && event.key) {
      const { size } = event.result as UploadResult;
      activity.push({ kind: "upload", key: event.key, size });
    }
  },
},
```

## Per-action metrics

Because it fires for every method on success and failure with a wall-clock `durationMs`, one `onAction` covers latency and throughput across the whole surface - no per-call wrapping. It fires once per call, so a bulk `upload([…])` of fifty objects emits a single timing for the batch, not fifty.

```ts lineNumbers
hooks: {
  onAction({ type, status, durationMs }) {
    metrics.timing(`files.${type}.duration`, durationMs, { status });
    metrics.increment(`files.${type}.${status}`);
  },
},
```

## Provenance receipts

With the [`receipts`](/receipts) option on, a successful mutating call (`upload`, `delete`, `copy`, `move`) also carries a `receipt` - a provenance record with the op, provider, key, byte size, etag, timing, and (when asked for) a SHA-256 of the upload body. It's an additive field: absent on reads, on failures, and whenever receipts are off.

```ts lineNumbers
hooks: {
  onAction(event) {
    if (event.receipt) {
      provenance.record(event.receipt);
    }
  },
},
```

Like every hook, `onAction` is **fire-and-forget**: the SDK calls it but never awaits it, and a hook that throws can't fail the operation it observes.

<AutoTypeTable
  path="../../packages/files-sdk/src/index.ts"
  name="FilesActionEvent"
/>
