Sending Custom Data between WordPress Customizer Preview and Controls Panes

TL;DR: Use wp.customize.Messenger() to send postMessage data to or from the iframe. See below for a minimal example.


Note: This isn’t about previewing custom theme options.

The WordPress Customizer is divided into two panes: the “Controls” pane where you configure options, and the “Preview” pane where you get a real-time preview of the changes you made in the Controls pane. The Preview pane is inside an iframe, so if you want to send custom data (“messages”) back and forth between them, you need to use the postMessage API.

The Customizer is already doing that for previewing theme options, but there are lots of other use cases for sending messages between the frames, like listening for keyboard events in the Preview pane, and then responding to them in the Controls pane.

Writing your own postMessage send/receive functions is fairly straightforward, but there are some security considerations to take into account, and the Customizer API already provides the wp.customize.Messenger class that can do all of that for you, so it’s best to use that, rather than rolling your own solution.

Minimal Example

Save this as wp-content/mu-plugins/post-message-example.php, then browse to the Customizer and open the dev tools console. Then, click inside the Preview frame, and start pressing keys. You should see the events being logged to the console.

<?php

// Print JavaScript for the Preview pane
add_action( 'customize_preview_init', function() {
	add_action( 'wp_print_scripts', function() {
		?>

		<script>
			// Wait until `wp.customize.settings`, etc is available, and then listen for keyboard events.
			document.addEventListener( 'DOMContentLoaded', () => {
				window.addEventListener( 'keyup', sendMessage );
			} );

			/**
			 * Send a `postMessage` to the Controls pane.
			 *
			 * @param {object} event
			 */
			const sendMessage = event => {
				const messenger = new wp.customize.Messenger( {
					channel      : 'my-plugin-slug',
					targetWindow : window.parent,
					url          : wp.customize.settings.url.allowed[ 0 ],
				} );

				// Note that you can't send `event` itself, since its properties aren't enumerable.
				// @see https://stackoverflow.com/q/48054951/450127
				const message = { key : event.which };

				messenger.send( 'my-message-id', message );
			}
		</script>

		<?php
	} );
} );

// Print JavaScript for the Controls pane
add_action( 'customize_controls_print_scripts', function() {
	?>

	<script>
		// Wait until `wp.customize.settings` is available and the `iframe` has been added to the DOM.
		document.addEventListener( 'DOMContentLoaded', () => {
			wp.customize.bind( 'ready', () => {
				wp.customize.previewer.bind( 'ready', receiveMessage );
			} );
		} );

		/**
		 * Listen for messages from the Previewer frame and handle them.
		 */
		const receiveMessage = () => {
			const iframe = document.getElementById( 'customize-preview' ).getElementsByTagName( 'iframe' )[0];

			const messenger = new wp.customize.Messenger( {
				channel      : 'my-plugin-slug',
				targetWindow : iframe.contentWindow,
				url          : wp.customize.settings.url.allowed[ 0 ],
			} );

			// Handle messages that are received.
			messenger.bind( 'my-message-id', event => {
				console.log( 'Received message from Previewer iframe:', event );
			} );
		}
	</script>

	<?php
} );

Real World Example

If you want to see a real-world example, you can look at how Quick Navigation Interface sends and receives messages.

Leave a Reply

Your email address will not be published. Required fields are marked *