A transformation runs your own JavaScript on every event before it's delivered. It can reshape the payload and headers, verify and enrich events, or decode and generate content such as PDFs, archives and images. The runtime is compatible with Node.js and runs npm libraries, so processing you would otherwise deploy as a separate function can run in Hookdeck, as long as it doesn't need network access.

How transformations work

Here are a few common uses:

  • Change payload formats: For example, converting XML to JSON
  • Unify models across sources: For example, conforming WooCommerce orders and Shopify orders to a single standard
  • Add compatibility when routing to an API: For example, adding keys and reformatting the payload
  • Add additional properties to payloads: For example, to more clearly indicate the event type for use in a filter or in your own application logic.
  • Verify tokens: For example, decoding a JWT and checking its signature, expiry and audience before delivery
  • Work with binary payloads: For example, decompressing a gzip file, extracting data from a PDF, or generating an image

Transformations and filters can be executed in any order. You can configure the execution order by dragging them in the UI or ordering them in the rules[] array in the API.

How to Order Transformations & Filters

Learn how to control the order of transformations and filters in your connections.

These guides walk through common transformations:

Extract Text and Data from a PDF

Turn an application/pdf request into a JSON event with bundled npm libraries.

Decode and Verify JWTs

Verify bearer tokens with Node.js crypto or jose, and filter out requests with invalid tokens.

Decompress Gzip and Zip Payloads

Turn compressed requests into JSON events, or compress payloads before delivery.

Transform Multipart File Uploads

Read, replace, add, and remove files in multipart/form-data requests.

Generate an Image from Text

Turn a JSON request into a PNG image delivered as image/png.

Add a transformation via the API

You can create a transformation and add it to a connection programmatically. This is a two-step process: first create the transformation, then add it as a rule on a connection.

Step 1: Create the transformation

curl -X POST "https://api.hookdeck.com/2026-09-01/transformations" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "add-event-type-header",
    "code": "addHandler(\"transform\", (request, context) => { request.headers[\"x-event-type\"] = request.body.type; return request; });"
  }'

Step 2: Add the transformation rule to a connection

hookdeck connection upsert my-connection \
  --rules-file rules.json

Where rules.json contains:

[
  {
    "type": "transformation",
    "transformation_id": "trs_123456789"
  }
]

See the CLI Connection Commands reference for all available options.

curl -X PUT "https://api.hookdeck.com/2026-09-01/connections" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-connection",
    "source": {
      "name": "my-source"
    },
    "destination": {
      "name": "my-destination"
    },
    "rules": [
      {
        "type": "transformation",
        "transformation_id": "trs_123456789"
      }
    ]
  }'

Syntax

Hookdeck allows for arbitrary transformations on request data using JavaScript (ES6). For a complete overview, see transformations syntax.

Limitations

Transformations have some important limitations to keep in mind.

  • The transformation runtime cannot perform any IO, or access any external resources such as the network or file system. fetch isn't available, and modules such as fs, http, net and child_process can be imported, but their functions throw.
  • Only Node.js built-in modules can be imported. To use npm libraries, bundle them into your code.

Each execution is also bounded by these limits. Async handlers, promises and timers are supported, and time spent awaiting counts toward the execution time.

LimitValue
Execution time1 second, including time spent awaiting
Memory128 MB per transformation
Code size, including bundled dependencies5 MB
Binary body exposed to the code20 MiB. Larger bodies arrive as null and are delivered unchanged.
A single Buffer, or the output of one decompression call32 MiB
Input to one compression call8 MiB
Brotli compression at quality 10 or higher128 KiB of input. Node.js defaults to quality 11, so pass a lower BROTLI_PARAM_QUALITY for larger inputs.
pbkdf2Iterations multiplied by output blocks up to 600,000
scrypt128 * N * r * p up to 64 MiB
RSA keysUp to 8,192 bits, and up to 3,072 bits for key generation

Large bundles take longer to load the first time they run, roughly 30 ms per MB of code. Minify bundles that include large libraries.

Environment

