Transformations
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.
fetchisn't available, and modules such asfs,http,netandchild_processcan 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.
| Limit | Value |
|---|---|
| Execution time | 1 second, including time spent awaiting |
| Memory | 128 MB per transformation |
| Code size, including bundled dependencies | 5 MB |
| Binary body exposed to the code | 20 MiB. Larger bodies arrive as null and are delivered unchanged. |
A single Buffer, or the output of one decompression call | 32 MiB |
| Input to one compression call | 8 MiB |
| Brotli compression at quality 10 or higher | 128 KiB of input. Node.js defaults to quality 11, so pass a lower BROTLI_PARAM_QUALITY for larger inputs. |
pbkdf2 | Iterations multiplied by output blocks up to 600,000 |
scrypt | 128 * N * r * p up to 64 MiB |
| RSA keys | Up 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_queryobject 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 body | Result |
|---|---|
Bytes: a Buffer, Uint8Array or ArrayBuffer | A 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 string | A 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 array | A 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-typewith it, such asapplication/jsonortext/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 drop down.

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.
Open the connection rules configuration.
Click to add a transformation rule.
Click Create new transformation.
Configure the transformation using the supported transformation syntax in the right-hand pane.
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 and select a recent payload, or edit the text directly.
- Click 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.
Name the transformation using the input field in the upper left, clicking to update the name.
Once satisfied with the transformation, click .
Make sure to click 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.
Open the connection rules configuration.
Next to the transformation rule, click Editor.
Configure the transformation using the supported transformation syntax in the right-hand pane.
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 and select a recent payload, or edit the text directly.
- Click 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.
Optionally, you may rename the transformation using the input field in the upper left. Click to confirm the name change.
Once satisfied, click .
Make sure to click 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 . Confirm the deletion by clicking in the modal that appears.
Troubleshoot a transformation
Execution logs are kept for every transformation, in order to facilitate troubleshooting.
Open the connection rules configuration.
Next to the transformation rule, click Editor.
Click .
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.
- Open the Issue page.
- Open the issue of type
transformation - Inspect the histogram of "Ignored Events" to see the number of events that were not created.
- Select the latest execution and click "Edit"
- Make the necessary changes to your transformation code and click "run" to validate that the transformation issue as been resolved.
- 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.
fetchisn'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.
| Module | Availability |
|---|---|
buffer | Buffer, Blob, File, atob, btoa, isUtf8, isAscii, transcode |
crypto | Hash 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. |
zlib | gzip, deflate, deflateRaw and brotli, with their inverses and unzip, as sync, callback and promisified functions; crc32; stream classes (buffered) |
util | inspect, format, types, promisify, callbackify, inherits, deprecate, isDeepStrictEqual, styleText, parseEnv, TextEncoder, TextDecoder |
events | EventEmitter, once, on, getEventListeners, errorMonitor |
stream, stream/promises | Readable, Writable, Transform, Duplex, PassThrough, pipeline, finished (Node.js 18 streams) |
string_decoder, punycode, querystring | Full module |
path | POSIX paths only. path.win32 isn't available. |
url | WHATWG URL and URLSearchParams, plus the legacy parse, format and resolve |
assert, assert/strict | Full module |
timers, timers/promises | Bounded by the execution timeout |
os, process, tty, module, perf_hooks, console, diagnostics_channel | Static values. Nothing describes the host. |
async_hooks | AsyncLocalStorage and AsyncResource, with synchronous context propagation only |
fs, http, https, net, child_process, dns, vm, worker_threads, and other I/O modules | Importable, 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,btoaURL,URLSearchParamscrypto(WebCrypto, includingcrypto.subtleandcrypto.randomUUID)structuredClone,queueMicrotaskAbortController,AbortSignal,Event,EventTarget,CustomEvent,DOMExceptionBlob,File,Headers,FormData,navigator,performance- Web streams:
ReadableStream,WritableStream,TransformStream,TextEncoderStream,TextDecoderStream,CompressionStream,DecompressionStream setTimeout,setInterval,setImmediateand theirclear*functionsprocess, withprocess.envholding your environment variablesWebAssembly
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.
Install the library and esbuild:
npm install fast-xml-parser npm install --save-dev esbuildWrite 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; }, };Bundle it:
npx esbuild transform.js --bundle --format=esm --platform=node --outfile=dist/transform.js--platform=nodeleaves imports of Node.js built-ins in place, so the bundle uses the runtime's built-in modules. Add--minifyto make the bundle smaller.Paste the contents of
dist/transform.jsinto 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
| Library | Version | ESM | CJS | Tested |
|---|---|---|---|---|
| jsonwebtoken | 9.0.3 | Yes | Yes | HS256, RS256 (PKCS#1 PEM) and ES256 sign, verify and decode |
| jose | 6.2.3 | Yes | Yes | SignJWT and jwtVerify, JWE compact encrypt and decrypt, PKCS#8 and SPKI import, JWK export and thumbprint |
| uuid | 14.0.0 | Yes | Yes | v1, v3, v4, v5, v6, v7, parse, stringify, validate, version |
| nanoid | 3.3.8 | Yes | Yes | nanoid, customAlphabet, non-secure variant |
| ulid | 3.0.2 | Yes | Yes | ulid, monotonicFactory, decodeTime, isValid |
| crypto-js | 4.2.0 | Yes | Yes | MD5, SHA1, SHA256, SHA512, SHA3, HmacSHA256, PBKDF2, AES encrypt and decrypt, WordArray.random |
| bcryptjs | 3.0.3 | Yes | Yes | genSaltSync, hashSync, compareSync, getRounds, getSalt |
| @noble/hashes | 2.4.0 | Yes | Yes | sha256, sha512, sha3, keccak, blake2b, blake3, hmac, hkdf, pbkdf2, scrypt |
| @noble/curves | 2.4.0 | Yes | Yes | ed25519, secp256k1 and P-256 keys, sign, verify |
| tweetnacl | 1.0.3 | Yes | Yes | detached sign, box, secretbox, hash, key pair from seed |
| standardwebhooks | 1.0.0 | Yes | Yes | Standard Webhooks signing and verification |
| stripe (webhooks) | 22.6.2 | Yes | Yes | Default Node.js build: webhooks.constructEvent and constructEventAsync, test header generation |
stripe (webhooks, worker build) | 22.6.2 | Yes | Yes | worker export condition: SubtleCrypto provider, constructEventAsync |
| node-forge | 1.4.0 | Yes | Yes | sha256, HMAC, PBKDF2, AES-GCM cipher, PEM key parsing, random bytes |
| hash-wasm | 4.12.0 | Yes | Yes | WebAssembly hashes: sha256, xxhash64, blake3, HMAC, argon2id |
| object-hash | 3.0.0 | Yes | Yes | Hashing objects, Maps, Sets and Dates; key hashing |
| @octokit/webhooks-methods | 6.0.0 | Yes | Yes | GitHub webhook signing and verification |
XML, CSV and text formats
| Library | Version | ESM | CJS | Tested |
|---|---|---|---|---|
| fast-xml-parser | 5.9.2 | Yes | Yes | XMLParser, XMLBuilder, XMLValidator |
| xml2js | 0.6.2 | Yes | Yes | parseString (callback and promise), Builder, parse errors |
| xml-js | 1.6.11 | Yes | Yes | xml2js, xml2json, js2xml |
| xmlbuilder2 | 4.0.3 | Yes | Yes | create, ele, txt, dat, convert |
| papaparse | 5.7.0 | Yes | Yes | parse with headers and typing, unparse |
| csv-parse and csv-stringify | csv-parse 7.0.2, csv-stringify 6.8.3 | Yes | Yes | csv-parse/sync and csv-stringify/sync |
| yaml | 2.9.1 | Yes | Yes | parse, stringify, parseDocument and edits, error reporting |
| js-yaml | 4.2.0 | Yes | Yes | load, loadAll, dump |
| json5 | 2.2.3 | Yes | Yes | parse and stringify |
| fast-json-stable-stringify | 2.1.0 | Yes | Yes | Deterministic key ordering |
| marked | 18.0.14 | Yes | Yes | parse, parseInline, lexer, Marked instances with custom renderers |
| he and entities | he 1.2.0, entities 8.1.0 | Yes | Yes | HTML and XML entity encode, decode and escape |
| xss and sanitize-html | xss 1.0.15, sanitize-html 2.17.7 | Yes | Yes | filterXSS and sanitizeHtml |
| handlebars | 4.7.9 | Yes | Yes | compile, precompile, helpers, partials |
| mustache | 4.2.0 | Yes | Yes | render with sections and partials |
Binary encodings and compression
| Library | Version | ESM | CJS | Tested |
|---|---|---|---|---|
| @msgpack/msgpack | 3.1.3 | Yes | Yes | encode and decode, extension codecs (BigInt, Date) |
| cborg | 6.1.2 | Yes | Yes | CBOR encode and decode |
| bs58 | 6.0.0 | Yes | Yes | encode, decode, decodeUnsafe |
| js-base64 | 3.9.4 | Yes | Yes | Encode and decode, URL-safe, Uint8Array conversions, btoa |
| iconv-lite | 0.7.3 | Yes | Yes | Encode and decode legacy charsets (win1252, shift_jis, and more) |
| fflate | 0.8.3 | Yes | Yes | gzip, zlib, deflate, zip and unzip with the synchronous API, including gzip produced by Node.js |
| fflate (async API) | 0.8.3 | No | No | The callback and async API runs on worker threads. Use the *Sync functions. |
| pako | 3.0.2 | Yes | Yes | deflate and inflate, raw, gzip and ungzip |
| jszip | 3.10.2 | Yes | Yes | generateAsync, loadAsync, folders |
| protobufjs | 7.6.4 | Yes | Yes | Parse .proto source, encode, decode, verify, toObject |
Validation
| Library | Version | ESM | CJS | Tested |
|---|---|---|---|---|
| ajv | 8.20.0 | Yes | Yes | Compile JSON Schema, validate, errorsText |
| zod | 4.6.5 | Yes | Yes | Object schemas, safeParse, transforms, prettifyError, toJSONSchema |
| joi | 18.2.1 | Yes | Yes | Object schemas with dates, emails and URIs; validation details |
| validator | 13.15.35 | Yes | Yes | isEmail, isURL, isIP, isUUID, isIBAN, isJWT, normalizeEmail, and more |
| libphonenumber-js | 1.13.14 | Yes | Yes | parse, format, validate, AsYouType |
Dates
| Library | Version | ESM | CJS | Tested |
|---|---|---|---|---|
| date-fns | 4.4.0 | Yes | Yes | Parse and format, ISO and RFC 7231, arithmetic, intervals, distances |
| dayjs (with utc and timezone plugins) | 1.11.23 | Yes | Yes | utc, timezone, customParseFormat, relativeTime and advancedFormat plugins |
| luxon | 3.7.2 | Yes | Yes | DateTime zones and formats, Duration, Interval, Info (full ICU and Intl) |
| moment and moment-timezone | moment 2.31.0, moment-timezone 0.6.4 | Yes | Yes | Formatting, durations, calendar, time zones |
Data manipulation and querying
| Library | Version | ESM | CJS | Tested |
|---|---|---|---|---|
| lodash | 4.18.1 | Yes | Yes | Collections, objects, strings, isEqual, cloneDeep, _.template |
| ramda | 0.32.0 | Yes | Yes | pipe, evolve, groupBy, sortWith, and more |
| deepmerge | 4.3.1 | Yes | Yes | deepmerge and deepmerge.all |
| qs | 6.15.2 | Yes | Yes | Nested parse and stringify |
| jsonpath-plus | 10.4.0 | Yes | Yes | JSONPath queries with filters |
| jmespath | 0.16.0 | Yes | Yes | search with functions (sort_by, max_by) |
| jsonata | 2.2.2 | Yes | Yes | Expressions and built-in functions (async evaluate) |
| semver | 7.8.5 | Yes | Yes | satisfies, ranges, inc, diff, coerce, sort |
| mime-types | 3.0.2 | Yes | Yes | lookup, extension, contentType, charset |
| decimal.js | 10.6.0 | Yes | Yes | Arbitrary-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.
| Group | ESM | CJS | Tested |
|---|---|---|---|
| crypto: hashes and HMAC | Yes | Yes | createHash (streaming, copy), createHmac, crypto.hash, getHashes |
| crypto: sign, verify and keys | Yes | Yes | createSign and createVerify; sign and verify for RSA (PKCS#1, PSS), EC and Ed25519; KeyObject PEM, DER and JWK; RSA-OAEP |
| crypto: ciphers | Yes | Yes | AES-GCM, AES-CBC, AES-CTR, AES-ECB, ChaCha20-Poly1305, auth tags, auto padding, getCiphers |
| crypto: key derivation and random | Yes | Yes | pbkdf2, scrypt (sync and callback), hkdf, randomBytes, randomFill, randomInt, randomUUID, getRandomValues, timingSafeEqual |
| crypto: key generation | Yes | Yes | generateKeyPairSync (RSA, EC, Ed25519, X25519), generateKeySync, ECDH, diffieHellman |
WebCrypto (crypto.subtle) | Yes | Yes | digest; 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 async | Yes | Yes | gzip, deflate, raw and brotli round trips (sync, callback, promisified), decoding payloads produced by Node.js, crc32 |
| zlib: streams | Yes | Yes | createGzip and createGunzip in stream pipelines |
| buffer | Yes | Yes | utf8, hex, base64, base64url, latin1 and utf16le encodings; integer, float and BigInt reads and writes; search; compare; atob and btoa |
| util | Yes | Yes | format, inspect, promisify, callbackify, inherits, deprecate, types, isDeepStrictEqual, TextEncoder |
| events | Yes | Yes | EventEmitter API, once and on helpers, max listeners, error handling |
| stream | Yes | Yes | Readable, Writable, Transform, Duplex, PassThrough, pipeline, finished, async iteration |
| string_decoder | Yes | Yes | Multi-byte utf8, utf16le and base64 across chunk boundaries |
| querystring | Yes | Yes | parse, stringify, escape, unescape |
| url (legacy and WHATWG) | Yes | Yes | URL and URLSearchParams, url.parse and url.format, file URLs, domainToASCII and domainToUnicode |
| path | Yes | Yes | join, resolve, relative, parse, format, normalize, matchesGlob (POSIX) |
| assert | Yes | Yes | assert, strict mode, deepStrictEqual, throws and rejects, match |
| timers and microtask ordering | Yes | Yes | setTimeout, setInterval, setImmediate, timers/promises, AbortController, nextTick and promise ordering |
| Web globals | Yes | Yes | TextEncoder and TextDecoder, structuredClone, AbortController and AbortSignal, Event and EventTarget, Blob, URL, atob and btoa, crypto.randomUUID, performance |
esbuild createRequire banner | Yes | n/a | createRequire(import.meta.url), builtinModules, isBuiltin |
Known gaps
| Gap | Workaround |
|---|---|
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. |