documentation/9-hooks.md
hooksType: object<string, Function[]>
This option represents the hooks to run. Thrown errors will be automatically converted to RequestError.
initType: InitHook[]
Default: []
(plainRequestOptions: OptionsInit, options: Options) => void
Called with the plain request options, right before their normalization.
The second argument represents the current Options instance.
Note:
- This hook must be synchronous.
Note:
- This is called every time options are merged.
Note:
- The
optionsobject may not have theurlproperty. To modify it, use abeforeRequesthook instead.
Note:
- This hook is called when a new instance of
Optionsis created.- Do not confuse this with the creation of
Requestorgot(…).
Note:
- When using
got(url)orgot(url, undefined, defaults)this hook will not be called.
This is especially useful in conjunction with got.extend() when the input needs custom handling.
For example, this can be used to fix typos to migrate from older versions faster.
import got from 'got';
const instance = got.extend({
hooks: {
init: [
plain => {
if ('followRedirects' in plain) {
plain.followRedirect = plain.followRedirects;
delete plain.followRedirects;
}
}
]
}
});
// Normally, the following would throw:
const response = await instance(
'https://example.com',
{
followRedirects: true
}
);
// There is no option named `followRedirects`, but we correct it in an `init` hook.
Or you can create your own option and store it in a context:
import got from 'got';
const instance = got.extend({
hooks: {
init: [
(plain, options) => {
if ('secret' in plain) {
options.context.secret = plain.secret;
delete plain.secret;
}
}
],
beforeRequest: [
options => {
options.headers.secret = options.context.secret;
}
]
}
});
const {headers} = await instance(
'https://httpbin.org/anything',
{
secret: 'passphrase'
}
).json();
console.log(headers.Secret);
//=> 'passphrase'
beforeRequestType: BeforeRequestHook[]
Default: []
(options: Options, context: BeforeRequestHookContext) => Promisable<void | Response | ResponseLike>
Called right before making the request with options.createNativeRequestOptions().
This hook is especially useful in conjunction with got.extend() when you want to sign your request.
The second parameter is a context object with the following properties:
retryCount - The current retry count (0 for the initial request, 1+ for retries).Note:
- Got will make no further changes to the request before it is sent.
Note:
- Changing
options.jsonoroptions.formhas no effect on the request. You should changeoptions.bodyinstead. If needed, update theoptions.headersaccordingly.
import got from 'got';
const response = await got.post(
'https://httpbin.org/anything',
{
json: {payload: 'old'},
hooks: {
beforeRequest: [
(options, context) => {
options.body = JSON.stringify({payload: 'new'});
options.headers['content-length'] = new TextEncoder().encode(options.body).byteLength.toString();
}
]
}
}
);
You can use context.retryCount to conditionally modify behavior based on whether it's the initial request or a retry:
import got from 'got';
const response = await got('https://httpbin.org/status/500', {
retry: {
limit: 2
},
hooks: {
beforeRequest: [
(options, context) => {
// Only log on initial request, not on retries
if (context.retryCount === 0) {
console.log('Making initial request');
}
}
]
}
});
Tip:
- You can indirectly override the
requestfunction by early returning aClientRequest-like instance or aIncomingMessage-like instance. This is very useful when creating a custom cache mechanism.- Read more about this tip.
beforeRedirectType: BeforeRedirectHook[]
Default: []
(updatedOptions: Options, plainResponse: PlainResponse) => Promisable<void>
The equivalent of beforeRequest but when redirecting.
Tip:
- This is especially useful when you want to avoid dead sites.
- Use this to remove app-specific sensitive headers on redirect. Got already strips
host,cookie,cookie2,authorization, andproxy-authorizationon cross-origin redirects, and strips request body headers when a redirect rewrites the request toGET.
import got from 'got';
const response = await got('https://example.com', {
hooks: {
beforeRedirect: [
(options, response) => {
if (options.hostname === 'deadSite') {
options.hostname = 'fallbackSite';
}
}
]
}
});
beforeRetryType: BeforeRetryHook[]
Default: []
(error: RequestError, retryCount: number) => Promisable<void>
The equivalent of beforeError but when retrying. Additionally, there is a second argument retryCount, the current retry number.
Note:
- When using the Stream API, this hook is ignored.
Note:
- When retrying, the
beforeRequesthook is called afterwards.
Note:
- If no retry occurs, the
beforeErrorhook is called instead.
This hook is especially useful when you want to retrieve the cause of a retry.
import got from 'got';
await got('https://httpbin.org/status/500', {
hooks: {
beforeRetry: [
(error, retryCount) => {
console.log(`Retrying [${retryCount}]: ${error.code}`);
// Retrying [1]: ERR_NON_2XX_3XX_RESPONSE
}
]
}
});
beforeCacheType: BeforeCacheHook[]
Default: []
(response: PlainResponse) => false | void
Called right before the response is cached. Allows you to control caching behavior by modifying response properties or preventing caching entirely.
This is especially useful when you want to prevent caching of specific responses or modify cache headers.
Return value:
false- Prevent caching (remaining hooks are skipped)void/undefined- Use default caching behavior (mutations take effect)
Modifying the response:
- Hooks can directly mutate response properties like
headers,statusCode, andstatusMessage- Mutations to
response.headersaffect how the caching layer decides whether to cache the response and for how long- Changes are applied to what gets cached, not to the response the user receives (they are separate objects)
Note:
- This hook is only called when the
cacheoption is enabled.
Note:
- This hook must be synchronous. It cannot return a Promise. If you need async logic to determine caching behavior, use a
beforeRequesthook instead.
Note:
- When returning
false, remaining hooks are skipped. The response headers the user receives are NOT modified - only the caching layer sees modified headers.
Note:
- If a hook throws an error, it will be propagated and the request will fail. This is consistent with how other hooks in Got handle errors.
Note:
- At this stage, the response body has not been read yet - it's still a stream. Properties like
response.bodyandresponse.rawBodyare not available. You can only inspect/modify response headers and status code.
import got from 'got';
// Simple: Don't cache errors
const instance = got.extend({
cache: new Map(),
hooks: {
beforeCache: [
(response) => response.statusCode >= 400 ? false : undefined
]
}
});
await instance('https://example.com');
import got from 'got';
// Advanced: Modify headers for fine control
const instance2 = got.extend({
cache: new Map(),
hooks: {
beforeCache: [
(response) => {
// Force caching with explicit duration
// Mutations work directly - no need to return
response.headers['cache-control'] = 'public, max-age=3600';
}
]
}
});
afterResponseType: AfterResponseHook[]
Default: []
(response: Response, retryWithMergedOptions: (options: OptionsInit) => never) => Promisable<Response | RequestPromise<Response>>
Each function should return the response. This is especially useful when you want to refresh an access token.
Note:
- When using the Stream API, this hook is ignored.
Note:
- Calling the
retryWithMergedOptionsfunction will triggerbeforeRetryhooks. By default, remainingafterResponsehooks are removed to prevent duplicate execution. To preserve remaining hooks on retry, setpreserveHooks: truein the options passed toretryWithMergedOptions. In case of an error,beforeRetryhooks will be called instead.- Meanwhile, the
init,beforeRequest, andbeforeRedirecthooks, as well as already executedafterResponsehooks, are skipped.
Note:
- To preserve remaining
afterResponsehooks after callingretryWithMergedOptions, setpreserveHooks: truein the options passed toretryWithMergedOptions. This is useful when you want hooks to run on retried requests.
Warning:
- Be cautious when using
preserveHooks: true. If a hook unconditionally callsretryWithMergedOptionswithpreserveHooks: true, it will create an infinite retry loop. Always ensure hooks have proper conditional logic to avoid infinite retries.
import got from 'got';
const instance = got.extend({
hooks: {
afterResponse: [
(response, retryWithMergedOptions) => {
// Unauthorized
if (response.statusCode === 401) {
// Refresh the access token
const updatedOptions = {
headers: {
token: getNewToken()
}
};
// Update the defaults
instance.defaults.options.merge(updatedOptions);
// Make a new retry
return retryWithMergedOptions(updatedOptions);
}
// No changes otherwise
return response;
}
],
beforeRetry: [
error => {
// This will be called on `retryWithMergedOptions(...)`
}
]
},
mutableDefaults: true
});
Example with preserveHooks:
import got from 'got';
const instance = got.extend({
hooks: {
afterResponse: [
(response, retryWithMergedOptions) => {
if (response.statusCode === 401) {
return retryWithMergedOptions({
headers: {
authorization: getNewToken()
},
preserveHooks: true // Keep remaining hooks
});
}
return response;
},
(response) => {
// This hook will run on the retried request
// (the original request is interrupted when the first hook triggers a retry)
console.log('Response received:', response.statusCode);
return response;
}
]
}
});
beforeErrorType: BeforeErrorHook[]
Default: []
(error: RequestError) => Promisable<Error>
Called with a RequestError instance. The error is passed to the hook right before it's thrown.
This hook can return any Error instance, allowing you to:
RequestError with additional propertiesError instances when you don't need Got-specific error informationThis is especially useful when you want to have more detailed errors or maintain backward compatibility with existing error handling code.
import got from 'got';
// Modify and return the error
await got('https://api.github.com/repos/sindresorhus/got/commits', {
responseType: 'json',
hooks: {
beforeError: [
error => {
const {response} = error;
if (response && response.body) {
error.name = 'GitHubError';
error.message = `${response.body.message} (${response.statusCode})`;
}
return error;
}
]
}
});
// Return a custom error class
class CustomAPIError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'CustomAPIError';
this.statusCode = statusCode;
}
}
await got('https://api.example.com/endpoint', {
hooks: {
beforeError: [
error => {
// Return a custom error for backward compatibility with your application
return new CustomAPIError(
error.message,
error.response?.statusCode
);
}
]
}
});