In modern web apps, you’ll often find yourself needing to communicate between browser contexts — like a parent window and an iframe — without direct DOM access. That’s where window.postMessage
comes in: a built-in browser API that allows secure cross-origin messaging between windows, iframes, and even popups.
This post walks through how postMessage
works, how to implement both sides of the communication, and what kinds of problems it can solve — like dynamically resizing an embedded iframe based on its content.
Why Use postMessage
?
You can’t directly reach into an iframe’s DOM if it’s served from another origin. The browser blocks this for security reasons. But sometimes you still need to exchange data — resize events, form submissions, status updates, etc.
postMessage
is your tool for that. It lets one window send a message to another, even across origins, as long as both sides are set up to send and receive.
The Sender Side
Let’s say you’ve got an app embedded inside an iframe — maybe it’s a scheduler, a dashboard, or a form. It renders dynamic content and needs to tell the parent how tall it is so the iframe can resize appropriately.
Here’s a simple sender implementation that posts a resize
message to the parent:
// inside the iframe context
function sendResize() {
const height = document.body.scrollHeight;
window.parent.postMessage(
{ type: 'resize', height },
'*' // In production, use a specific origin string
);
}
window.addEventListener('load', sendResize);
window.addEventListener('resize', sendResize);
In a real-world case, we encountered this exact setup with Zoom’s embedded calendar scheduler. Zoom handles this logic internally — they emit a resize
message from inside the iframe when the content height changes.
The Receiver Side (React Example)
On the parent page, we need to listen for that postMessage
event and apply the new height to the iframe. Here’s a clean implementation using React:
import React, { useEffect, useRef, useState } from 'react';
const EmbeddedIframe = ({ src, id }) => {
const iframeRef = useRef<HTMLIFrameElement>(null);
const [height, setHeight] = useState<number>(600); // fallback height
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
// Filter by origin and message type for safety
if (
event.origin === 'https://scheduler.zoom.us' && // Replace as needed
event.data?.type === 'resize' &&
typeof event.data.height === 'number'
) {
setHeight(event.data.height);
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
return (
<iframe
id={id}
ref={iframeRef}
src={src}
style={{
width: '100%',
border: 'none',
height: `${height}px`,
transition: 'height 0.3s ease'
}}
/>
);
};
This gives you a fully reactive iframe that adjusts itself based on whatever the embedded app reports — without layout bugs or manual hacks.
What This Enables
With this setup, you can:
- Embed third-party content cleanly without hardcoded heights
- Coordinate actions between different parts of a complex app (e.g., parent → iframe → popup)
- Trigger parent-side behavior based on iframe events, such as submitting a form or completing a booking
- Build safer integrations by filtering messages by
origin
andtype
Tips for Using postMessage
Well
- Always validate
event.origin
to ensure you're not accepting untrusted input. - Namespace your messages with a
type
field to avoid conflicts ({ type: 'resize', height: ... }
). - Clean up listeners on unmount to avoid memory leaks in single-page apps.
- Debounce or throttle resize messages if the embedded content resizes frequently.
- Avoid sending large payloads —
postMessage
is for coordination, not data transport.
The postMessage
API is an underrated workhorse for modern web integrations. It gives you a clean, browser-native way to bridge the sandbox between isolated browser contexts — safely and predictably.
Whether you’re working with Zoom’s embedded scheduler, custom widgets, or internal tools, knowing how to handle postMessage
properly will save you time and help your apps feel more seamless.