# Node API

> The hydration-proof Node API: run tests from a script with run(), read and merge reports, define configs, plugins and adapters, and use the stable exit codes.

Source: https://hydration.jscrate.dev/docs/node-api
Last updated: 2026-09-18

The hydration-proof Node API runs the same test as the CLI from your own
script. `run()` resolves with the report and the exit code and never calls
`process.exit`, so you can start a server, seed data or publish the report
around it. Everything on this page is exported from the `hydration-proof`
package.

## Run a test with run()

```ts title="run-from-node.ts"
import { run } from "hydration-proof";

const { exitCode, report } = await run({
  overrides: { url: "http://localhost:3000", reporters: ["json"] },
  write: (text) => process.stdout.write(text),
});

// The report is the same object the JSON reporter writes.
for (const issue of report.issues) {
  if (issue.severity !== "error") continue;
  console.log(
    `${issue.route.pattern} ${issue.code} ${issue.source?.file ?? "(no source)"} — ${issue.cause?.title ?? "unknown cause"}`
  );
}

process.exit(exitCode);
```

`run()` reads the config the way the CLI does, from `cwd` or the file you name
in `config`. `overrides` are the same overrides the command line applies, such
as `url`, `routes`, `reporters`, `failOn`, `probes` or `shard`. `write`
receives the progress and the terminal report (standard output by default),
and `reporters` adds your own reporters to the configured ones.

```ts
function run(options?: RunOptions): Promise<RunResult>
```

Runs a test the way `hydration-proof test` does and resolves with the report and exit code. It never calls `process.exit`.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `cwd` | `string` | `process.cwd()` | Directory the config is looked up from. |
| `config` | `string` | — | Config file path; found automatically when omitted. |
| `overrides` | `CliOverrides` | — | The same overrides the command line applies, e.g. `{ url, routes, reporters, failOn }`. |
| `write` | `(text: string) => void` | — | Where progress and the terminal report are written. |
| `reporters` | `Reporter[]` | — | Extra reporters (in addition to the configured ones). |
| `signal` | `AbortSignal` | — | Abort the run (the app and browser are shut down). |

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `report` (required) | `Report` | — | The full report, the same object `report.json` holds. |
| `exitCode` (required) | `ExitCode` | — | The exit code the CLI would use. See `ExitCode`. |
| `failures` (required) | `string[]` | — | Human-readable reasons the run failed. |
| `files` (required) | `string[]` | — | Report files that were written. |
| `notes` (required) | `string[]` | — | Informational notes printed at the end of the run. |
| `outputDir` (required) | `string` | — | Where the reports were written. |

To stop a run early, pass an `AbortSignal`. When it aborts, the app and the
browser are shut down:

```ts title="run-with-timeout.ts"
import { run } from "hydration-proof";

// Stop the run, the app and the browser after ten minutes.
await run({ signal: AbortSignal.timeout(10 * 60_000) });
```

## Handle errors with RunError

Problems found on pages never throw: they are in the report, and `exitCode`
says whether the policy failed. `run()` throws a `RunError` for failures that
stop a run before any page is tested, such as an invalid config, a failing
`setup` hook or login, a browser that cannot launch, or an app that cannot be
started. Its `exitCode` is the one the CLI would use:

```ts title="hydration.ts"
import { run, RunError } from "hydration-proof";

try {
  const result = await run();
  process.exitCode = result.exitCode;
} catch (error) {
  if (!(error instanceof RunError)) throw error;
  console.error(error.message);
  process.exitCode = error.exitCode;
}
```

```ts
class RunError extends Error {
  override name: string;
  readonly exitCode: ExitCode;
  readonly details: string | undefined;
  constructor(message: string, exitCode: ExitCode, details?: string);
}
```

Thrown by `run()` for failures that stop a run before any page is tested, with the exit code the CLI would use.

## Exit codes

`ExitCode` names the codes the CLI exits with. They are stable:

| Name          | Code  | Meaning                                                        |
| ------------- | ----- | -------------------------------------------------------------- |
| `Ok`          | `0`   | Every page passed the policy                                   |
| `Failed`      | `1`   | Findings, a budget or an expired ignore rule failed the policy |
| `Usage`       | `2`   | Invalid configuration or command-line usage                    |
| `Server`      | `3`   | The app could not be built, started or reached                 |
| `Browser`     | `4`   | The browser is missing or failed to launch                     |
| `Internal`    | `70`  | A bug in hydration-proof                                       |
| `Interrupted` | `130` | Interrupted (Ctrl+C)                                           |

```ts
const ExitCode: {
  /** Every page passed the policy. */
  readonly Ok: 0;
  /** Issues, budget or expired ignore rules failed the policy. */
  readonly Failed: 1;
  /** Invalid configuration or command-line usage. */
  readonly Usage: 2;
  /** The app could not be built, started or reached. */
  readonly Server: 3;
  /** The browser is missing or failed to launch. */
  readonly Browser: 4;
  /** A bug in hydration-proof. */
  readonly Internal: 70;
  /** Interrupted (Ctrl+C). */
  readonly Interrupted: 130;
}
```

