remotion-dev/remotion · error · Error
Expected an image, video, or canvas element
Error message
Expected an image, video, or canvas element
What it means
Thrown in copyImageToClipboard (a generic guard at the end of the element-dispatch chain): the function expects the timeline item thumbnail source to be an HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement, but received an element with some other tagName. It fires when the caller passes an unsupported DOM element type to the clipboard-copy routine — the image and VIDEO branches have already been exhausted. This is an internal invariant violation rather than user input error; the caller should only pass elements produced from a sequence thumbnail that are one of the three supported media types.
Solutions
- Pass the actual <img>, <video>, or <canvas> node, not a wrapper element.
- Check element.tagName before calling and route unsupported types to a different handler.
- For arbitrary elements, first draw them into a canvas (e.g. via foreignObject/SVG serialization) and copy that canvas.
- Update Remotion Studio if a recent DOM refactor broke the element resolution.
Example fix
// before
await copyImageToClipboard(wrapperDiv); // throws
// after
const media = wrapperDiv.querySelector('img, video, canvas');
if (media) await copyImageToClipboard(media); Defensive patterns
Strategy: type-guard
Validate before calling
if (!['IMG','VIDEO','CANVAS'].includes(el.tagName)) throw new TypeError('copyImageToClipboard requires img/video/canvas'); Type guard
const isCopyable = (el: Element): el is HTMLImageElement | HTMLVideoElement | HTMLCanvasElement => el instanceof HTMLImageElement || el instanceof HTMLVideoElement || el instanceof HTMLCanvasElement;
Try / catch
try {
await copyImageToClipboard(el);
} catch (err) {
if (err.message.startsWith('Expected an image, video, or canvas')) {
// resolve the actual media node before retrying
}
} Prevention
- Always pass the media node, not wrapper elements
- Narrow with instanceof HTMLImageElement/HTMLVideoElement/HTMLCanvasElement first
- Assert tagName in dev builds to catch DOM structure regressions early
When it happens
Trigger: Invoking the copy-image action on a timeline thumbnail element that is not an <img>, <video>, or <canvas> — e.g. a <div>, <svg>, or other wrapper element was passed instead of the media node.
Common situations: DOM structure changes in Studio so the element resolved from the thumbnail no longer is a media element; custom code reusing copyImageToClipboard with an unsupported node.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Called measureText() with "fontFamily
- Cannot paste effects: no effects were copied
- Cannot update effect prop: computed
- Component style must be an object to add a position
- Could not create canvas context
AI-assisted analysis of remotion-dev/remotion@46a3a6bf13 (2026-09-18).
Data as JSON: /api/errors/15d37d8928a1e097.
Report an issue: GitHub.
Appendix: source
Thrown at packages/studio/src/components/Timeline/get-sequence-context-menu-items.ts:102
context.drawImage(image, 0, 0);
} else if (element.tagName === 'VIDEO') {
const video = element as HTMLVideoElement;
if (video.videoWidth === 0 || video.videoHeight === 0) {
throw new Error('Video frame has not loaded');
}
canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const context = canvas.getContext('2d');
if (!context) {
throw new Error('Could not create canvas context');
}
context.drawImage(video, 0, 0);
} else {
throw new Error('Expected an image, video, or canvas element');
}
const blob = new Promise<Blob>((resolve, reject) => {
canvas.toBlob((result) => {
if (result) {
resolve(result);
} else {
reject(new Error('Could not convert image to PNG'));
}
}, 'image/png');
});
await navigator.clipboard.write([
new ClipboardItem({
'image/png': blob,
}),
]);
};View on GitHub (pinned to 46a3a6bf13)