Skip to content

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.