Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

In modern Node.js, send custom request headers by passing a headers object to fetch(). For lower-level control, pass headers to http.request() or call req.setHeader() before the request is sent. Header names are case-insensitive, existing values are replaced when set again, and arrays are used when a protocol requires repeated values.

Send headers with the built-in fetch API

Node.js includes a web-standard fetch API. Put authentication, tracing, content negotiation, and other metadata in the request options:

const token = process.env.API_TOKEN;
const traceId = crypto.randomUUID();

const response = await fetch('https://api.example.com/data', {
  headers: {
    Authorization: `Bearer ${token}`,
    'X-Trace-Id': traceId,
    Accept: 'application/json'
  }
});

if (!response.ok) {
  throw new Error(`Request failed: ${response.status} ${response.statusText}`);
}

const data = await response.json();
console.log(data);

In an ES module, import the UUID helper when needed:

import crypto from 'node:crypto';

If your project uses CommonJS, use const crypto = require('node:crypto');. The headers option accepts a plain object or a Headers instance.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

POST, PUT, and JSON bodies

The header mechanism is the same for every HTTP method. Add method, serialize the body, and identify its media type:

const response = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.API_TOKEN}`,
    'Content-Type': 'application/json',
    Accept: 'application/json'
  },
  body: JSON.stringify({ name: 'Ada', role: 'admin' })
});

const text = await response.text();
console.log(response.status, text);

Do not put a JavaScript object directly in body; convert JSON with JSON.stringify(). Keep tokens out of source control and avoid logging the complete headers object.

Using a Headers instance

const headers = new Headers({
  Authorization: `Bearer ${process.env.API_TOKEN}`,
  Accept: 'application/json'
});
headers.set('X-Client-Version', '2');

const response = await fetch('https://api.example.com/data', { headers });

Headers normalizes names and provides methods such as set(), append(), get(), and has(). Whether a repeated value is valid depends on the header’s protocol rules; do not use repetition merely to work around a typo.

Use node:http when you need lower-level control

The node:http module exposes the request stream and callback events. Supply a headers object in the options:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import http from 'node:http';

const token = process.env.API_TOKEN;
const req = http.request('http://localhost:3000/resource', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${token}`,
    'X-Trace-Id': 'trace-123',
    Accept: 'application/json'
  }
}, (res) => {
  let body = '';
  res.setEncoding('utf8');
  res.on('data', chunk => { body += chunk; });
  res.on('end', () => {
    console.log(res.statusCode, body);
  });
});

req.on('error', console.error);
req.end();

Always finish a request. Calling req.end() sends a request with no body; for a body, write it before ending.

Set a header after creating the request

import http from 'node:http';

const req = http.request('http://localhost:3000/resource', (res) => {
  res.pipe(process.stdout);
});

req.setHeader('X-Trace-Id', 'trace-123');
req.setHeader('Authorization', `Bearer ${process.env.API_TOKEN}`);
req.end();

request.setHeader(name, value) sets one outgoing value. If the same header already exists, the new value replaces it. Configure every header before the request is sent; changing a queued header after headers have been flushed is too late.

Repeated headers, cookies, and replacement rules

Node treats ordinary header-name lookup case-insensitively. Content-Type, content-type, and CONTENT-TYPE identify the same header. Setting a name again replaces its value rather than creating an accidental duplicate.

When a protocol explicitly allows multiple values with the same name, pass an array of strings:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
req.setHeader('Cookie', [
  'type=ninja',
  'language=javascript'
]);

Use this pattern only for headers whose wire format supports repetition. Combining values with commas is not interchangeable for every header, and multiple Authorization values are normally invalid.

Inspect headers before they leave a node:http request

Lower-level requests provide methods that make configuration errors visible:

import http from 'node:http';

const req = http.request('http://localhost:3000/resource', {
  headers: { 'X-Debug': 'one' }
}, res => {
  res.resume();
});

console.log(req.getHeaders());
console.log(req.getHeaderNames());
console.log(req.getHeader('x-debug'));
console.log(req.hasHeader('X-Debug'));
console.log(req.getRawHeaderNames());
req.end();

getHeaders() returns the queued values, while getHeaderNames() lists ordinary names. getRawHeaderNames() preserves the casing used when each name was set. Ordinary lookup remains case-insensitive.

This inspection proves what Node queued, not necessarily what a proxy forwarded or what the destination accepted. For fetch, confirm receipt at a server you control or with a controlled request-inspection endpoint. Never print bearer tokens, cookies, or other secrets in production logs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing fetch or node:http

Need fetch node:http
Simple promise-based request Compact, web-standard API Requires callbacks and stream handling
Direct request-stream control Higher-level abstraction Explicit request methods and events
Repeated values Uses the Headers abstraction and protocol handling Arrays explicitly send multiple values with one name
Client-side header inspection Usually verify at the server or network boundary getHeaders(), getHeaderNames(), hasHeader(), and related methods
Portability Follows the web Fetch API shape Specific to Node’s HTTP stack

