postMessage API
This lesson explains postMessage API with clear examples, use cases, and best practices so you can use HTML5 APIs confidently in real web applications.
HTML5 postMessage API Overview
The HTML5 postMessage() API allows safe communication between
different browsing contexts such as windows, tabs, popups, iframes, and
embedded applications. It is especially useful when two pages are from
different origins and cannot directly access each other because of the
browser's same-origin policy.
The postMessage API is commonly used for iframe communication, embedded
widgets, payment flows, authentication popups, micro frontends, browser
extensions, analytics embeds, chat widgets, and cross-domain integrations.
| Feature | Description |
| API Name | postMessage API |
| Main Method | window.postMessage() |
| Main Event | message |
| Message Object | MessageEvent |
| Common Contexts | Window, iframe, popup, tab, embedded widget. |
| Security Rule | Always validate event.origin and target origin. |
Basic postMessage Example
// Send message to another window
const iframe =
document.querySelector("#paymentFrame");
iframe.contentWindow.postMessage(
"Hello iframe",
"https://payments.example.com"
);
// Receive message
window.addEventListener("message", (event) => {
if (event.origin !== "https://payments.example.com") {
return;
}
console.log("Message received:", event.data);
});
This example sends a message to an iframe and receives a response using
the message event. The receiver checks
event.origin before trusting the message.
How postMessage Works
| Step | Description | Example Syntax |
| Select Target | Choose iframe, popup, parent, opener, or another window. | iframe.contentWindow |
| Send Message | Send data using postMessage(). | targetWindow.postMessage(data, origin) |
| Receive Message | Listen for the message event. | window.addEventListener("message", callback) |
| Validate Origin | Check sender origin before using data. | event.origin === allowedOrigin |
| Process Data | Read message payload from event.data. | event.data.type |
| Reply Back | Use event.source.postMessage(). | event.source.postMessage(reply, event.origin) |
postMessage Syntax
targetWindow.postMessage(
message,
targetOrigin
);
| Parameter | Description | Example |
message | Data to send. Can be string, object, array, number, or structured data. | "hello" |
targetOrigin | Expected origin of receiving window. | "https://example.com" |
transfer | Optional transferable objects such as MessagePort or ArrayBuffer. | [arrayBuffer] |
MessageEvent Properties
| Property | Description | Common Use |
event.data | Message payload sent by the other window. | Read message content. |
event.origin | Origin of the sender. | Security validation. |
event.source | Reference to the sender window. | Reply back to sender. |
event.ports | Transferred message ports. | Channel messaging. |
event.lastEventId | Last event ID if available. | Rarely used with postMessage. |
Parent Page to iframe Example
<iframe
id="profileFrame"
src="https://widgets.example.com/profile">
</iframe>
<script>
const frame =
document.querySelector("#profileFrame");
frame.addEventListener("load", () => {
frame.contentWindow.postMessage(
{
type: "USER_DATA",
userId: 101
},
"https://widgets.example.com"
);
});
</script>
The parent page sends structured data to an embedded iframe after the
iframe loads.
iframe to Parent Page Example
// Inside iframe page
window.parent.postMessage(
{
type: "PROFILE_READY",
height: document.body.scrollHeight
},
"https://app.example.com"
);
This pattern is commonly used by embedded widgets to notify the parent
page when content is ready or when the iframe height changes.
Receive Structured Messages
const allowedOrigin =
"https://widgets.example.com";
window.addEventListener("message", (event) => {
if (event.origin !== allowedOrigin) {
return;
}
const message =
event.data;
if (!message || message.type !== "PROFILE_READY") {
return;
}
console.log(
"Widget height:",
message.height
);
});
Structured messages with a type field are easier to validate,
route, and maintain than plain string messages.
Reply to a Message
window.addEventListener("message", (event) => {
if (event.origin !== "https://app.example.com") {
return;
}
if (event.data.type === "PING") {
event.source.postMessage(
{
type: "PONG"
},
event.origin
);
}
});
The event.source property provides a reference to the sender,
allowing the receiver to respond directly.
Transfer ArrayBuffer Example
const buffer =
new ArrayBuffer(1024);
iframe.contentWindow.postMessage(
{
type: "BINARY_DATA",
buffer: buffer
},
"https://processor.example.com",
[buffer]
);
Transferable objects move ownership instead of copying data. This is more
efficient for large binary data but means the original context can no
longer use the transferred object.
MessageChannel with postMessage
const channel =
new MessageChannel();
iframe.contentWindow.postMessage(
{
type: "CONNECT"
},
"https://widgets.example.com",
[channel.port2]
);
channel.port1.onmessage = (event) => {
console.log(
"Channel message:",
event.data
);
};
channel.port1.postMessage(
{
type: "HELLO"
}
);
MessageChannel creates a dedicated communication channel
between contexts and is useful for more complex integrations.
postMessage Security Rules
| Rule | Why It Matters | Good Practice |
Never use "*" for sensitive data. | Any origin could receive the message. | Use an exact origin like "https://app.example.com". |
Always check event.origin. | Prevents malicious pages from sending fake messages. | Compare against an allowlist. |
Validate event.data. | Messages can contain unexpected data. | Check type, shape, and required fields. |
| Avoid sending secrets. | Messages may be exposed through integration mistakes. | Send short-lived codes or IDs instead of long-lived tokens. |
| Use one message contract. | Prevents fragile integrations. | Use type, payload, and version fields. |
Safe Message Contract Example
const message =
{
type: "CART_UPDATED",
version: 1,
payload: {
cartId: "cart-123",
itemCount: 3
}
}
iframe.contentWindow.postMessage(
message,
"https://checkout.example.com"
);
A consistent message contract makes cross-window communication easier to
validate, document, test, and maintain.
Common postMessage API Use Cases
- Parent page and iframe communication.
- Payment iframe checkout integrations.
- Authentication popup callbacks.
- Embedded chat widgets.
- Micro frontend communication.
- Browser extension content scripts.
- Cross-domain analytics widgets.
- Resizing embedded iframe content.
postMessage API Best Practices
- Always specify an exact
targetOrigin. - Always validate
event.origin before reading data. - Use structured messages with a clear
type. - Validate message shape before acting on it.
- Avoid sending passwords, long-lived tokens, or sensitive user data.
- Remove message listeners when they are no longer needed.
- Use allowlists when communicating with multiple trusted origins.
- Document your message contract for long-term maintainability.
Common postMessage Mistakes
- Using
"*" as targetOrigin for sensitive messages. - Not checking
event.origin. - Trusting
event.data without validation. - Sending secrets or long-lived tokens through messages.
- Forgetting that any window can send a
message event. - Not removing temporary message listeners.
- Using inconsistent message formats across teams.
- Assuming iframe content is ready before sending messages.
postMessage vs Broadcast Channel vs Web Workers
| API | Communicates Between | Best Use |
| postMessage | Windows, iframes, popups, workers. | Cross-window and cross-origin communication. |
| Broadcast Channel | Same-origin tabs, windows, iframes, workers. | Sync state across same-origin contexts. |
| Web Workers | Main thread and worker thread. | Background processing and CPU-heavy work. |
| MessageChannel | Two connected message ports. | Dedicated two-way communication channel. |
Key Takeaways
- The postMessage API enables communication between windows, iframes, popups, and workers.
- Use
targetWindow.postMessage(message, targetOrigin) to send messages. - Use the
message event to receive messages. - Always validate
event.origin before trusting a message. - Use exact origins instead of
"*" for sensitive data. - Use structured message contracts for reliable integrations.
Security Tip
Treat every incoming message event as untrusted input. Check
event.origin, validate the message shape, and avoid sending
secrets through postMessage(). A small validation mistake can
create serious cross-origin security bugs.