WordPress 7.0 Custom Block Breaks the Editor Iframe

WordPress 7.0 forces the iframed block editor, so an old apiVersion block loses its styles and scripts for everyone. Here is the apiVersion 3 fix that holds.
WordPressGutenbergBlock Editor
May 22, 20265 min read919 words

The Problem

I updated a client site to WordPress 7.0 the morning it shipped, and the block editor came up looking wrong. Their custom pricing-table block rendered fine on the public site, but inside the editor it had lost every bit of its styling, and the small script that powered the tab switcher threw an error before the block even appeared. Stranger still, core blocks on the same page that had always worked were now unstyled too.

The console showed this on load:

Uncaught TypeError: Cannot read properties of null (reading 'querySelector')
    at initPricingTabs (pricing-block.js:14:31)

Only one block on the whole site was old. Everything else used core blocks. Yet the entire editing canvas was affected. If you upgraded to WordPress 7.0 and your custom blocks suddenly look broken in the editor while the front end is fine, this is almost certainly what you are hitting.

Why It Happens

WordPress 7.0 finishes the move it has been building toward for two years: the post editor canvas now renders fully inside an <iframe>. That iframe has its own document and its own window. The admin chrome around it does not.

Two things break the moment that happens.

First, scripts. Most older block scripts call the global document or window directly. Before 7.0 that worked because the block markup lived in the same DOM as the admin page. Now the block lives inside the iframe, so document.querySelector('.tab') runs against the parent document, finds nothing, and returns null. That is the Cannot read properties of null error above.

Second, styles. Editor styles enqueued with enqueue_block_editor_assets load in the parent frame only. They never reach the iframe where your block is actually drawn, so the block renders with no CSS.

There is one more piece that explains why core blocks broke too. A block registered with apiVersion 1 or 2 is treated as not iframe-ready. WordPress has to keep that block working, and the compatibility path it falls back to drags the shared canvas out of its isolated render, which is why one stale block can knock the styling out for everything around it. The official iframe migration guide spells out the version requirement.

The Fix

Three changes, all on the block itself. None of them touch the front end.

First, bump the block to apiVersion 3 in block.json. Core blocks have shipped at version 3 since 6.3, so this is just catching your block up.

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "acme/pricing-table",
  "editorScript": "file:./index.js",
  "viewScript": "file:./view.js",
  "editorStyle": "file:./editor.css"
}

Second, stop reaching for the global document and window. Grab a ref to your block node and read ownerDocument off it, which is the iframe document where the block actually lives.

import { useRef, useEffect } from '@wordpress/element';
import { useBlockProps } from '@wordpress/block-editor';

export default function Edit() {
  const blockProps = useBlockProps();
  const ref = useRef(null);

  useEffect(() => {
    const node = ref.current;
    if (!node) return;

    const doc = node.ownerDocument;   // the iframe document, not the parent
    const win = doc.defaultView;      // the iframe window
    const tabs = node.querySelectorAll('.tab');

    tabs.forEach((tab) => {
      tab.addEventListener('click', () => {
        // wire listeners against `node`/`doc`, never the global `document`
      });
    });
  }, []);

  return <div {...blockProps} ref={ref}>{/* block markup */}</div>;
}

Third, enqueue your editor styles so they load inside the iframe. Swap enqueue_block_editor_assets for enqueue_block_assets, which has run in both the parent and the iframe since 6.3.

add_action('enqueue_block_assets', function () {
    if (! is_admin()) {
        return;
    }
    wp_enqueue_style(
        'acme-pricing-editor',
        plugins_url('editor.css', __FILE__),
        [],
        '1.0.0'
    );
});

That is_admin() guard keeps the editor-only CSS from leaking onto the front end, since enqueue_block_assets fires in both places.

To find the culprit fast on a big plugin stack, install the Plugin Check plugin. Its WordPress 7.0 rules now flag any block registered below apiVersion 3, so you get a list instead of bisecting by hand.

Verify the fix by opening the editor, then inspecting the canvas: confirm the block is rendered inside the <iframe>, the styles are applied, and the console is clean. If the block draws correctly with its CSS and no null errors, the iframe migration is holding.

The Lesson

The iframed editor is not a bug, it is the direction WordPress committed to, and 7.0 is the version that stops tolerating blocks that ignore it. The fix is always the same shape: declare apiVersion 3, read the document off the block node instead of the global, and enqueue editor styles where the iframe can see them. Raising the version alone is not enough if your scripts still touch the global document.

If a 7.0 update left your custom blocks broken in the editor and you would rather not bisect a 30-plugin stack yourself, this is exactly the kind of migration I handle on client sites. See my services. For a related editor styling problem from the previous release, read WordPress 6.9 block styles broken layout.

Need your blocks working in the 7.0 editor without a rebuild? Get in touch.

Back to blogStart a project