WordPress 7 Block Editor Shows a Blank Canvas — How to Fix CSP Headers for the Iframed Editor

· 9 min read

A client messaged me last week: "I updated to WordPress 7 and now I can't edit any pages. The sidebar is there but the writing area is just white." They sent a screenshot — the block inserter, settings panel, and top toolbar all rendered perfectly. The main editing canvas was gone. Just a blank white rectangle where their content should have been.

I have seen this exact symptom on four sites since WordPress 7.0 shipped in May. Every time, the cause was the same: a Content Security Policy header blocking the editor's blob: iframe. With WordPress 7.1 releasing on 19 August and removing the last fallback to a non-iframed editor, this is about to hit a lot more sites.

What Changed in WordPress 7.0

The block editor has been gradually moving towards rendering the editing canvas inside an iframe. The template editor was iframed back in WordPress 5.8. The site editor followed. In WordPress 7.0, the post editor joined them — conditionally. If every block in your post content used Block API version 3 or higher, WordPress rendered the editor canvas inside a blob: URL iframe.

That conditional is important. Some sites escaped the problem in 7.0 because they had older blocks in their content that forced the editor back to non-iframe mode. WordPress 7.1 removes that fallback entirely. After 19 August, the post editor is always iframed, regardless of theme type or block API versions. Gutenberg PR #74042 deleted the conditions outright.

The iframe uses a blob: URL — not srcdoc, not a regular HTTP URL. JavaScript constructs an HTML document in memory, creates a Blob from it, and generates a URL via URL.createObjectURL(). The result looks like:

blob:https://example.com/550e8400-e29b-41d4-a716-446655440000

This gives the iframe the same origin as the parent admin page. It works perfectly — unless your server sends a Content Security Policy header that does not permit blob: as a frame source.

The Console Error That Gives It Away

Open the browser console on the broken editor page. You will see something like this:

Refused to frame 'blob:https://example.com/550e8400-e29b-41d4-a716-446655440000'
because it violates the following Content Security Policy directive:
"default-src 'self'". Note that 'frame-src' was not explicitly set,
so 'default-src' is used as a fallback.

Followed immediately by a JavaScript error:

Uncaught TypeError: Cannot destructure property 'documentElement' of 'z' as it is null

That second error is the editor code trying to access the iframe's contentDocument, which is null because the browser refused to load the blob: URL. The sidebar loaded fine because it is part of the parent page, not the iframe.

If you see these two errors together, the fix is straightforward.

Where the CSP Header Comes From

Before changing anything, find out what is setting your CSP header. Open the Network tab in DevTools, click the post.php or post-new.php request, and look at the response headers for Content-Security-Policy. The header could be coming from any of these:

  • nginx — an add_header Content-Security-Policy directive in your server block or a location block
  • Apache — a Header set Content-Security-Policy line in .htaccess or the virtual host config
  • A WordPress security plugin — Shield Security, HTTP Headers, or any plugin that manages security headers
  • Your hosting panel — some panel hardening scripts add CSP headers
  • A CDN — Cloudflare Transform Rules, Fastly VCL, or similar

I have seen all five in the wild. The most common offender across the sites I manage is a hand-written nginx CSP header that was added during a security hardening pass and never updated.

The Fix: Add blob: to frame-src

The browser checks the CSP of the parent page (the wp-admin page) when JavaScript tries to create the blob: iframe. The directive that controls which URLs can be framed is frame-src. If frame-src is not set explicitly, the browser falls back to child-src, then to default-src.

You need blob: in frame-src. The WordPress admin area also needs 'unsafe-inline' and 'unsafe-eval' in script-src (the block editor uses eval() and inline scripts), and 'unsafe-inline' in style-src (the editor generates styles dynamically).

nginx — split CSP by location

# Strict CSP for front-end visitors
location / {
    add_header Content-Security-Policy
        "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';"
        always;
}

# Permissive CSP for wp-admin (block editor needs blob: frames)
location /wp-admin/ {
    add_header Content-Security-Policy
        "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src 'self' blob:; frame-ancestors 'self'; base-uri 'self';"
        always;
}

One nginx gotcha: if a child location block has any add_header directive, it replaces all headers from the parent block. You must repeat every header you want in each location that defines its own add_header.

Apache — conditional CSP in .htaccess


    # Strict CSP for front-end
    Header set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';"

    # Override for wp-admin
    
        Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src 'self' blob:; frame-ancestors 'self'; base-uri 'self';"
    

PHP — set the header from a must-use plugin

If you cannot modify server config (managed hosting, for example), set the CSP from PHP. Drop this in wp-content/mu-plugins/admin-csp.php:

<?php
add_action('admin_init', function () {
    header(
        "Content-Security-Policy: default-src 'self'; "
        . "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
        . "style-src 'self' 'unsafe-inline'; "
        . "img-src 'self' data: https:; "
        . "font-src 'self' data:; "
        . "frame-src 'self' blob:; "
        . "frame-ancestors 'self'; "
        . "base-uri 'self';"
    );
});

This only fires on admin pages, so it will not weaken your front-end CSP. Note that if your server also sends a CSP header, the PHP header may be ignored or may create a second CSP header — browsers enforce all CSP headers they receive, and the most restrictive one wins per directive. Remove or adjust the server-level header for /wp-admin/ to avoid conflicts.

Security plugins

If a plugin is setting the CSP, find its header configuration and add blob: to the frame-src directive. In Shield Security, this is under Security Zones > HTTP Headers > Content Security Policy. In the HTTP Headers plugin, edit the CSP rule directly. Each plugin handles it differently — check the browser response headers to confirm the plugin's header is the one that changed.

Test Before You Deploy

Before changing your production CSP, use Content-Security-Policy-Report-Only to test:

add_header Content-Security-Policy-Report-Only
    "default-src 'self'; frame-src 'self' blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;"
    always;

This logs violations to the browser console without actually blocking anything. Open the editor, confirm no new violations appear, then switch to the enforcing header.

Auditing Your Fleet

If you manage multiple sites, check which ones have CSP headers that will break in 7.1:

for site in /var/www/*/; do
  domain=$(basename "$site")
  csp=$(curl -sI "https://$domain/wp-admin/" 2>/dev/null \
    | grep -i "content-security-policy:" \
    | head -1)
  if [ -n "$csp" ] && ! echo "$csp" | grep -q "blob:"; then
    echo "NEEDS FIX: $domain"
    echo "  $csp"
  fi
done

This is not perfect — it will not catch CSP headers that only apply to authenticated requests — but it catches the server-level headers that cause most of the problems. For a thorough check, log into each site's admin and watch the browser console while opening the post editor.

The Bigger Picture

WordPress core still has no dedicated CSP filter hook (Trac #57424 has been open for over a year). The wp_headers filter works on front-end pages but does not reliably cover admin pages. The admin_init action is the recommended workaround for setting admin-area headers from PHP, but it is not elegant.

If you hardened your servers with CSP headers — which you should have — WordPress 7.1 is about to test whether those headers accounted for blob: iframes. Most did not, because until now there was no reason to. Fix them before 19 August, or your editors will be staring at a blank canvas wondering what happened.

This is exactly the kind of issue I catch during routine maintenance before it reaches clients. If you are managing WordPress sites and would prefer someone else handled the server configuration, security headers, and upgrade compatibility checks — have a look at the maintenance plans.

Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans Get in Touch

Get in Touch to Discuss Your Needs