Edition for Web Developers — Last Updated 29 October 2025
Support in all current engines.
Web browsers, for security and privacy reasons, prevent documents in different domains from affecting each other; that is, cross-site scripting is disallowed.
While this is an important security feature, it prevents pages from different domains from communicating even when those pages are not hostile. This section introduces a messaging system that allows documents to communicate with each other regardless of their source domain, in a way designed to not enable cross-site scripting attacks.
  
  The postMessage() API can be used as a tracking
  vector.
For example, if document A contains an iframe element that contains document B,
   and script in document A calls postMessage() on the
   Window object of document B, then a message event will be fired on that object,
   marked as originating from the Window of document A. The script in document A might
   look like:
var  o =  document. getElementsByTagName( 'iframe' )[ 0 ]; 
o. contentWindow. postMessage( 'Hello world' ,  'https://b.example.org/' ); To register an event handler for incoming events, the script would use addEventListener() (or similar mechanisms). For example, the script in document B
   might look like:
window. addEventListener( 'message' ,  receiver,  false ); 
function  receiver( e)  { 
  if  ( e. origin ==  'https://example.com' )  { 
    if  ( e. data ==  'Hello world' )  { 
      e. source. postMessage( 'Hello' ,  e. origin); 
    }  else  { 
      alert( e. data); 
    } 
  } 
} This script first checks the domain is the expected domain, and then looks at the message, which it either displays to the user, or responds to by sending a message back to the document which sent the message in the first place.
Use of this API requires extra care to protect users from hostile entities abusing a site for their own purposes.
Authors should check the origin attribute to
  ensure that messages are only accepted from domains that they expect to receive messages from.
  Otherwise, bugs in the author's message handling code could be exploited by hostile sites.
Furthermore, even after checking the origin
  attribute, authors should also check that the data in question is of the expected format.
  Otherwise, if the source of the event has been attacked using a cross-site scripting flaw, further
  unchecked processing of information sent using the postMessage() method could result in the attack being
  propagated into the receiver.
Authors should not use the wildcard keyword (*) in the targetOrigin argument in messages that contain any confidential information, as otherwise there is no way to guarantee that the message is only delivered to the recipient to which it was intended.
Authors who accept messages from any origin are encouraged to consider the risks of a denial-of-service attack. An attacker could send a high volume of messages; if the receiving page performs expensive computation or causes network traffic to be sent for each such message, the attacker's message could be multiplied into a denial-of-service attack. Authors are encouraged to employ rate limiting (only accepting a certain number of messages per minute) to make such attacks impractical.
window.postMessage(message [, options ])Posts a message to the given window. Messages can be structured objects, e.g. nested objects
    and arrays, can contain JavaScript values (strings, numbers, Date objects, etc.),
    and can contain certain data objects such as File Blob,
    FileList, and ArrayBuffer objects.
Objects listed in the transfer member
    of options are transferred, not just cloned, meaning that they are no longer usable
    on the sending side.
A target origin can be specified using the targetOrigin member of
    options. If not provided, it defaults to "/". This default
    restricts the message to same-origin targets only.
If the origin of the target window doesn't match the given target origin, the message is
    discarded, to avoid information leakage. To send the message to the target regardless of origin,
    set the target origin to "*".
Throws a "DataCloneError" DOMException if
    transfer array contains duplicate objects or if message could not be
    cloned.
window.postMessage(message, targetOrigin [, transfer ])This is an alternate version of postMessage() where the target origin is specified
   as a parameter. Calling window.postMessage(message, target, transfer) is
   equivalent to window.postMessage(message, {targetOrigin,
   transfer}).
When posting a message to a Window of a browsing context
  that has just been navigated to a new Document is likely to result in the message not
  receiving its intended recipient: the scripts in the target browsing context have to
  have had time to set up listeners for the messages. Thus, for instance, in situations where a
  message is to be sent to the Window of newly created child iframe,
  authors are advised to have the child Document post a message to their parent
  announcing their readiness to receive messages, and for the parent to wait for this message before
  beginning posting messages.
Support in all current engines.
elements, the main document and a single
Support in all current engines.
To enable independent pieces of code (e.g. running in different browsing contexts) to communicate directly, authors can use channel messaging.
Communication channels in this mechanism are implemented as two-ways pipes, with a port at each end. Messages sent in one port are delivered at the other port, and vice-versa. Messages are delivered as DOM events, without interrupting or blocking running tasks.
To create a connection (two "entangled" ports), the MessageChannel()
  constructor is called:
var  channel =  new  MessageChannel(); One of the ports is kept as the local port, and the other port is sent to the remote code, e.g.
  using postMessage():
otherWindow. postMessage( 'hello' ,  'https://example.com' ,  [ channel. port2]); To send messages, the postMessage() method on
  the port is used:
channel. port1. postMessage( 'hello' ); To receive messages, one listens to message events:
channel. port1. onmessage =  handleMessage; 
function  handleMessage( event)  { 
  // message is in event.data 
  // ... 
} Data sent on a port can be structured data; for example here an array of strings is passed on a
  MessagePort:
port1. postMessage([ 'hello' ,  'world' ]); In this example, two JavaScript libraries are connected to each other using
   MessagePorts. This allows the libraries to later be hosted in different frames, or
   in Worker objects, without any change to the APIs.
< script  src = "contacts.js" ></ script >  <!-- exposes a contacts object --> 
< script  src = "compose-mail.js" ></ script >  <!-- exposes a composer object --> 
< script > 
 var  channel =  new  MessageChannel(); 
 composer. addContactsProvider( channel. port1); 
 contacts. registerConsumer( channel. port2); 
</ script > Here's what the "addContactsProvider()" function's implementation could look like:
function  addContactsProvider( port)  { 
  port. onmessage =  function  ( event)  { 
    switch  ( event. data. messageType)  { 
      case  'search-result' :  handleSearchResult( event. data. results);  break ; 
      case  'search-done' :  handleSearchDone();  break ; 
      case  'search-error' :  handleSearchError( event. data. message);  break ; 
      // ... 
    } 
  }; 
}; Alternatively, it could be implemented as follows:
function  addContactsProvider( port)  { 
  port. addEventListener( 'message' ,  function  ( event)  { 
    if  ( event. data. messageType ==  'search-result' ) 
      handleSearchResult( event. data. results); 
  }); 
  port. addEventListener( 'message' ,  function  ( event)  { 
    if  ( event. data. messageType ==  'search-done' ) 
      handleSearchDone(); 
  }); 
  port. addEventListener( 'message' ,  function  ( event)  { 
    if  ( event. data. messageType ==  'search-error' ) 
      handleSearchError( event. data. message); 
  }); 
  // ... 
  port. start(); 
}; The key difference is that when using addEventListener(), the start() method must also be invoked. When using onmessage, the call to start() is implied.
The start() method, whether called explicitly or
   implicitly (by setting onmessage),
   starts the flow of messages: messages posted on message ports are initially paused, so that they
   don't get dropped on the floor before the script has had a chance to set up its handlers.
Ports can be viewed as a way to expose limited capabilities (in the object-capability model sense) to other actors in the system. This can either be a weak capability system, where the ports are merely used as a convenient model within a particular origin, or as a strong capability model, where they are provided by one origin provider as the only mechanism by which another origin consumer can effect change in or obtain information from provider.
For example, consider a situation in which a social web site embeds in one iframe
  the user's email contacts provider (an address book site, from a second origin), and in a second
  iframe a game (from a third origin). The outer social site and the game in the second
  iframe cannot access anything inside the first iframe; together they can
  only:
Navigate the iframe to a new URL, such as the same
   URL but with a different fragment,
   causing the Window in the iframe to receive a hashchange event.
Resize the iframe, causing the Window in the iframe
   to receive a resize event.
Send a message event to the Window in the
   iframe using the window.postMessage()
   API.
The contacts provider can use these methods, most particularly the third one, to provide an API
  that can be accessed by other origins to manipulate the user's address book. For example, it could
  respond to a message "add-contact Guillaume Tell
  <tell@pomme.example.net>" by adding the given person and email address to the user's
  address book.
To avoid any site on the web being able to manipulate the user's contacts, the contacts provider might only allow certain trusted sites, such as the social site, to do this.
Now suppose the game wanted to add a contact to the user's address book, and that the social site was willing to allow it to do so on its behalf, essentially "sharing" the trust that the contacts provider had with the social site. There are several ways it could do this; most simply, it could just proxy messages between the game site and the contacts site. However, this solution has a number of difficulties: it requires the social site to either completely trust the game site not to abuse the privilege, or it requires that the social site verify each request to make sure it's not a request that it doesn't want to allow (such as adding multiple contacts, reading the contacts, or deleting them); it also requires some additional complexity if there's ever the possibility of multiple games simultaneously trying to interact with the contacts provider.
Using message channels and MessagePort objects, however, all of these problems can
  go away. When the game tells the social site that it wants to add a contact, the social site can
  ask the contacts provider not for it to add a contact, but for the capability to add a
  single contact. The contacts provider then creates a pair of MessagePort objects, and
  sends one of them back to the social site, who forwards it on to the game. The game and the
  contacts provider then have a direct connection, and the contacts provider knows to only honor a
  single "add contact" request, nothing else. In other words, the game has been granted the
  capability to add a single contact.
Continuing the example from the previous section, consider the contacts provider in particular.
  While an initial implementation might have simply used XMLHttpRequest objects in the
  service's iframe, an evolution of the service might instead want to use a shared worker with a single WebSocket connection.
