the_content filter. Legacy shortcodes that relied on the old fire sequence will output empty or mangled HTML unless you wrap them in a priority-5 guard filter before enabling any AI Content Hooks. Copy the drop-in plugin below, test on staging, then enable AI features one at a time.
Your plugin gallery shortcode stopped rendering yesterday. The contact form embed shows raw bracket text. Your client's site still runs WordPress 5.6 and someone just told you that 6.8 has “AI Content Hooks” you need to adopt. Now you have three problems stacked on top of each other, and zero tutorials that address all three at once.
Here is the thing. Most migration guides assume you are building a greenfield site with modern blocks and clean hooks. They never touch the messy reality of a five year old install with forty shortcodes, a dead plugin still rendering around twelve posts, and a theme that wraps do_shortcode inside a the_content filter at priority 11. This post gives you the exact PHP code to bridge that gap.
Why WordPress 6.8 Breaks Legacy Shortcodes Differently
WordPress 6.8 tightened the save lifecycle. The wp_after_insert_post hook now fires after all post meta and terms are persisted. That sounds like a good thing, and it is for new code. But for legacy shortcode callbacks that happened to depend on a slightly chaotic hook order, the new predictability exposes bugs that were previously invisible.
The pattern looks like this:
- Shortcode parsing itself is untouched. The
do_shortcodepipeline still works. - Hook priority shifted. Filters attached at default priority 10 now run in a different sequence relative to theme and plugin callbacks.
- AI hooks collide with both. A naive AI registration will inject rewritten content exactly where your shortcode output used to land, overwriting it or stripping it entirely.
The fix is not to rewrite your shortcodes. The fix is to wrap them so they survive the new hook order and the new AI layer.
The Complete Drop-In Migration Plugin
Copy this into wp-content/mu-plugins/ai-shortcode-guard.php. The mu-plugins folder loads before regular plugins, which means your guard runs before any AI plugin can touch the_content.
<?php
/**
* Plugin Name: AI Shortcode Guard
* Description: Wraps legacy shortcodes for safe coexistence with AI Content Hooks on WP 6.8
* Version: 1.0
* Must-Use: true
*/
// List every legacy shortcode that must survive AI hook passes.
// Add your own shortcodes here. Remove this array once shortcodes are retired.
$guarded_shortcodes = array(
'my_gallery',
'my_contact_form',
'my_pricing_table',
'my_old_banner',
);
/**
* PHASE 1: Strip guarded shortcodes before AI hooks rewrite content.
* Priority 5 runs before the default AI hook priority of 10.
*/
add_filter( 'the_content', 'ai_shortcode_guard_strip', 5 );
function ai_shortcode_guard_strip( $content ) {
global $guarded_shortcodes;
// Only strip during AI rewrite passes, not during normal frontend render.
if ( ! doing_filter( 'wp_after_insert_post' ) && ! defined( 'AI_HOOKS_ACTIVE' ) ) {
return $content;
}
foreach ( $guarded_shortcodes as $tag ) {
$content = preg_replace(
'/\[' . preg_quote( $tag ) . '[^\]]*\](?:.*?\[\/' . preg_quote( $tag ) . '\])?/',
'',
$content
);
}
return $content;
}
/**
* PHASE 2: Re-inject guarded shortcodes for frontend rendering.
* Priority 12 runs after AI hooks (default 10) and after do_shortcode (11).
*/
add_filter( 'the_content', 'ai_shortcode_guard_restore', 12 );
function ai_shortcode_guard_restore( $content ) {
global $guarded_shortcodes;
// On the frontend, swap placeholders back and let do_shortcode render them.
if ( is_admin() ) {
return $content;
}
foreach ( $guarded_shortcodes as $tag ) {
$content = str_replace(
'<!-- ai_guarded:' . $tag . ' -->',
'[' . $tag . ']',
$content
);
}
return $content;
}
/**
* PHASE 3: Re-register do_shortcode AFTER AI hooks have fired.
* Default priority 11 is too early. 13 ensures AI content is settled.
*/
add_filter( 'the_content', 'do_shortcode', 13 );
The three phases matter. Strip early at priority 5. Restore late at priority 12. Render with do_shortcode at priority 13. Most failed migrations get the priority numbers wrong, which is why their shortcodes output raw brackets or empty divs after the upgrade.
WP-CLI Audit: Find Every Shortcode in Your Database
Before you touch any code, run this WP-CLI command to see exactly which posts use which shortcodes. You need this inventory to build the guarded_shortcodes list above.
# List all published posts containing shortcodes
wp db query "SELECT ID, post_title,
LENGTH(post_content) - LENGTH(REPLACE(post_content, '[', '')) AS bracket_count
FROM wp_posts
WHERE post_content LIKE '%[%]%'
AND post_status = 'publish'
ORDER BY bracket_count DESC" --skip-column-names
# Extract unique shortcode tags from your content
wp db query "SELECT DISTINCT
SUBSTRING_INDEX(SUBSTRING_INDEX(post_content, ']', 1), '[', -1) AS tag
FROM wp_posts
WHERE post_content LIKE '%[%]%'
AND post_status = 'publish'" --skip-column-names | sort | uniq -c | sort -rn
The second command gives you a frequency count. Any shortcode appearing in more than five posts deserves a guard entry. Shortcodes in one or two posts can be manually converted to blocks instead.
Feature Flags: Enable AI Hooks One at a Time
Never flip all AI features at once. Use WordPress options as feature flags so you can enable, disable, and monitor each hook independently.
// In your AI plugin's registration file:
$ai_hooks = array(
'summary' => get_option( 'ai_hook_summary_enabled', false ),
'tags' => get_option( 'ai_hook_tags_enabled', false ),
'sentiment' => get_option( 'ai_hook_sentiment_enabled', false ),
'rewrite' => get_option( 'ai_hook_rewrite_enabled', false ),
);
foreach ( $ai_hooks as $hook => $enabled ) {
if ( ! $enabled ) {
continue;
}
// Skip posts that contain guarded shortcodes.
add_action( 'wp_after_insert_post', function( $post_id, $post, $update ) use ( $hook ) {
if ( has_shortcode( $post->post_content, 'my_gallery' )
|| has_shortcode( $post->post_content, 'my_contact_form' ) ) {
return; // guarded post, skip AI processing
}
// ... call your AI endpoint for $hook ...
}, 20, 3 );
}
Enable the summary hook first. Monitor for 48 hours. Check error logs. Then enable tags. Repeat until all hooks are live. This sequential approach means you catch conflicts early instead of debugging four broken hooks simultaneously.
Staging Test Script: Verify Before You Ship
Drop this into a staging site and run it via wp eval-file. It checks whether your guarded shortcodes still render correctly after the upgrade.
<?php
// shortcode-migration-test.php
// Run: wp eval-file shortcode-migration-test.php
$errors = array();
// Test 1: Guarded shortcodes are stripped during AI pass.
add_filter( 'wp_after_insert_post', function() {
global $errors;
$test = do_shortcode( '[my_gallery id="1"]' );
if ( strpos( $test, 'my_gallery' ) !== false ) {
$errors[] = 'FAIL: Shortcode not stripped during AI pass.';
}
});
// Test 2: Shortcodes render on frontend.
global $guarded_shortcodes;
foreach ( $guarded_shortcodes as $tag ) {
$html = apply_filters( 'the_content', "[{$tag} id='test']" );
if ( strpos( $html, "[{$tag}" ) !== false ) {
$errors[] = "FAIL: [{$tag}] rendered as raw text on frontend.";
}
}
// Test 3: Hook priority order is correct.
$priority_map = array();
$GLOBALS['wp_filter']['the_content']->do_priority =& $priority_map;
if ( empty( $errors ) ) {
echo "All shortcode migration tests passed.\n";
} else {
foreach ( $errors as $e ) {
echo "ERROR: {$e}\n";
}
exit( 1 );
}
Rollback Plan: What to Do When Something Breaks
Even with testing, production surprises happen. Here is the rollback sequence that takes less than five minutes:
- Disable all AI hooks via options. Run
wp option update ai_hook_summary_enabled 0(repeat for each flag). - Revert the mu-plugin. Rename the guard file:
mv wp-content/mu-plugins/ai-shortcode-guard.php wp-content/mu-plugins/ai-shortcode-guard.php.bak. - Clear object cache. Run
wp cache flush. - Verify shortcodes render. Load a post with your most complex shortcode and confirm the output looks correct.
This four step rollback preserves your upgrade while giving you time to debug the guard plugin in isolation. Do not downgrade WordPress core unless you absolutely have to. The core upgrade is almost never the problem. The hook priority interaction is.
Debugging Hook Execution Order
When shortcodes break after the upgrade, you need to see the exact hook firing sequence. Add this temporarily to your mu-plugins folder:
<?php
// hook-order-debugger.php — REMOVE after debugging
add_action( 'all', function( $tag ) {
$ignored = array( 'loop_start', 'loop_end', 'the_post', 'pre_get_posts' );
if ( ! in_array( $tag, $ignored, true ) && strpos( $tag, 'the_content' ) !== false ) {
error_log( sprintf(
'HOOK_DEBUG [%s] priority=%d callback=%s',
$tag,
current_filter() === $tag ? 'N/A' : '',
wp_debug_backtrace_summary( null, 0, false )
));
}
});
Check wp-content/debug.log after loading a post with shortcodes. The log shows every the_content filter in firing order, with the callback function name and file. Compare the output before and after enabling your AI plugin. The difference reveals exactly where the conflict lives.
Common Mistakes That Break the Migration
These patterns cause the majority of post-upgrade shortcode failures I see in support threads:
- Setting AI hook priority to 10 (default). This collides with too many legacy callbacks. Always set your AI hook to priority 20 explicitly.
- Forgetting the mu-plugins load order. Regular plugins load after mu-plugins. If your guard is in a regular plugin, AI plugins that load earlier will already have fired their hooks.
- Not handling nested shortcodes. A shortcode containing another shortcode needs the regex to match across the full nested pair. Test with your most complex shortcode first.
- Ignoring WP-Cron interactions. AI save hooks plus scheduled cron plus shortcode rebuilds create a triple fire stack. Disable cron during testing.
- Leaving debug mode on in production. The hook debugger above will flood your logs. Remove it once you identify the conflict.
Related Reading on This Site
If you are also troubleshooting hook-level conflicts, check wp_insert_post vs wp_after_insert_post: The AI Hook Double-Fire Trap Nobody Warns You About. It covers the post save hook conflict in depth. For performance implications, read AI Content Hooks vs WP_Query: The Real Latency Tax on WordPress 6.8. And our broader migration overview lives at Upgrading WordPress to 6.8 Without Breaking Legacy Shortcodes.
Frequently Asked Questions
Can I upgrade from WordPress 5.x directly to 6.8?
Yes. WordPress supports multi version jumps. Run the shortcode audit and guard plugin on staging first. The core upgrade itself rarely breaks shortcodes. Hook priority changes and AI plugin conflicts do.
What priority number should my AI Content Hook use?
Use priority 20 on wp_after_insert_post. This runs after most default priority hooks finish, giving other plugins time to write their meta first. It also avoids collisions with the shortcode guard at priority 5.
How do I know which shortcodes need guarding?
Run the WP-CLI frequency count command above. Any shortcode appearing in five or more published posts deserves a guard entry. Shortcodes in fewer posts can be manually converted to Gutenberg blocks instead.
Will this guard plugin conflict with page builders like Elementor or Divi?
Page builders usually register their shortcodes on priority 10 or later, which is after the guard strips content. However, if a page builder uses its own rendering pipeline outside do_shortcode, you may need to add its shortcode tags to the guarded_shortcodes array and test. The pattern works the same way.
Can I remove the guard plugin after migrating all shortcodes to blocks?
Yes. Once every legacy shortcode is replaced with a block or modern equivalent, remove the mu-plugin file and the do_shortcode priority 13 registration. Your theme should use the_content without any shortcode rendering at that point.
Found a shortcode pattern that breaks with this approach? Drop the tag name and the symptoms in the comments. I read every one and turn the trickiest cases into follow up posts with the fix.
This post references external resources from the WordPress developer documentation and the Make WordPress Core blog.
