denoland/deno · error · DOMException
DataCloneError
DataCloneError
Error message
Cannot clone object of unsupported type.
What it means
On MessagePort.postMessage's fast path (no transfer list, at most one argument), the top-level message is checked with the internal `isUncloneable` helper (ext/web/13_message_port.js:308-316). Values whose prototype was marked not-serializable (Web API platform types such as URL, Headers, Request) or that were flagged per-instance via node:worker_threads `markAsUncloneable()` throw DataCloneError "Cannot clone object of unsupported type." before V8's serializer runs — without this check they would silently clone as `{}`.
Source
Thrown at ext/web/13_message_port.js:310
* @param {object[] | StructuredSerializeOptions} transferOrOptions
*/
postMessage(message, transferOrOptions = { __proto__: null }) {
webidl.assertBranded(this, MessagePortPrototype);
const prefix = "Failed to execute 'postMessage' on 'MessagePort'";
webidl.requiredArguments(arguments.length, 1, prefix);
const portId = getMessagePortId(this);
const portClosed = portId === null;
// Fast path: no transferables - serialize and send in one shot,
// bypassing the JsMessageData serde overhead
if (
transferOrOptions === undefined ||
transferOrOptions === null ||
(arguments.length <= 1)
) {
if (portClosed) return;
// Honor markAsUncloneable for top-level postMessage values.
if (isUncloneable(message)) {
throw new DOMException(
"Cannot clone object of unsupported type.",
"DataCloneError",
);
}
const data = serializeMessageData(message, serializeErrorCb);
const currentPortId = getMessagePortId(this);
if (currentPortId === null) return;
op_message_port_post_message_raw(currentPortId, data);
return;
}
message = webidl.converters.any(message);
let options;
if (
webidl.type(transferOrOptions) === "Object" &&
transferOrOptions !== undefined &&
transferOrOptions[SymbolIterator] !== undefined
) {
const transfer = webidl.converters["sequence<object>"](View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Post plain serializable data: convert first (url.href, headers-to-array, JSON string).
- If the value is genuinely transferable (ArrayBuffer, another MessagePort), list it in the transfer list instead of relying on cloning.
- Run a `structuredClone` probe on unknown payloads before posting to fail early with a clear context.
Example fix
// before
port.postMessage(new URL("https://example.com"));
// after
port.postMessage(new URL("https://example.com").href); Defensive patterns
Strategy: try-catch
Validate before calling
function isCloneableTopLevel(v) {
if (v === null) return true;
const t = typeof v;
if (t !== "object" && t !== "function") return true; // primitives always clone
try {
structuredClone(v);
return true;
} catch {
return false;
}
}
if (!isCloneableTopLevel(msg)) msg = toPlainData(msg); Type guard
const isPlainMessage = (v) => v === null || ["string", "number", "boolean", "bigint", "undefined"].includes(typeof v);
Try / catch
try {
port.postMessage(msg);
} catch (e) {
if (e instanceof DOMException && e.name === "DataCloneError") {
port.postMessage(toPlainData(msg)); // strip platform objects, retry
} else {
throw e;
}
} Prevention
- Post data-only payloads (POJOs, primitives) across MessagePorts.
- Convert platform objects before posting: url.href, headers-to-entries, awaited bodies.
- Fail fast: probe unknown payloads with structuredClone before posting so errors carry your context.
When it happens
Trigger: `port.postMessage(new URL('https://example.com'))` with no second argument; posting a Request/Headers instance or a markAsUncloneable-flagged object as the entire message.
Common situations: Sending live web-platform objects between workers instead of plain data; Node-compat worker_threads code relying on markAsUncloneable semantics; refactors that forward framework objects through postMessage.
Related errors
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/d1febf552f10e7cc.
Report an issue: GitHub.