Exit codes. They are stable: `0` ok, `1` failed, `2` usage or config error, `3` server, `4` browser, `70` internal error. An interrupted run exits `130`.

## Read and merge reports

`readReport()` reads `report.json` from a report folder or a file path.
`mergeReports()` combines the reports of sharded or per-project runs into one
report, removing duplicates. The CLI's
[`merge-reports`](https://hydration.jscrate.dev/docs/ci#split-the-run-across-jobs) command wraps it, and
also applies the CI policy and runs the reporters.

```ts title="merge.ts"
import { writeFileSync } from "node:fs";
import { mergeReports, readReport } from "hydration-proof";

const report = mergeReports(
  ["shards/shard-1", "shards/shard-2"].map((dir) => readReport(dir))
);

writeFileSync("merged-report.json", JSON.stringify(report, null, 2));
console.log(`${report.summary.failed} pages failed`);
```

```ts
function mergeReports(inputs: readonly MergeInput[]): Report
```

Combines the reports of sharded or per-project runs into one, removing duplicates. The CLI's `merge-reports` command wraps it.

```ts
function readReport(path: string): MergeInput
```

Read `report.json` from a folder or a file path.

## Configs, plugins and adapters

`defineConfig()`, `definePlugin()` and `defineAdapter()` give the config file,
a plugin and an adapter type checking and completion. `ADAPTERS` lists the
built-in adapters in detection order, most specific first. See
[plugins](https://hydration.jscrate.dev/docs/plugins) for normalizers, cause detectors, route providers and
reporters, and [adapters](https://hydration.jscrate.dev/docs/adapters) for frameworks hydration-proof does
not know.

```ts
function defineConfig(config: HydrationProofConfig): HydrationProofConfig
```

Identity helper that gives `hydration-proof.config.ts` type checking and completion.

```ts
function definePlugin(plugin: HydrationProofPlugin): HydrationProofPlugin
```

Identity helper with type checking for plugins.

```ts
function defineAdapter(definition: AdapterDefinition): Adapter
```

Create an adapter for a framework hydration-proof does not know.

## Issue codes and documentation links

`ISSUES` maps every issue code to its name, title, default severity and
description. `docsUrl()` returns the documentation page of a code:

```ts title="list-codes.ts"
import { docsUrl, ISSUES } from "hydration-proof";

for (const [code, issue] of ISSUES) {
  console.log(`${code} ${issue.severity} ${issue.title} ${docsUrl(code)}`);
}
```

```ts
const ISSUES: ReadonlyMap<IssueCode, IssueDefinition>
```

Every issue code with its name, title, default severity and description. The [issue code reference](https://hydration.jscrate.dev/docs/issues) is generated from it.

```ts
function docsUrl(code: IssueCode): string
```

The documentation URL for an issue code, e.g. `https://hydration.jscrate.dev/docs/issues/hp1001`.

## Schemas and versions

`configJsonSchema()` returns the JSON Schema of the config file, the same one
published at `/schema/config.json` and shipped as
`node_modules/hydration-proof/schema/config.json`. `VERSION` is the installed
version. `REPORT_SCHEMA_VERSION` is the `schemaVersion` written into
`report.json`, `1`, and it changes only in a major release.

```ts
function configJsonSchema(): JsonSchema
```

The JSON Schema of the config file, the same one published at `/schema/config.json`.

```ts
const VERSION: string
```

The installed version of hydration-proof.

```ts
const REPORT_SCHEMA_VERSION = 1
```

The `schemaVersion` written into `report.json`. It changes only in a major release.

## What is stable in the hydration-proof Node API

From 1.0 on, the stable parts follow semver: they change only in a major
release, with a migration path.

- **Stable:** `run()`, `defineConfig`, `definePlugin`, `defineAdapter`,
  `mergeReports`, `ISSUES` and the types they use; the exit codes; and
  `report.json` with `schemaVersion: 1`, where fields are added but never
  removed or repurposed.
- **Not stable:** anything the package does not export; the HTML report's
  markup and embedded data; the wording of messages, the set of `evidence`
  entries and confidence scores; and which cause a finding is given. Treat a
  cause as an explanation for a person, not something to assert on.

A code's default severity can change in a minor release. If your script
depends on what fails, set `failOn` or a budget explicitly. The
[compatibility](https://hydration.jscrate.dev/docs/compatibility) page has the full list.

## Related

- [The CLI](https://hydration.jscrate.dev/docs/cli): the same run from the command line
- [Write a plugin](https://hydration.jscrate.dev/docs/plugins): reporters, detectors and route providers
- [Adapters](https://hydration.jscrate.dev/docs/adapters) for other frameworks
- [The report format](https://hydration.jscrate.dev/docs/reports)
- [Compatibility and stability](https://hydration.jscrate.dev/docs/compatibility)
