Fix "This block contains unexpected or invalid content" in Gutenberg

intermediate๐Ÿ“ WordPress2026-06-11| WordPress 5.0+, Gutenberg block editor, any PHP/MySQL stack (Nginx or Apache)

Error Message

This block contains unexpected or invalid content
#gutenberg#block-editor#wordpress-ui#validation-error

The error

You open a post in Gutenberg and hit a gray banner on one or more blocks:

This block contains unexpected or invalid content

Two buttons appear: Attempt Block Recovery and Convert to HTML. You click "Attempt Block Recovery." Nothing happens โ€” or the content disappears. Here's how to get it back.

Why this happens

Gutenberg stores blocks as HTML comments: <!-- wp:paragraph --> delimiters with JSON attributes embedded. On every load, it validates the saved markup against each block's registered schema. Any mismatch throws this error.

Six common triggers:

  • A plugin or theme update changed a block's output format. Old saved markup no longer validates against the new schema.
  • Content was written directly into the post_content column via raw SQL or wpdb->update(), mangling the block comment delimiters.
  • A PHP notice or warning printed output before <!-- wp:blockname -->, corrupting the delimiter string.
  • Copy-pasting from Word or Google Docs injected non-standard markup โ€” smart quotes, &nbsp;, or extra wrapper divs โ€” breaking block parsing.
  • The block's JavaScript bundle wasn't loaded (missing enqueue_block_editor_assets hook registration), so Gutenberg couldn't recognize the block type.
  • A plugin's wp_update_post() call double-encoded block comment markers, turning <!-- into &lt;!--.

Step 1 โ€” Find the broken block

Before clicking any recovery button, switch to Code Editor mode: Ctrl+Shift+Alt+M (or โ‹ฎ menu โ†’ Code editor). You'll see the raw block markup. Scan for these signs of corruption:

<!-- wp:paragraph -->
<p>Normal content</p>
<!-- /wp:paragraph -->

<!-- wp:columns {"someProp":"value"} -->   <!-- โ† broken if plugin changed the schema -->
<div class="wp-block-columns">...</div>
<!-- /wp:columns -->

Red flags: mismatched open/close tags, HTML entities like &lt;!-- instead of <!--, stray PHP error text before a block marker, or a block type name that no longer exists after a plugin was removed.

Step 2 โ€” Try "Attempt Block Recovery" first

Click the three dots (โ‹ฎ) on the broken block โ†’ Attempt Block Recovery. Gutenberg rewrites the delimiter and re-validates the attributes. For minor schema drift after a plugin update, this usually works. Content comes back, you save, done.

If recovery wipes the content instead โ€” hit Ctrl+Z immediately, before saving. The undo stack survives block recovery attempts.

Step 3 โ€” Convert to HTML (safe fallback)

Recovery didn't work? Select Convert to HTML. This wraps the raw markup in a wp:html block, which Gutenberg stores verbatim without schema validation. The post renders correctly on the frontend. You lose block interactivity โ€” column drag handles, reusable block syncing โ€” but nothing gets deleted.

<!-- wp:html -->
<div class="wp-block-columns">
  <div class="wp-block-column">...</div>
</div>
<!-- /wp:html -->

Treat this as a temporary fix for a live site. Rebuild the block properly once you've tracked down the root cause.

Step 4 โ€” Fix corrupted delimiters in the database

When a plugin update breaks block attribute structure across many posts, use WP-CLI rather than editing each post manually:

# View raw post content
wp post get 123 --field=post_content

# Pull to a file, edit, then push back
wp post get 123 --field=post_content > /tmp/post123.html
# Edit /tmp/post123.html โ€” fix the block markup
wp post update 123 --post_content="$(cat /tmp/post123.html)"

If a plugin renamed its block type โ€” e.g. wp:old-plugin/card โ†’ wp:new-plugin/card โ€” do a targeted search-replace. Always dry-run first:

# Dry run โ€” shows what would change, touches nothing
wp search-replace '<!-- wp:old-plugin/card' '<!-- wp:new-plugin/card' --all-tables --dry-run

# Looks right? Drop --dry-run
wp search-replace '<!-- wp:old-plugin/card' '<!-- wp:new-plugin/card' --all-tables
wp search-replace '<!-- /wp:old-plugin/card' '<!-- /wp:new-plugin/card' --all-tables

Step 5 โ€” Hunt for PHP errors polluting block output

A sneaky culprit: a plugin printing a PHP notice or warning before the REST API response. Gutenberg receives JSON with stray text prepended, can't parse it, and marks the block invalid on reload.

# Watch the error log while saving a post
tail -f /var/log/nginx/error.log | grep -i 'php\|notice\|warning'

# Or enable debug logging in wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);

Spot Notice: Undefined variable entries timestamped exactly when you save a post? That's your culprit. Fix the offending plugin. Short-term workaround for production:

error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);

Step 6 โ€” Re-register missing block types

Deactivating a plugin removes its block registrations. Gutenberg flags every one of those blocks as invalid. Re-enabling the plugin fixes it instantly.

Want to remove the plugin permanently? Convert all its blocks to HTML (Step 3) before deactivating. Find the affected posts first:

wp db query "SELECT ID, post_title FROM wp_posts WHERE post_content LIKE '%wp:removed-plugin%' AND post_status != 'auto-draft';"

Verify the fix

  • Open the post in Gutenberg โ€” no gray banners should appear.
  • Switch to Code Editor mode. Confirm every <!-- wp:blockname --> has a matching <!-- /wp:blockname -->.
  • Save, then hard-refresh with Ctrl+Shift+R. Gutenberg re-validates all blocks on load.
  • View the post on the frontend โ€” content should render correctly.
  • Run wp post get <ID> --field=post_content and scan for stray characters before block markers.

Prevent it from happening again

  • Before any block plugin update: snapshot the database โ€” wp db export backup_$(date +%Y%m%d).sql. Takes about 10 seconds and can save hours of recovery work.
  • Block plugins should ship a deprecated array in their block registration so Gutenberg migrates old markup automatically. If an update breaks blocks without providing deprecations, that's a bug โ€” file one.
  • Never edit post_content directly via raw SQL. Use wp_update_post() and verify the returned content still has intact block delimiters.
  • Test plugin updates on staging first. A staging environment with recent production content catches validation errors before real users see them.

Related Error Notes