frontend

PostMessage API in JavaScript

January 4, 2026

PostMessage API in JavaScript

Overview

The PostMessage API allows secure cross-origin communication between windows, iframes, and web workers. It provides a safe way to exchange data between different origins without violating the same-origin policy.

Basic Syntax

// Send message
targetWindow.postMessage(message, targetOrigin, [transfer]);

// Receive message
window.addEventListener("message", (event) => {
  // Handle message
});

Parameters

  • message: Data to be sent (any serializable object)
  • targetOrigin: URL of target window ("*" allows any origin, but not recommended)
  • transfer: Optional array of Transferable objects

Basic Examples

1. Parent Window to Iframe

document.addEventListener("DOMContentLoaded", () => {
  const iframe = document.querySelector("iframe");
  
  if (iframe && iframe.contentWindow) {
    iframe.contentWindow.postMessage(
      {
        type: "PARENT_MESSAGE",
        data: "Hello from parent!",
      },
      "https://trusted-origin.com" // Target origin
    );
  }
});

2. Iframe to Parent Window

// Inside iframe
window.parent.postMessage(
  {
    type: "IFRAME_MESSAGE",
    data: "Hello from iframe!",
  },
  "*" // ⚠️ Use specific origin in production
);

3. Listening for Messages

window.addEventListener("message", (event) => {
  // ⚠️ Always verify the sender's origin
  if (event.origin !== "https://trusted-origin.com") {
    return; // Ignore messages from untrusted origins
  }
  
  // Handle different message types
  switch (event.data.type) {
    case "PARENT_MESSAGE":
      console.log("Received from parent:", event.data.data);
      break;
    case "IFRAME_MESSAGE":
      console.log("Received from iframe:", event.data.data);
      break;
  }
});

Complete Parent-Iframe Communication

Parent Window Code

class ParentWindow {
  constructor() {
    this.iframe = document.querySelector("iframe");
    this.init();
  }
  
  init() {
    // Send initial message to iframe
    this.sendToIframe({
      type: "INIT",
      data: { config: "parent-config" },
    });
    
    // Listen for iframe messages
    window.addEventListener("message", this.handleMessage.bind(this));
  }
  
  sendToIframe(message) {
    if (this.iframe && this.iframe.contentWindow) {
      this.iframe.contentWindow.postMessage(message, "*");
      // ⚠️ In production, use specific origin: "https://iframe-origin.com"
    }
  }
  
  handleMessage(event) {
    // In production, verify origin
    // if (event.origin !== "https://iframe-origin.com") return;
    
    switch (event.data.type) {
      case "IFRAME_READY":
        console.log("Iframe is ready");
        break;
      case "DATA_UPDATE":
        console.log("Received data update:", event.data.data);
        this.handleDataUpdate(event.data.data);
        break;
    }
  }
  
  handleDataUpdate(data) {
    // Process data from iframe
  }
}

const parentWindow = new ParentWindow();

Iframe Code

class IframeWindow {
  constructor() {
    this.init();
  }
  
  init() {
    // Notify parent that iframe is ready
    this.sendToParent({
      type: "IFRAME_READY",
      data: { status: "ready" },
    });
    
    // Listen for parent messages
    window.addEventListener("message", this.handleMessage.bind(this));
  }
  
  sendToParent(message) {
    window.parent.postMessage(message, "*");
    // ⚠️ In production, use specific origin: "https://parent-origin.com"
  }
  
  handleMessage(event) {
    // In production, verify origin
    // if (event.origin !== "https://parent-origin.com") return;
    
    switch (event.data.type) {
      case "INIT":
        console.log("Received init config:", event.data.data);
        this.initialize(event.data.data);
        break;
      case "UPDATE_REQUEST":
        this.sendToParent({
          type: "DATA_UPDATE",
          data: { value: "updated-data" },
        });
        break;
    }
  }
  
  initialize(config) {
    // Initialize iframe with config from parent
  }
}

const iframeWindow = new IframeWindow();

Window-to-Window Communication

// Open new window
const newWindow = window.open("https://example.com", "newWindow");

// Wait for window to load, then send message
newWindow.addEventListener("load", () => {
  newWindow.postMessage(
    { type: "GREETING", message: "Hello!" },
    "https://example.com"
  );
});

// Listen for messages from new window
window.addEventListener("message", (event) => {
  if (event.origin !== "https://example.com") return;
  
  if (event.data.type === "RESPONSE") {
    console.log("Response:", event.data.message);
  }
});

Web Worker Communication