Hookdeck sandboxes your transformation process within a V8 runtime environment known as an isolate. Isolates allow multiple JavaScript VM instances to operate in parallel, sharing low-level resources while remaining fundamentally distinct and secure.

The runtime implements Node.js built-in modules and web APIs inside the isolate, so code written for Node.js runs as long as it doesn't need I/O. In the event that memory or time limits are exceeded, the isolate will terminate the execution of your code.

Your code can use Node.js built-in modules such as crypto and zlib, web globals such as Buffer and URL, async handlers, ES modules, and npm libraries you bundle into it. See Node.js compatibility for the modules, globals and tested npm libraries, how to bundle dependencies, and known gaps.

Transformations syntax

Hookdeck allows for arbitrary transformations on request data using JavaScript (ES6).

Handler function

The transformation handler is used to apply changes to a request. For example, the transformation code below adds a new header to the payload.

addHandler("transform", (request, context) => {
  request.headers["example-header"] = "Hello World";
  return request;
});

The handler can be async or return a promise.

Let's take a look at the handler function's component parts.

Request object

request is the request object received on the connection. Its structure is denoted below.

{
  headers: { [key: string]: string };
  body: string | boolean | number | object | Buffer | null;
  query: string;
  parsed_query: object;
  path: string;
}

JSON and form payloads arrive as parsed objects, and text payloads as strings.

Binary payloads, such as application/octet-stream, PDFs, images, archives, and requests sent with Content-Encoding: gzip, arrive as a Buffer. Binary bodies larger than 20 MiB arrive as null.

For binary-safe multipart requests, request.body contains parsed text fields and file metadata. Each file also has content, a Buffer holding its bytes, so you can read, replace, add, or remove files. See transform multipart file uploads.

Context object

context is an object containing the connection information. Its structure is denoted below.

{
  connection: Connection;
}

Where Connection is an object, as defined in the response of the GET /connections/:id endpoint.

Return value

The handler must return a valid request object, containing a valid content-type header.

The parsed_query object in the returned request can safely be ignored – Hookdeck will recreate this parsed query on its own.

The type of the returned body, together with the content-type header, decides what Hookdeck delivers:

Returned bodyResult
Bytes: a Buffer, Uint8Array or ArrayBufferA binary payload, delivered byte for byte with the returned content-type, or application/octet-stream if there is none. This works for any request, including text, JSON and multipart requests.
A stringA text payload, delivered with the returned content-type. For text, JSON and multipart requests, any content type works, such as text/csv, application/yaml, application/javascript, application/graphql, or a vendor type.
An object or arrayA JSON, form, or multipart payload, depending on the content-type. With any other content type, the transformation fails with Body must be a string for <type>. Serialize it yourself, return bytes (a Buffer) for a binary payload, or use a JSON content type.

