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
originandtype
Tips for Using postMessage Well
- Always validate
event.originto ensure you're not accepting untrusted input. - Namespace your messages with a
typefield 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 —
postMessageis 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.
.png)