Main Thread

const worker = new Worker("worker.js");

// Send message to worker
worker.postMessage({ type: "CALCULATE", data: [1, 2, 3, 4, 5] });

// Listen for messages from worker
worker.addEventListener("message", (event) => {
  console.log("Result from worker:", event.data);
});

// Handle errors
worker.addEventListener("error", (error) => {
  console.error("Worker error:", error);
});

Worker Thread (worker.js)

// Listen for messages from main thread
self.addEventListener("message", (event) => {
  if (event.data.type === "CALCULATE") {
    const result = event.data.data.reduce((a, b) => a + b, 0);
    
    // Send result back to main thread
    self.postMessage({ type: "RESULT", value: result });
  }
});

Message Event Properties

window.addEventListener("message", (event) => {
  console.log("Origin:", event.origin);        // Sender's origin
  console.log("Source:", event.source);        // Window reference
  console.log("Data:", event.data);            // Message data
  console.log("Type:", event.type);            // "message"
});

Security Best Practices

1. Always Verify Origin

window.addEventListener("message", (event) => {
  // ✅ Good: Verify origin
  const allowedOrigins = [
    "https://trusted-site.com",
    "https://another-trusted-site.com"
  ];
  
  if (!allowedOrigins.includes(event.origin)) {
    console.warn("Message from untrusted origin:", event.origin);
    return;
  }
  
  // Process message
  handleMessage(event.data);
});

2. Use Specific Target Origin

// ✅ Good: Specific origin
iframe.contentWindow.postMessage(data, "https://trusted-origin.com");

// ❌ Bad: Wildcard (allows any origin)
iframe.contentWindow.postMessage(data, "*");

3. Validate Message Data

window.addEventListener("message", (event) => {
  if (event.origin !== "https://trusted-origin.com") return;
  
  // Validate message structure
  if (!event.data || typeof event.data !== "object") {
    console.error("Invalid message format");
    return;
  }
  
  if (!event.data.type) {
    console.error("Message missing type");
    return;
  }
  
  // Process valid message
  handleMessage(event.data);
});

4. Use Message Types

// Define message types
const MessageTypes = {
  INIT: "INIT",
  UPDATE: "UPDATE",
  ERROR: "ERROR"
};

// Send typed messages
postMessage({ type: MessageTypes.UPDATE, payload: data });

// Handle typed messages
window.addEventListener("message", (event) => {
  switch (event.data.type) {
    case MessageTypes.INIT:
      handleInit(event.data);
      break;
    case MessageTypes.UPDATE:
      handleUpdate(event.data);
      break;
    case MessageTypes.ERROR:
      handleError(event.data);
      break;
  }
});

Transferable Objects

For better performance with large data:

// Transfer ArrayBuffer (moves ownership, original becomes unusable)
const buffer = new ArrayBuffer(1024);
worker.postMessage({ data: buffer }, [buffer]);
// buffer is now transferred, not copied

// Transfer ImageBitmap
const imageBitmap = await createImageBitmap(image);
canvas.transferControlToOffscreen();
worker.postMessage({ image: imageBitmap }, [imageBitmap]);

Error Handling

function safePostMessage(target, message, origin) {
  try {
    if (target && typeof target.postMessage === "function") {
      target.postMessage(message, origin);
    } else {
      console.error("Target window not available");
    }
  } catch (error) {
    console.error("PostMessage error:", error);
  }
}

// Usage
safePostMessage(iframe.contentWindow, data, "https://trusted-origin.com");

Common Use Cases

1. Authentication Token Sharing

// Parent sends auth token to iframe
iframe.contentWindow.postMessage(
  { type: "AUTH_TOKEN", token: userToken },
  "https://iframe-origin.com"
);

2. Form Data Collection

// Iframe collects form data and sends to parent
window.parent.postMessage(
  { type: "FORM_SUBMIT", formData: formData },
  "https://parent-origin.com"
);

3. Resize Notifications

// Iframe notifies parent of size changes
const resizeObserver = new ResizeObserver(() => {
  window.parent.postMessage(
    { type: "RESIZE", dimensions: { width, height } },
    "https://parent-origin.com"
  );
});

Browser Support

PostMessage is supported in:

  • Chrome 1+
  • Firefox 3+
  • Safari 4+
  • Edge 12+
  • IE 8+

Summary

The PostMessage API enables secure cross-origin communication between different browsing contexts. It's essential for iframe communication, web worker messaging, and window-to-window data exchange. Always verify message origins and validate data to ensure security.