Pattern Manager is a WordPress plugin from WP Engine for building and managing block pattern PHP files. I picked it up for a personal project, immediately ran into a wall of bugs and missing functionality, and ended up spending more time patching it than I’d budgeted. Here’s what was broken and what Claude Code and I did about it.
Modernizing the stack
Before any of the bug work, the plugin needed some housekeeping. The plugin’s declared minimums were PHP 7.4 and WordPress 6.1 — both well behind what the codebase was actually doing. Bumped those to PHP 8.0 and WordPress 6.4.
On the JavaScript side, the app module was still on React 17 and a fairly old set of @wordpress packages. Upgraded to React 18, @wordpress/scripts 31, @wordpress/components 32, and updated the supporting packages to match. The React upgrade meant swapping out the deprecated render() call for createRoot(), and wp_localize_script got replaced with wp_add_inline_script, which is the current recommended approach for passing PHP data to JavaScript.
I also added object caching to get_theme_patterns(). Every request was reading and parsing all the pattern PHP files from disk with no caching layer — on every page load. Wrapped it in wp_cache_get/wp_cache_set with a cache key scoped to the active theme, and added an invalidate_patterns_cache() helper that fires whenever a pattern is written or deleted.
The editor sidebar basically didn’t exist
This one took me a while to even notice — and that’s kind of the most embarrassing part of this process.
Pattern Manager ships with a block editor module that’s supposed to add a sidebar with pattern metadata fields: categories, keywords, description, viewport width, post types, transforms. None of it was showing up. I just assumed I was missing something.
Turns out in the version I had laying around, the JavaScript had never been built. The js/build/ directory didn’t exist, and the enqueue function bails early if index.asset.php isn’t there. The whole sidebar had been silently doing nothing.
npm install && npm run build
Run that inside wp-modules/editor/ and the whole thing turns on. Not complicated once you do it — and you will, if you want to use my version of it, or make additional modifications.
Renaming a pattern would destroy its content
This one is actually nasty. If you rename a pattern through the editor’s attribute panel, the pattern’s PHP file gets overwritten with an empty body. Your content is just gone.
The root cause is interesting. Gutenberg sends a delta-only REST PATCH, meaning it only includes fields that changed. If you only changed the title, content isn’t in the request. But Pattern Manager stores pattern content in flat PHP files — not the database — so after each save it clears post_content in the DB. By the time the save hook runs, $post->post_content is an empty string, and that’s what gets written to disk.
The fix is a single check:
$content = $request->has_param('content')
? $post->post_content
: $existing_content;
If content wasn’t sent in the request, use what’s already in the file. That’s it.
Saving categories was taking forever
Changing a pattern’s categories should be fast. It wasn’t.
Every category save was triggering the full update_pattern() path, which calls move_block_images_to_theme(), which makes a synchronous wp_remote_get() call for every image in the pattern. A pattern with a few photos means several HTTP round trips before you get a response back.
Categories live in the pattern file’s PHP header as a single comment line:
* Categories: featured, hero
So we replaced the whole update_pattern() call with a targeted regex swap on that line and a cache invalidation. Instant.
Query Monitor was leaking into the pattern preview iframe
If you run Query Monitor in local development (and you probably do), its HTML output was being injected into the pattern preview iframe. One line to fix it, scoped to the preview renderer only:
add_filter('qm/dispatch/html', '__return_false');
The Post Types selector was surfacing system internals
The Post Types field in the sidebar lets you restrict which post types a pattern is available on. It was also showing wp_template, wp_font_face, wp_global_styles, wp_navigation, and whatever else WordPress has quietly registered internally. Not useful.
The original code used a static blocklist, which kept breaking as WordPress added new internal types. I replaced it with a filter based on WordPress’s own visibility flags:
const v = (postType as any).visibility;
return v?.show_ui && v?.show_in_nav_menus && postType.slug !== 'pm_pattern';
Post types meant for real content have both show_ui and show_in_nav_menus set to true. System types don’t. No blocklist to maintain going forward.
WordPress admin styles were bleeding into react-select
Every react-select input in the sidebar had a thin blue border that didn’t belong there. WordPress admin ships global input[type="text"] CSS that doesn’t know (or care) about the hidden input inside react-select‘s internals.
The fix: add classNamePrefix="pm-select" to each select component (there were four), then target the scoped class in the stylesheet:
.pm-select__input-container input {
border: 0 !important;
box-shadow: none !important;
min-height: unset !important;
outline: none !important;
padding: 0 !important;
}
Category editing in the admin grid
Even with the sidebar working, changing a pattern’s categories meant opening it in the full block editor. That’s a lot of overhead for a metadata change. I added a fourth action button to each pattern card in the admin grid that opens a modal with a react-select dropdown, pulling from the same registered categories the sidebar uses. The save hits a dedicated REST endpoint that does the same targeted file-header replacement as the category fix above — no image sync, just writes the line and busts the cache.
The action bar was a mess at three columns
The admin grid shows pattern cards with a hover action bar containing four buttons: Edit, Duplicate, Delete, Categories. In the three-column layout, those four buttons were crammed into a single row and overflowing the card.
CSS container queries made this clean. Mark the card as a container, then swap the layout when it gets narrow:
.grid-item { container-type: inline-size; }
.item-actions-inside {
grid-template-columns: repeat(4, 1fr);
@container (max-width: 420px) {
grid-template-columns: repeat(2, 1fr);
}
}
Two rows of two at narrow widths. Looks right.
The pattern editor canvas was too narrow
Patterns were being edited at the theme’s contentSize, which for a lot of themes sits somewhere around 640px. Fine for a paragraph block, but it makes wide patterns, hero sections, and anything with a multi-column layout nearly impossible to work on accurately — you’re basically editing in thumbnail mode.
WordPress’s block_editor_settings_all filter lets you modify editor settings per context. For the pattern post type, I promote contentSize to match the theme’s wideSize:
function set_pattern_editor_canvas_width( array $settings, $context ): array {
if ( get_pattern_post_type() !== $context->post->post_type ?? '' ) {
return $settings;
}
$wide_size = $settings['__experimentalFeatures']['layout']['wideSize'] ?? '1200px';
$settings['__experimentalFeatures']['layout']['contentSize'] = $wide_size;
return $settings;
}
add_filter( 'block_editor_settings_all', __NAMESPACE__ . '\set_pattern_editor_canvas_width', 10, 2 );
Narrow patterns still render at their natural width. Wide patterns get room to breathe. No effect on the front end.
All of this landed in my fork of the StudioPress Pattern Manager repo. Not bad for a plugin I picked up to solve a completely different problem.

Leave a Reply