Bytes returned with a textual content-type (text/*, JSON, XML, application/x-www-form-urlencoded or application/jwt) become a text payload when they're valid UTF-8, and valid JSON for JSON types. Bytes that don't decode, that declare a charset other than UTF-8, or that are returned with a content-encoding header stay binary.

For a binary request:

  • Returning the body unchanged, or null, keeps the original bytes.
  • Returning a string or object converts the payload to text. Set a textual content-type with it, such as application/json or text/plain; leaving a binary content type fails the transformation.

For example, this transformation turns a JSON list of orders into CSV:

addHandler('transform', (request) => {
  const rows = request.body.orders.map((order) => `${order.id},${order.total}`);
  request.body = ['id,total', ...rows].join('\n');
  request.headers['content-type'] = 'text/csv';
  return request;
});

The following transformation formats an order based on its source:

const formatOrder = (order, type) => {
  if (type === 'shopify') {
    // Format for shopify here
    return order;
  } if (type === 'woocommerce') {
    // Format for woocomerce here
    return order;
  }
}

// Handle that formats the order from Shopify and Woocommerce
addHandler('transform', (request, context) => {
  const { body, headers, path, query } = request;
  const { connection } = context;
  return {
    body: formatOrder(body, connection.source.name),
    headers,
    path,
    query
  };
});

Environment variables

Transformations support environment variables for storing secrets. Access your variable on the process.env object. For example, to access a variable called TEST use process.env.TEST.

Environment variables are managed in the transformations editor via the Variables drop down.

Manage transformation environment variables

Create a transformation

Transformations are applied to a connection, just like any other rule. But unlike other rules, transformations can be shared between multiple connections.

To version control your transformations, consider creating or updating your connection using the API or the Hookdeck Terraform provider.

  1. Open the connection rules configuration.

  2. Click Transform to add a transformation rule.

  3. Click Create new transformation.

  4. Configure the transformation using the supported transformation syntax in the right-hand pane.

  5. Once written, test your transformation.

    • The left pane's Input tab shows the request your transformation will be tested against. To change this request, click Change and select a recent payload, or edit the text directly.
    • Click Run to test the transformation. Once run, the left pane's Output tab shows the resulting payload, whereas the Diff tab shows the difference between the test input and test output.
  6. Name the transformation using the input field in the upper left, clicking Confirm to update the name.

  7. Once satisfied with the transformation, click Confirm.

  8. Make sure to click Save again on the connection form to apply your changes.

From this point forward, events received on the connection are transformed prior to delivery to their destination.

Edit a transformation

Editing a transformation changes how payload data is transformed before delivery.

  1. Open the connection rules configuration.

  2. Next to the transformation rule, click Editor.

  3. Configure the transformation using the supported transformation syntax in the right-hand pane.

  4. Once written, test your transformation.

    • The left pane's Input tab shows the request your transformation will be tested against. To change this request, click Change and select a recent payload, or edit the text directly.
    • Click Run to test the transformation. Once run, the left pane's Output tab shows the resulting payload, whereas the Diff tab shows the difference between the test input and test output.
  5. Optionally, you may rename the transformation using the input field in the upper left. Click Confirm to confirm the name change.

  6. Once satisfied, click Confirm.

  7. Make sure to click Save again on the connection form to apply your changes.

Transformations are updated immediately, and any events received going forward are run through the updated transformation.

Keep in mind that updating a transformation will affect all connections that use the same transformation. If this is not your desired behavior, you can delete a transformation and create a new one.

Delete a transformation

A Transformation can only be deleted if it is not being used by any Connections.

To remove a transformation from a connection, follow the instructions for configuring connection rules and click the trash icon to remove the transformation from the connection rules.

Once a Transformation has been removed from all Connections, click on Transformations in the sidebar under Connections and select the transformation you wish to delete. Scroll to the bottom of the page and click Delete. Confirm the deletion by clicking Delete in the modal that appears.

Troubleshoot a transformation

Execution logs are kept for every transformation, in order to facilitate troubleshooting.

  1. Open the connection rules configuration.

  2. Next to the transformation rule, click Editor.

  3. Click View Executions.

  4. Click an event to open its transformation execution.

    • The Input tab shows the unmodified request received by Hookdeck.
    • The Output tab shows the request after the transformation was applied.
    • The Diff tab shows the difference between the input and output requests.
    • The Console section shows any lines logged to the console in the course of applying the transformation.

Transformation issues

When your transformation fails Hookdeck will log a FATAL execution which will appear in the logs described above. Additionally, a new Issue will be opened. An issue will also be opened if you transformation does execute but logs a warning or an error with console.error or console.warn.

In the case of a fatal failure, Events may have not been created. Hookdeck keeps tracks of those as Ignored Events in the associated issue. Ignored events can be inspected and retried directly within the issue.

  1. Open the Issue page.
  2. Open the issue of type transformation
  3. Inspect the histogram of "Ignored Events" to see the number of events that were not created.
  4. Select the latest execution and click "Edit"
  5. Make the necessary changes to your transformation code and click "run" to validate that the transformation issue as been resolved.
  6. Go back to the original issue, click and submit the "Bulk Retry".

Node.js compatibility

Transformations run in a sandboxed V8 isolate that implements Node.js built-in modules and web APIs, so code written for Node.js runs unchanged as long as it doesn't need I/O. Two rules apply:

  • No I/O. Transformations can't reach the network or the filesystem, or start processes. fetch isn't defined.
  • Only built-in modules can be imported. To use an npm library, bundle it into your transformation code.

Built-in modules

Import built-in modules with require or import, with or without the node: prefix. require('zlib') and import { gunzipSync } from 'node:zlib' load the same module.

ModuleAvailability
bufferBuffer, Blob, File, atob, btoa, isUtf8, isAscii, transcode
cryptoHash and Hmac, Cipheriv and Decipheriv, Sign and Verify, sign/verify, KeyObject (PEM, DER, JWK), public and private encrypt/decrypt, pbkdf2, scrypt, hkdf, generateKeyPair(Sync), ECDH, diffieHellman, X509Certificate, random*, timingSafeEqual, webcrypto and subtle. Not supported: Diffie-Hellman groups and the legacy createCipher.
zlibgzip, deflate, deflateRaw and brotli, with their inverses and unzip, as sync, callback and promisified functions; crc32; stream classes (buffered)
utilinspect, format, types, promisify, callbackify, inherits, deprecate, isDeepStrictEqual, styleText, parseEnv, TextEncoder, TextDecoder
eventsEventEmitter, once, on, getEventListeners, errorMonitor
stream, stream/promisesReadable, Writable, Transform, Duplex, PassThrough, pipeline, finished (Node.js 18 streams)
string_decoder, punycode, querystringFull module
pathPOSIX paths only. path.win32 isn't available.
urlWHATWG URL and URLSearchParams, plus the legacy parse, format and resolve
assert, assert/strictFull module
timers, timers/promisesBounded by the execution timeout
os, process, tty, module, perf_hooks, console, diagnostics_channelStatic values. Nothing describes the host.
async_hooksAsyncLocalStorage and AsyncResource, with synchronous context propagation only
fs, http, https, net, child_process, dns, vm, worker_threads, and other I/O modulesImportable, so libraries that reference them still load. Calling any of their functions throws.

Calling a function from an I/O module fails the transformation with an error like this one:

Error: node:fs.readFileSync is not available in transformations: they run without filesystem, network or process access.

Pure helpers and constants on those modules still work, such as net.isIP and http.METHODS, and you can construct an http.Agent.

Globals

These globals are available without an import:

  • Buffer, TextEncoder, TextDecoder, atob, btoa
  • URL, URLSearchParams
  • crypto (WebCrypto, including crypto.subtle and crypto.randomUUID)
  • structuredClone, queueMicrotask
  • AbortController, AbortSignal, Event, EventTarget, CustomEvent, DOMException
  • Blob, File, Headers, FormData, navigator, performance
  • Web streams: ReadableStream, WritableStream, TransformStream, TextEncoderStream, TextDecoderStream, CompressionStream, DecompressionStream
  • setTimeout, setInterval, setImmediate and their clear* functions
  • process, with process.env holding your environment variables
  • WebAssembly

fetch, WebSocket, MessageChannel and BroadcastChannel aren't available.

Dynamic import() of a built-in module works, for example await import('node:crypto').

Script and module formats

A transformation can be a classic script that registers its handler with addHandler:

const { createHmac } = require('node:crypto');

addHandler('transform', async (request, context) => {
  request.headers['x-signature'] = createHmac('sha256', process.env.SIGNING_SECRET)
    .update(JSON.stringify(request.body))
    .digest('hex');
  return request;
});

Code with a top-level import or export runs as an ES module. Export the handler as export default { transform }, as a default function, or as a named transform function:

import { createHash } from 'node:crypto';

export default {
  transform(request, context) {
    request.headers['x-body-sha256'] = createHash('sha256')
      .update(JSON.stringify(request.body))
      .digest('hex');
    return request;
  },
};

Modules support top-level await, and import.meta.url is defined.

Async code and timers

Handlers can be async or return a promise. Timers and node:timers/promises work.

The whole execution is limited to 1 second, including time spent awaiting promises and timers. A transformation that runs longer fails with Script execution timed out.

Bundle dependencies

Transformations can't install packages. To use an npm library, bundle it and its dependencies into a single file with esbuild, then use the bundle as your transformation code.

  1. Install the library and esbuild:

    npm install fast-xml-parser
    npm install --save-dev esbuild
    
  2. Write the transformation as an ES module that imports the library:

    import { XMLParser } from 'fast-xml-parser';
    
    const parser = new XMLParser({ ignoreAttributes: false });
    
    export default {
      transform(request) {
        request.body = parser.parse(request.body);
        request.headers['content-type'] = 'application/json';
        return request;
      },
    };
    
  3. Bundle it:

    npx esbuild transform.js --bundle --format=esm --platform=node --outfile=dist/transform.js
    

    --platform=node leaves imports of Node.js built-ins in place, so the bundle uses the runtime's built-in modules. Add --minify to make the bundle smaller.

  4. Paste the contents of dist/transform.js into the transformation editor, or upload the file with the Hookdeck CLI:

    hookdeck gateway transformation upsert xml-to-json --code-file dist/transform.js
    

If you prefer an addHandler script, require the library and bundle with --format=cjs instead:

const { XMLParser } = require('fast-xml-parser');

const parser = new XMLParser({ ignoreAttributes: false });

addHandler('transform', (request) => {
  request.body = parser.parse(request.body);
  request.headers['content-type'] = 'application/json';
  return request;
});
npx esbuild transform.js --bundle --format=cjs --platform=node --outfile=dist/transform.js

Importing an npm package without bundling it fails with:

Error: Cannot find module 'lodash'. Only Node.js built-in modules (e.g. 'node:crypto') can be required in a transformation; bundle other dependencies into your code.

When a library offers both a synchronous and an asynchronous API, prefer the synchronous one. It's simpler to reason about within the 1-second limit, and some asynchronous APIs rely on worker threads, which aren't available.

npm library compatibility

Hookdeck tests popular I/O-free npm libraries against the transformation runtime. Each library is bundled with esbuild the way you would bundle it, both as an ES module (ESM, --format=esm) and as an addHandler script (CJS, --format=cjs). The bundle then runs in the transformation runtime and in Node.js, and the test passes only if both produce exactly the same output.

60 of the 61 library cases pass. A library that isn't listed may still work: if it doesn't need I/O, bundle it and test it in the transformation editor.

Auth and crypto

LibraryVersionESMCJSTested
jsonwebtoken9.0.3YesYesHS256, RS256 (PKCS#1 PEM) and ES256 sign, verify and decode
jose6.2.3YesYesSignJWT and jwtVerify, JWE compact encrypt and decrypt, PKCS#8 and SPKI import, JWK export and thumbprint
uuid14.0.0YesYesv1, v3, v4, v5, v6, v7, parse, stringify, validate, version
nanoid3.3.8YesYesnanoid, customAlphabet, non-secure variant
ulid3.0.2YesYesulid, monotonicFactory, decodeTime, isValid
crypto-js4.2.0YesYesMD5, SHA1, SHA256, SHA512, SHA3, HmacSHA256, PBKDF2, AES encrypt and decrypt, WordArray.random
bcryptjs3.0.3YesYesgenSaltSync, hashSync, compareSync, getRounds, getSalt
@noble/hashes2.4.0YesYessha256, sha512, sha3, keccak, blake2b, blake3, hmac, hkdf, pbkdf2, scrypt
@noble/curves2.4.0YesYesed25519, secp256k1 and P-256 keys, sign, verify
tweetnacl1.0.3YesYesdetached sign, box, secretbox, hash, key pair from seed
standardwebhooks1.0.0YesYesStandard Webhooks signing and verification
stripe (webhooks)22.6.2YesYesDefault Node.js build: webhooks.constructEvent and constructEventAsync, test header generation
stripe (webhooks, worker build)22.6.2YesYesworker export condition: SubtleCrypto provider, constructEventAsync
node-forge1.4.0YesYessha256, HMAC, PBKDF2, AES-GCM cipher, PEM key parsing, random bytes
hash-wasm4.12.0YesYesWebAssembly hashes: sha256, xxhash64, blake3, HMAC, argon2id
object-hash3.0.0YesYesHashing objects, Maps, Sets and Dates; key hashing
@octokit/webhooks-methods6.0.0YesYesGitHub webhook signing and verification

XML, CSV and text formats

LibraryVersionESMCJSTested
fast-xml-parser5.9.2YesYesXMLParser, XMLBuilder, XMLValidator
xml2js0.6.2YesYesparseString (callback and promise), Builder, parse errors
xml-js1.6.11YesYesxml2js, xml2json, js2xml
xmlbuilder24.0.3YesYescreate, ele, txt, dat, convert
papaparse5.7.0YesYesparse with headers and typing, unparse
csv-parse and csv-stringifycsv-parse 7.0.2, csv-stringify 6.8.3YesYescsv-parse/sync and csv-stringify/sync
yaml2.9.1YesYesparse, stringify, parseDocument and edits, error reporting
js-yaml4.2.0YesYesload, loadAll, dump
json52.2.3YesYesparse and stringify
fast-json-stable-stringify2.1.0YesYesDeterministic key ordering
marked18.0.14YesYesparse, parseInline, lexer, Marked instances with custom renderers
he and entitieshe 1.2.0, entities 8.1.0YesYesHTML and XML entity encode, decode and escape
xss and sanitize-htmlxss 1.0.15, sanitize-html 2.17.7YesYesfilterXSS and sanitizeHtml
handlebars4.7.9YesYescompile, precompile, helpers, partials
mustache4.2.0YesYesrender with sections and partials

Binary encodings and compression

LibraryVersionESMCJSTested
@msgpack/msgpack3.1.3YesYesencode and decode, extension codecs (BigInt, Date)
cborg6.1.2YesYesCBOR encode and decode
bs586.0.0YesYesencode, decode, decodeUnsafe
js-base643.9.4YesYesEncode and decode, URL-safe, Uint8Array conversions, btoa
iconv-lite0.7.3YesYesEncode and decode legacy charsets (win1252, shift_jis, and more)
fflate0.8.3YesYesgzip, zlib, deflate, zip and unzip with the synchronous API, including gzip produced by Node.js
fflate (async API)0.8.3NoNoThe callback and async API runs on worker threads. Use the *Sync functions.
pako3.0.2YesYesdeflate and inflate, raw, gzip and ungzip
jszip3.10.2YesYesgenerateAsync, loadAsync, folders
protobufjs7.6.4YesYesParse .proto source, encode, decode, verify, toObject

Validation

LibraryVersionESMCJSTested
ajv8.20.0YesYesCompile JSON Schema, validate, errorsText
zod4.6.5YesYesObject schemas, safeParse, transforms, prettifyError, toJSONSchema
joi18.2.1YesYesObject schemas with dates, emails and URIs; validation details
validator13.15.35YesYesisEmail, isURL, isIP, isUUID, isIBAN, isJWT, normalizeEmail, and more
libphonenumber-js1.13.14YesYesparse, format, validate, AsYouType

Dates

LibraryVersionESMCJSTested
date-fns4.4.0YesYesParse and format, ISO and RFC 7231, arithmetic, intervals, distances
dayjs (with utc and timezone plugins)1.11.23YesYesutc, timezone, customParseFormat, relativeTime and advancedFormat plugins
luxon3.7.2YesYesDateTime zones and formats, Duration, Interval, Info (full ICU and Intl)
moment and moment-timezonemoment 2.31.0, moment-timezone 0.6.4YesYesFormatting, durations, calendar, time zones

Data manipulation and querying

LibraryVersionESMCJSTested
lodash4.18.1YesYesCollections, objects, strings, isEqual, cloneDeep, _.template
ramda0.32.0YesYespipe, evolve, groupBy, sortWith, and more
deepmerge4.3.1YesYesdeepmerge and deepmerge.all
qs6.15.2YesYesNested parse and stringify
jsonpath-plus10.4.0YesYesJSONPath queries with filters
jmespath0.16.0YesYessearch with functions (sort_by, max_by)
jsonata2.2.2YesYesExpressions and built-in functions (async evaluate)
semver7.8.5YesYessatisfies, ranges, inc, diff, coerce, sort
mime-types3.0.2YesYeslookup, extension, contentType, charset
decimal.js10.6.0YesYesArbitrary-precision arithmetic and formatting

Node.js built-in tests

The built-in modules are tested the same way as the libraries, by comparing the runtime's output with Node.js. All 21 groups pass.

GroupESMCJSTested
crypto: hashes and HMACYesYescreateHash (streaming, copy), createHmac, crypto.hash, getHashes
crypto: sign, verify and keysYesYescreateSign and createVerify; sign and verify for RSA (PKCS#1, PSS), EC and Ed25519; KeyObject PEM, DER and JWK; RSA-OAEP
crypto: ciphersYesYesAES-GCM, AES-CBC, AES-CTR, AES-ECB, ChaCha20-Poly1305, auth tags, auto padding, getCiphers
crypto: key derivation and randomYesYespbkdf2, scrypt (sync and callback), hkdf, randomBytes, randomFill, randomInt, randomUUID, getRandomValues, timingSafeEqual
crypto: key generationYesYesgenerateKeyPairSync (RSA, EC, Ed25519, X25519), generateKeySync, ECDH, diffieHellman
WebCrypto (crypto.subtle)YesYesdigest; HMAC, ECDSA, RSA-PSS, RSASSA and Ed25519 sign and verify; AES-GCM, AES-CBC, AES-CTR; RSA-OAEP; PBKDF2, HKDF and ECDH derivation; raw, JWK, SPKI and PKCS#8 import and export
zlib: sync and asyncYesYesgzip, deflate, raw and brotli round trips (sync, callback, promisified), decoding payloads produced by Node.js, crc32
zlib: streamsYesYescreateGzip and createGunzip in stream pipelines
bufferYesYesutf8, hex, base64, base64url, latin1 and utf16le encodings; integer, float and BigInt reads and writes; search; compare; atob and btoa
utilYesYesformat, inspect, promisify, callbackify, inherits, deprecate, types, isDeepStrictEqual, TextEncoder
eventsYesYesEventEmitter API, once and on helpers, max listeners, error handling
streamYesYesReadable, Writable, Transform, Duplex, PassThrough, pipeline, finished, async iteration
string_decoderYesYesMulti-byte utf8, utf16le and base64 across chunk boundaries
querystringYesYesparse, stringify, escape, unescape
url (legacy and WHATWG)YesYesURL and URLSearchParams, url.parse and url.format, file URLs, domainToASCII and domainToUnicode
pathYesYesjoin, resolve, relative, parse, format, normalize, matchesGlob (POSIX)
assertYesYesassert, strict mode, deepStrictEqual, throws and rejects, match
timers and microtask orderingYesYessetTimeout, setInterval, setImmediate, timers/promises, AbortController, nextTick and promise ordering
Web globalsYesYesTextEncoder and TextDecoder, structuredClone, AbortController and AbortSignal, Event and EventTarget, Blob, URL, atob and btoa, crypto.randomUUID, performance
esbuild createRequire bannerYesn/acreateRequire(import.meta.url), builtinModules, isBuiltin

Known gaps

GapWorkaround
fflate's callback and async API (gunzip, unzip, and others) uses worker threads, which aren't available.Use the synchronous functions, such as gunzipSync and unzipSync.
path.win32 isn't implemented.Use path.posix (the default path), or split Windows paths on \\ yourself.
util.parseArgs, util.MIMEType and util.types.isCryptoKey are missing.Parse arguments or media types with string methods, or bundle a library such as mime-types.
MessageChannel and BroadcastChannel aren't available.Use promises, queueMicrotask or setTimeout to schedule work.
Some exports that Node.js 22 adds to built-in modules are missing.Check for the function before you call it, or bundle a polyfill.