---
title: "content.hooks"
description: "A Hookable bus for content-change events."
canonical_url: "https://content.comark.dev/reference/content/hooks"
---
# content.hooks

> A Hookable bus for content-change events.

## `content.hooks`

A [`Hookable`](https://github.com/unjs/hookable) event bus for content-change events. Subscribe with `content.hooks.hook(name, callback)`; the events are typed as [`ContentHooks`](https://content.comark.dev/reference/types/content#contenthooks).

**Type:** `Hookable<`[`ContentHooks`](https://content.comark.dev/reference/types/content#contenthooks)`>`

| Hook                | Signature                                 | Fired when                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `read:after`        | `(ctx) => void \| Promise<void>`          | A public read resolved: `get()`, `list()`, `navigation()`, or a plugin read such as `query()` and `search()`. Carries [`ContentReadContext`](https://content.comark.dev/reference/types/content#contentreadcontext) with deterministic cache tags. Never fires for a failed read.                                                                                                                                                                    |
| `clean`             | `({ ref, key }) => void \| Promise<void>` | [`clean()`](https://content.comark.dev/reference/content/clean) is about to delete the instance's persisted data. Delete what you store for it outside the content cache.                                                                                                                                                                                                                                                                            |
| `watch:file:update` | `(source, key, file) => void`             | A watched file is created or changed.                                                                                                                                                                                                                                                                                                                                                                                                                |
| `watch:file:remove` | `(source, key) => void`                   | A watched file is deleted.                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `watch:error`       | `(source, key, error) => void`            | A watched file failed to parse or validate. The entry is dropped from the manifest, the error is logged, and the watcher keeps running.                                                                                                                                                                                                                                                                                                              |
| `file:parsed`       | `(ctx) => void \| Promise<void>`          | After a file extension parser assembles a [`ContentFile`](https://content.comark.dev/reference/types/content#contentfiletdata-tmeta), and on [`ingest()`](https://content.comark.dev/reference/content/ingest). Handlers may mutate or null [`FileParsedContext.file`](https://content.comark.dev/reference/types/file-parsed#fileparsedcontext). Not fired by [`update()`](https://content.comark.dev/reference/content/update) or by parsed reads. |
| `typegen:field`     | `(ctx) => void \| Promise<void>`          | While [generating source types](https://content.comark.dev/guide/typescript); plugins may override `ctx.type` and register imports via `ctx.imports`. See [`TypegenFieldContext`](https://content.comark.dev/reference/types/typegen#typegenfieldcontext).                                                                                                                                                                                           |
| `file:upsert`       | `(source, key, file) => void`             | [`update()`](https://content.comark.dev/reference/content/update), [`ingest()`](https://content.comark.dev/reference/content/ingest) or a `get()` parse upserts an entry.                                                                                                                                                                                                                                                                            |
| `file:remove`       | `(source, key) => void`                   | [`remove()`](https://content.comark.dev/reference/content/remove) drops an entry.                                                                                                                                                                                                                                                                                                                                                                    |
| `file:bulk:load`    | `(source, items) => void`                 | A source's entries are bulk-loaded (for example snapshot hydration).                                                                                                                                                                                                                                                                                                                                                                                 |

## `read:after`

Fired after every public read resolves, with a [`ContentReadContext`](https://content.comark.dev/reference/types/content#contentreadcontext): the `operation`, its `input`, the `result`, the instance's `ref` when it's pinned with [`withRef()`](https://content.comark.dev/reference/content/with-ref), and `tags`. The tags are deterministic, so a framework cache can key a rendered page on them and invalidate it when content changes:

| Read                                    | Tags                                                           |
| --------------------------------------- | -------------------------------------------------------------- |
| `get('/blog/hello')`                    | `content`, `content:path:/blog/hello`, `content:source:<name>` |
| `list()`                                | `content`, `content:list`, `content:source:<name>`             |
| `navigation()`                          | `content`, `content:navigation`                                |
| `query().all()`, `.first()`, `.count()` | `content`, `content:query`, `content:source:<name>`            |
| `search(term)`                          | `content`, `content:search`, `content:source:<name>`           |

Set-based reads tag the operation and every source they touched, never individual paths: a newly added document's path is not in the previous result, so a path tag could not invalidate a cached listing. Counts emit too, since a cached count depends on its source the same way. `navigation()` emits once; the listing it builds on does not emit on its own.

Register hooks at construction with the `hooks` option to have them on the instance and on every [`withRef()`](https://content.comark.dev/reference/content/with-ref) sibling:

```ts [tags.ts]
const content = comarkContent({
  source: fs('./content'),
  hooks: {
    'read:after': ({ tags }) => tags.forEach(cacheTag),
  },
})
```

Plugins emit their own operations with [`callReadHook()`](https://content.comark.dev/plugins/custom/plugin-api#read-events). The [HTTP client](https://content.comark.dev/reference/client/create-content-client#options-hooks) emits the same tags locally for the same reads.

## `file:parsed`

Fired at the end of [`parserRegistry.process()`](https://content.comark.dev/plugins/custom/plugin-api) after a file extension parser assembles a [`ContentFile`](https://content.comark.dev/reference/types/content#contentfiletdata-tmeta), and by [`ingest()`](https://content.comark.dev/reference/content/ingest) for a file built by hand. Handlers receive a [`FileParsedContext`](https://content.comark.dev/reference/types/file-parsed#fileparsedcontext) and may mutate `ctx.file` or set it to `null` to reject the document.

It is **not** fired for content that is already parsed: [`update()`](https://content.comark.dev/reference/content/update), snapshot hydration, and per-file reads from a [parsed source](https://content.comark.dev/sources/snapshot). That work happened on the producing instance; see [custom parsed sources](https://content.comark.dev/sources/snapshot#custom-parsed-sources).

The [`markdown-fields`](https://content.comark.dev/plugins/built-in/markdown-fields) plugin transforms `x-content` markdown fields here — in frontmatter, JSON, YAML, and any other parsed `file.data`. The [`schema-validation`](https://content.comark.dev/plugins/built-in/schema-validation) plugin validates against the source schema — behavior controlled by [`onError`](https://content.comark.dev/plugins/built-in/schema-validation#api-onerror).

## `typegen:field`

Fired while [`generateSourceTypes()`](https://content.comark.dev/guide/typescript) walks each source schema. Handlers receive a [`TypegenFieldContext`](https://content.comark.dev/reference/types/typegen#typegenfieldcontext): override `ctx.type` for semantic fields and call `ctx.imports.add(from, ...names)` for extra type imports.

## Usage

```ts [hooks.ts]
content.hooks.hook('file:upsert', (source, key, file) => {
  // re-index, revalidate, push the change over a socket, ...
})
```


## Sitemap

See the full [sitemap](https://content.comark.dev/sitemap.md) for all pages.
