{"record":{"id":"19c2d72a7d3d6c50","repo":"dotnet/runtime","slug":"please-install-node-fetch-and-node-abort-contro-19c2d7","errorCode":null,"errorMessage":"Please install `node-fetch` and `node-abort-controller` npm packages to enable HTTP client support.","messagePattern":"Please install `node-fetch` and `node-abort-controller` npm packages to enable HTTP client support\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/http.ts","lineNumber":20,"sourceCode":"// The .NET Foundation licenses this file to you under the MIT license.\n\nimport BuildConfiguration from \"consts:configuration\";\n\nimport type { VoidPtr, ControllablePromise } from \"./types\";\n\nimport { wrapAsCancelablePromise } from \"./cancelable-promise\";\nimport { ENVIRONMENT_IS_NODE } from \"./per-module\";\nimport { assertJsInterop } from \"./utils\";\nimport { MemoryViewType, Span } from \"./marshaled-types\";\nimport { dotnetLogger, dotnetAssert } from \"./cross-module\";\n\n\nfunction verifyEnvironment() {\n    if (typeof globalThis.fetch !== \"function\" || typeof globalThis.AbortController !== \"function\") {\n        const message = ENVIRONMENT_IS_NODE\n            ? \"Please install `node-fetch` and `node-abort-controller` npm packages to enable HTTP client support.\"\n            : \"This browser doesn't support fetch API. Please use a modern browser. See also https://learn.microsoft.com/aspnet/core/blazor/supported-platforms\";\n        throw new Error(message);\n    }\n}\n\nfunction commonAsserts(controller: HttpController) {\n    if (BuildConfiguration !== \"Debug\") {\n        return;\n    }\n    assertJsInterop();\n    dotnetAssert.check(controller, \"expected controller\");\n}\n\nexport function httpSupportsStreamingRequest(): boolean {\n    // Detecting streaming request support works like this:\n    // If the browser doesn't support a particular body type, it calls toString() on the object and uses the result as the body.\n    // So, if the browser doesn't support request streams, the request body becomes the string \"[object ReadableStream]\".\n    // When a string is used as a body, it conveniently sets the Content-Type header to text/plain;charset=UTF-8.\n    // So, if that header is set, then we know the browser doesn't support streams in request objects, and we can exit early.\n    // Safari does support streams in request objects, but doesn't allow them to be used with fetch, so the duplex option is tested, which Safari doesn't currently support.","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/dotnet/runtime/blob/60108ba66eb7d1d12f595480091b4ad80a24b172/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/http.ts#L2-L38","documentation":"verifyEnvironment() checks that globalThis.fetch and globalThis.AbortController exist before any HttpClient operation. On Node.js (ENVIRONMENT_IS_NODE) these are absent in older Node versions, so the message instructs installing node-fetch and node-abort-controller as polyfills. httpCreateController() and httpFetch() both call verifyEnvironment().","triggerScenarios":"Using System.Net.Http.HttpClient (or HttpClientHandler streaming) from .NET WASM running under Node.js where global fetch/AbortController are missing, without having loaded the polyfills onto globalThis first.","commonSituations":"Server-side prerendering or SSR of a Blazor/JSInterop app under Node < 18; unit tests that run the WASM runtime in Node without installing fetch polyfills; an export-to-PDF or background service that uses HttpClient from .NET-in-Node.","solutions":["Install the polyfills: `npm install node-fetch node-abort-controller` and import them so they attach to globalThis before the runtime initializes (import 'node-abort-controller/polyfill'; import { fetch } from 'node-fetch'; globalThis.fetch = fetch;).","Upgrade to Node.js >= 18 where fetch and AbortController are available globally and the polyfills are unnecessary.","If you cannot install packages, gate HttpClient usage to environments that have fetch, and avoid HTTP calls from Node-hosted WASM."],"exampleFix":"// before\n// Node 16, .NET WASM calls HttpClient -> throws\n\n// after (Node < 18)\nimport 'node-abort-controller/polyfill';\nimport { fetch, Headers, Request, Response } from 'node-fetch';\nglobalThis.fetch = fetch as any;\nglobalThis.Headers = Headers as any;\nglobalThis.Request = Request as any;\nglobalThis.Response = Response as any;\n// then boot the runtime","handlingStrategy":"validation","validationCode":"// Detect missing fetch/AbortController under Node before booting the runtime.\nfunction ensureFetchPolyfill() {\n  if (typeof globalThis.fetch !== \"function\" || typeof globalThis.AbortController !== \"function\") {\n    // Node < 18: require polyfills\n    require(\"node-abort-controller/polyfill\");\n    const { fetch, Headers, Request, Response } = require(\"node-fetch\");\n    Object.assign(globalThis, { fetch, Headers, Request, Response });\n  }\n}\nensureFetchPolyfill();","typeGuard":"function hasFetch(): boolean {\n  return typeof globalThis.fetch === \"function\" && typeof globalThis.AbortController === \"function\";\n}","tryCatchPattern":"null","preventionTips":["Install node-fetch and node-abort-controller in any Node-hosted WASM project on Node < 18.","Prefer Node >= 18 which has native fetch and AbortController.","Import polyfills before initializing the dotnet runtime.","Gate HttpClient usage behind a hasFetch() feature check."],"tags":["http","node","fetch","polyfill","environment"],"backgroundTag":null,"analyzedSha":"60108ba66eb7d1d12f595480091b4ad80a24b172","analyzedAt":"2026-08-10T18:54:11.478Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}