# The request lifecycle

Ky runs hooks at specific points as a request becomes a response or a retry. The value a hook returns can replace a request or response, skip work, or start another attempt.

## How the hooks fit together

Configure the lifecycle through [`Hooks`](https://bench-ky-6l.atloria.app/p/bench-ky-6l-QSDF7DxJ6h/developer/ky-hooks#hooks) on a [`ky` instance](https://bench-ky-6l.atloria.app/p/bench-ky-6l-QSDF7DxJ6h/developer/ky#default).

```mermaid
flowchart TD
    A["init hooks: edit options synchronously"] --> B["beforeRequest hooks: edit or replace request"]
    B -->|"Request continues"| C["Fetch"]
    B -->|"Response returned"| F["afterResponse hooks"]
    C --> F
    F -->|"Response or void"| G{"Non-2xx and throwHttpErrors enabled?"}
    F -->|"ky.retry()"| H["Retry decision and delay"]
    G -->|"No"| I["Return response"]
    G -->|"Yes"| H
    H -->|"Retry confirmed"| J["beforeRetry hooks"]
    J --> C
    H -->|"No retry"| K["beforeError hooks"]
    K --> L["Throw error"]
```

`init` edits the options before Ky constructs the request. `beforeRequest` runs once, before retry handling begins. If it returns a `Request`, that becomes the outgoing request and the remaining `beforeRequest` hooks still run. If it returns a `Response`, Ky skips the network request and the remaining `beforeRequest` hooks. Returning nothing leaves the request flow unchanged.

After a response arrives, `afterResponse` receives a clone, so a hook can inspect its body without consuming the response Ky continues with. Returning a `Response` replaces the current response; returning nothing keeps it. A non-2xx response is checked after these hooks. When HTTP errors are enabled, Ky retries eligible failures before it ultimately throws an [`HTTPError`](https://bench-ky-6l.atloria.app/p/bench-ky-6l-QSDF7DxJ6h/developer/ky-httperror#httperror).

`beforeRetry` runs only after Ky has decided to retry, with a retry count of at least one. A returned `Request` replaces the retry request; a returned `Response` supplies the response instead of another fetch. Returning `ky.stop` stops the retry flow without throwing and leaves no response to read, so body shortcuts such as `.text()` are not suitable for that case. `beforeError` runs as Ky is about to throw an error; return an `Error` to keep or replace the error that reaches the caller. An exception from an `init` hook is synchronous and bypasses `beforeError`.

## Keep hook errors out of retry handling

For how `shouldRetry`, `beforeRetry`, and returned requests or responses shape the flow, see [How Ky works](https://bench-ky-6l.atloria.app/p/bench-ky-6l-QSDF7DxJ6h/developer/how-ky-works). This page adds the error boundary: exceptions from `beforeRequest` and `afterResponse` do not trigger retries, while an `init` exception propagates synchronously and bypasses `beforeError`.

For retry eligibility and delay behavior, see [Retry failed requests](https://bench-ky-6l.atloria.app/p/bench-ky-6l-QSDF7DxJ6h/developer/retry-failed-requests). For handling the errors that leave the lifecycle, see [Ky errors and validation](https://bench-ky-6l.atloria.app/p/bench-ky-6l-QSDF7DxJ6h/developer/ky-errors-and-validation).