Use fetch for most new application and service code. Choose node:http when you need stream-level behavior, callback events, or explicit inspection of queued headers.

Header values, encoding, and security limits

  • Header values are converted for network transmission. Invalid characters in a string can cause Node to throw.
  • Do not place newlines or untrusted raw text in a header value; validate user-controlled input before constructing headers.
  • For UTF-8 filename parameters, use the standards-based encoding required by the relevant protocol rather than inserting arbitrary Unicode into a legacy parameter.
  • Request headers describe what your client sends. They are different from response headers: req.setHeader() configures an outgoing request, while a Node server uses res.setHeader() for its response.
  • Authentication headers do not override TLS requirements. Send credentials over HTTPS and avoid exposing them in error messages, URLs, or debug output.

Common failures and fixes

The server says the header is missing

  • Check that the header is inside the headers option, not beside it.
  • With node:http, call setHeader() before req.end() and before any operation that flushes the request.
  • Inspect req.getHeaders(), then verify at the receiving server. A redirect, reverse proxy, or gateway may alter what arrives.

A second value did not appear

Calling setHeader() twice replaces the first value. Use an array only when the protocol permits repeated fields, such as multiple cookies.

Authentication fails despite a token

  • Use the scheme expected by the API, commonly Authorization: Bearer TOKEN.
  • Confirm the token is not undefined because an environment variable was absent.
  • Check that a redirect or proxy is not removing credentials when crossing origins. Follow the API’s redirect and authentication rules rather than blindly forwarding secrets.

Node throws an invalid-header error

Inspect the value for control characters, accidental line breaks, or unvalidated user input. Header values must be valid for HTTP transmission.

The request hangs

Ensure req.end() is called, consume or resume the response stream, and attach an error listener. With fetch, set an appropriate timeout strategy for your application and handle rejected promises.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The response body is empty or truncated

For node:http, wait for the end event or pipe the response. For JSON with fetch, call response.json() only after checking that the server returned a compatible body and status.

Test a custom header with a tiny local server

A local receiver lets you distinguish client configuration from a remote gateway problem:

import http from 'node:http';

const server = http.createServer((req, res) => {
  console.log(req.headers);
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ received: req.headers['x-trace-id'] ?? null }));
});

server.listen(3000, () => console.log('Listening on http://localhost:3000'));

Run the server, send the earlier request to http://localhost:3000/resource, and check the printed lower-case key. Node’s incoming-header object uses normalized names, so casing in the original source is not a reliable way to test whether a field arrived.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and reliability considerations

  • Keep a single, reusable header-construction function so authentication and tracing rules are consistent across calls.
  • Do not create large diagnostic headers; intermediaries impose size limits and may reject requests before your application sees them.
  • Use connection reuse and an appropriate HTTP client strategy for high-volume workloads, but treat retries carefully: replaying a request with an expired token or a non-idempotent body can create a second side effect.
  • Log status, request identifiers, and timing, not secret header values. Correlate failures with a trace ID that is safe to record.
  • Test through the same proxy, load balancer, or service mesh used in production when header behavior matters; local success does not prove an intermediary will preserve every field.

Or skip the browser setup

If your goal is to send headers while capturing a page rather than to build an HTTP client, ScreenshotNeo accepts custom headers directly in one screenshot request. It also removes cookie-consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Its MCP server gives Claude, Cursor, and other MCP clients screenshot, page-info, and PDF tools.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Here is the Node.js call (see the ScreenshotNeo documentation for all parameters):

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
const buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', buffer));

The same endpoint can be called with cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Or Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

ScreenshotNeo includes 1,000 screenshots a month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Can I use lowercase header names?

Yes. HTTP header names are case-insensitive, and Node’s ordinary lookup treats different casing as the same name.

Does setting a header twice append it?

No. The later value replaces the earlier one. Use an array only when repeated fields are valid for the protocol.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why can a header visible in Node still be absent at the API?

Node may have queued it correctly while a redirect, proxy, gateway, or destination policy changes or rejects it. Verify at the receiving boundary.

Frequently Asked Questions

Should new Node.js code use fetch or node:http for custom headers?

Use the built-in fetch API for most requests. Choose node:http when you need direct request-stream control, callback events, or detailed inspection of queued headers.

How do I send multiple Cookie headers?

With node:http, pass an array such as req.setHeader('Cookie', ['type=ninja', 'language=javascript']), provided the target protocol accepts repeated values.

Can I change headers after calling req.end()?

No. Configure headers before the request is sent; after they are flushed, changing the queued value is too late.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.