---
title: usage
description: Meter storage, bandwidth, and operation counts across a Files instance, and read the running totals back with files.usage(). Counts the bytes you actually read out of a download lazily, optionally bucketed per tenant or prefix. No native dependencies; works on any adapter.
---

The built-in `usage()` plugin tallies every operation on a `Files` instance and surfaces the running totals as `files.usage()`. Each call counts as one operation, `upload` adds its size to `bytesUp`, and `download` / `head` wrap the returned body so the bytes you **actually read** add to `bytesDown` — the lazy, stream-level accounting a fire-and-forget [hook](/api/onaction) can't do.

```ts lineNumbers
import { createFiles } from "files-sdk";
import { s3 } from "files-sdk/s3";
import { usage } from "files-sdk/usage";

const files = createFiles({
  adapter: s3({ bucket: "uploads" }),
  plugins: [usage()],
});

await files.upload("a.txt", "hello");
await (await files.download("a.txt")).text();

files.usage();
// { operations: 2, bytesUp: 5, bytesDown: 5, operationsByKind: { upload: 1, download: 1, … } }
```

Because `usage()` adds `files.usage()`, construct the instance with [`createFiles`](/plugins/api#createfiles) so the method shows up on the type — the same as [`versioning()`](/plugins/versioning).

## How it works

- **`upload`** adds its result's reported size to `bytesUp`. Nothing is buffered — the body still streams to the adapter.
- **`download` / `head`** return a body wrapped so the bytes are counted as they flow: `stream()` is metered chunk-by-chunk (an aborted read counts only what was consumed), and `arrayBuffer()` / `blob()` / `text()` count the body's length the first time one resolves. **An unread body costs nothing** — and a body read twice counts once.
- **Every verb** increments `operations` and its `operationsByKind` entry. A call that throws (a missing key, a `validation()` veto) isn't counted, and because plugins run [outside retries](/retries) a call counts once no matter how many attempts it takes.

Bulk `upload([...])` / `download([...])` count **per item**.

## Reading the totals

`extend` adds three methods:

| Method           | Returns                                                   |
| ---------------- | --------------------------------------------------------- |
| `usage()`        | A `UsageStats` snapshot aggregated across every group.    |
| `usageByGroup()` | A `Record<string, UsageStats>` keyed by the group label.  |
| `resetUsage()`   | Zeroes every counter, starting a fresh accounting window. |

Each snapshot is a fresh copy, so mutating it never touches the running totals.

## Grouping by tenant or prefix

Pass `group` to bucket usage by a label derived from each operation — a tenant id, a key prefix, anything. It receives the full [operation](/plugins/api#filesoperation), so branch on `op.kind` to reach `op.key`, `op.from` / `op.to`, etc.

```ts lineNumbers
const files = createFiles({
  adapter: s3({ bucket: "uploads" }),
  plugins: [
    usage({ group: (op) => ("key" in op ? op.key.split("/")[0] : "shared") }),
  ],
});

await files.upload("acme/logo.png", bytes);
await files.upload("globex/logo.png", bytes);

files.usageByGroup(); // { acme: { bytesUp: … }, globex: { bytesUp: … } }
files.usage(); // the two buckets summed
```

## Ordering

Put `usage()` **first** (outermost) to meter what your application sees: a later body-transforming plugin like [`compression()`](/plugins/compression) or [`encryption()`](/plugins/encryption) reports the **logical** size up the chain, and the internal sub-operations a plugin like [`dedup()`](/plugins/dedup) issues stay below it, unmetered.

```ts
plugins: [usage(), compression(), encryption(key)];
```

Placed **last** (innermost) it instead meters the bytes-on-the-wire to the provider and the provider operations those plugins expand into. Both are valid — pick the layer whose numbers you want to bill against.

## Things to keep in mind

- **`bytesUp` comes from each upload's reported result size.** The rare adapter that doesn't report a size contributes `0` to `bytesUp` for that upload.
- **`bytesDown` is only the body you read.** Reading metadata with `head()` and never touching the body costs nothing; the moment you call a body accessor, the bytes are tallied.
- **It counts logical operations, not provider requests.** Retries happen below the plugin, so a call that retries three times still counts once.
- **No metadata, no native deps.** Unlike the transforming plugins it never reads or rewrites the body, so streaming, range downloads, `url()`, and `signedUploadUrl()` all keep working on any adapter.
