denoland/deno · error · DOMException
DataCloneError
DataCloneError
Error message
Cannot clone object of unsupported type.
What it means
Worker.postMessage serializes with structured clone. On the no-transfer fast path the polyfill first checks an isUncloneable marker (honoring markAsUncloneable) so values the V8 serializer would silently flatten to {} — URL objects, marked instances — instead throw DOMException 'Cannot clone object of unsupported type.' with DataCloneError, matching the web MessagePort path and Node's behavior.
Source
Thrown at ext/node/polyfills/worker_threads.ts:792
}
}
};
postMessage(message, transferOrOptions = { __proto__: null }) {
const prefix = "Failed to execute 'postMessage' on 'MessagePort'";
webidl.requiredArguments(arguments.length, 1, prefix);
if (this.#status !== "RUNNING") return;
// Fast path: no transferables
if (
transferOrOptions === undefined ||
transferOrOptions === null ||
(arguments.length <= 1)
) {
// Reject non-serializable values (e.g. URL) and per-instance
// markAsUncloneable values before V8's serializer silently turns them
// into `{}`, matching the web MessagePort path and Node's behavior.
if (isUncloneable(message)) {
throw new DOMException(
"Cannot clone object of unsupported type.",
"DataCloneError",
);
}
op_host_post_message_raw(
this.#id,
serializeMessageData(message),
);
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
- Convert to clone-safe data first: url.href instead of the URL object, plain objects instead of class instances.
- Add a toJSON()/toPlain() conversion on your classes, or explicitly JSON round-trip when the data allows it.
- For shared mutable state use SharedArrayBuffer instead of posting objects.
- Respect markAsUncloneable — if a class is marked, pass its fields, not the instance.
Example fix
// before
worker.postMessage({ endpoint: new URL('https://api.example.com/v1') });
// after
worker.postMessage({ endpoint: 'https://api.example.com/v1' }); // strings clone cleanly Defensive patterns
Strategy: type-guard
Validate before calling
function isCloneable(v: unknown, depth = 0): boolean {
if (v == null) return true;
const t = typeof v;
if (t !== 'object') return t !== 'function' && t !== 'symbol';
if (v instanceof URL) return false; // known uncloneable
if (v instanceof Date || v instanceof RegExp || v instanceof ArrayBuffer || v instanceof SharedArrayBuffer) return true;
if (depth > 8) return false;
const kids = Array.isArray(v) ? v : (v instanceof Map || v instanceof Set) ? [...v] : Object.values(v as object);
return kids.every((x) => isCloneable(x, depth + 1));
}
if (!isCloneable(msg)) throw new TypeError('message is not structured-clone safe');
worker.postMessage(msg); Type guard
Use isCloneable from validationCode as a deep guard: if (!isCloneable(msg)) msg = toPlainObject(msg);
Try / catch
try { worker.postMessage(msg); } catch (e) { if (e?.name === 'DataCloneError') { /* re-send as serialized primitives, e.g. JSON round-trip */ } else throw e; } Prevention
- Keep worker messages plain data: POJOs, arrays, primitives.
- Convert URL objects and class instances at the boundary with one sanitizeMessage() helper.
- Write a clone-safety unit test for every new message payload shape.
When it happens
Trigger: worker.postMessage(new URL('https://x')) or posting an instance registered via markAsUncloneable(); functions and other inherently non-cloneable values hit the same rejection.
Common situations: Sharing parsed URL or class-instance objects between threads and expecting JSON-like behavior; libraries that mark handles uncloneable to prevent silent data loss; values that otherwise 'arrive as empty object'.
Related errors
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/860b593625465e00.
Report an issue: GitHub.