If the initial design used MessagePort objects to grant capabilities, or even just
  to allow multiple simultaneous independent sessions, the service implementation can switch from
  the XMLHttpRequests-in-each-iframe model to the
  shared-WebSocket model without changing the API at all: the ports on the service
  provider side can all be forwarded to the shared worker without it affecting the users of the API
  in the slightest.
Support in all current engines.
channel = new MessageChannel()Returns a new MessageChannel object with two new MessagePort
    objects.
channel.port1Returns the first MessagePort object.
channel.port2Returns the second MessagePort object.
MessagePort, Worker, and
  DedicatedWorkerGlobalScopeThe following are the event handlers (and their corresponding event handler event types)  supported,
   as event handler IDL attributes, by MessagePort,
   Worker, and DedicatedWorkerGlobalScope objects:
| Event handler | Event handler event type | 
|---|---|
| onmessageSupport in all current engines. Firefox41+Safari5+Chrome2+ Opera10.6+Edge79+ Edge (Legacy)12+Internet Explorer10+ Firefox Android?Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11.5+ DedicatedWorkerGlobalScope/message_event Support in all current engines. Firefox3.5+Safari4+Chrome4+ Opera10.6+Edge79+ Edge (Legacy)12+Internet Explorer10+ Firefox Android?Safari iOS5+Chrome Android?WebView Android37+Samsung Internet?Opera Android11.5+ | message | 
| onmessageerrorMessagePort/messageerror_event Support in all current engines. Firefox57+Safari16.4+Chrome60+ Opera?Edge79+ Edge (Legacy)18Internet ExplorerNo Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android47+ DedicatedWorkerGlobalScope/messageerror_event Support in all current engines. Firefox57+Safari16.4+Chrome60+ Opera?Edge79+ Edge (Legacy)18Internet ExplorerNo Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android47+ | messageerror | 
Support in all current engines.
Each channel has two message ports. Data sent through one port is received by the other port, and vice versa.
port.postMessage(message [, transfer])port.postMessage(message [, { transfer }])Posts a message through the channel. Objects listed in transfer are transferred, not just cloned, meaning that they are no longer usable on the sending side.
Throws a "DataCloneError" DOMException if
    transfer contains duplicate objects or port, or if message
    could not be cloned.
port.start()Begins dispatching messages received on the port.
port.close()Disconnects the port, so that it is no longer active.
Authors are strongly encouraged to explicitly close MessagePort
  objects to disentangle them, so that their resources can be recollected. Creating many
  MessagePort objects and discarding them without closing them can lead to high
  transient memory usage since garbage collection is not necessarily performed promptly, especially
  for MessagePorts where garbage collection can involve cross-process coordination.
Support in all current engines.
Support in all current engines.
Pages on a single origin opened by the same user in the same user agent but in different unrelated browsing contexts sometimes need to send notifications to each other, for example "hey, the user logged in over here, check your credentials again".
For elaborate cases, e.g. to manage locking of shared state, to manage synchronization of
  resources between a server and multiple local clients, to share a WebSocket
  connection with a remote host, and so forth, shared workers are
  the most appropriate solution.
For simple cases, though, where a shared worker would be an unreasonable overhead, authors can use the simple channel-based broadcast mechanism described in this section.
broadcastChannel = new BroadcastChannel(name)Returns a new BroadcastChannel object via which messages for the given channel
    name can be sent and received.
broadcastChannel.nameReturns the channel name (as passed to the constructor).
broadcastChannel.postMessage(message)Sends the given message to other BroadcastChannel objects set up for this
    channel. Messages can be structured objects, e.g. nested objects and arrays.
broadcastChannel.close()Closes the BroadcastChannel object, opening it up to garbage
   collection.
Authors are strongly encouraged to explicitly close BroadcastChannel
  objects when they are no longer needed, so that they can be garbage collected. Creating many
  BroadcastChannel objects and discarding them while leaving them with an event
  listener and without closing them can lead to an apparent memory leak, since the objects will
  continue to live for as long as they have an event listener (or until their page or worker is
  closed).
Suppose a page wants to know when the user logs out, even when the user does so from another tab at the same site:
var  authChannel =  new  BroadcastChannel( 'auth' ); 
authChannel. onmessage =  function  ( event)  { 
  if  ( event. data ==  'logout' ) 
    showLogout(); 
} 
function  logoutRequested()  { 
  // called when the user asks us to log them out 
  doLogout(); 
  showLogout(); 
  authChannel. postMessage( 'logout' ); 
} 
function  doLogout()  { 
  // actually log the user out (e.g. clearing cookies) 
  // ... 
} 
function  showLogout()  { 
  // update the UI to indicate we're logged out 
  // ... 
}