CMS Framework - v2.2.2
Site Editor - Patterns
The patterns module surfaces both theme-shipped patterns (PHP files) and user-authored patterns (DB rows) through a single read API. Unlike templates, patterns do not merge by slug — theme patterns and user patterns occupy disjoint keyspaces.
Added in 2.0.0 (H2).
Storage
Patterns occupy two disjoint sources rather than a file/DB merge:
- Theme patterns — PHP files at
themes/{active}/patterns/{slug}.phpwith a leading WP-style header doc-comment (Title:,Slug:,Categories:,Description:,Block Types:). Read-only at runtime; the body after the doc-comment becomes the pattern content. - User patterns — DB rows in
block_patternswith columnsid,slug,theme(nullable),title,description,source,synced(bool),categories(JSON),block_types(JSON),block_content(JSON viaHasBlockContent),author_id, timestamps.
User-pattern slugs carry a user/ prefix at storage time — this guarantees a theme pattern named hero and a user pattern named hero do not collide in the merged inserter map. The REST surface presents the unprefixed user-facing slug; the storage form is exposed as name on the unsynced response (mirroring WP's namespaced pattern names).
Pattern file format
Theme patterns are parsed by PatternFileParser from PHP files of the form:
<?php
/**
* Title: Call to Action
* Slug: cta
* Categories: featured, cta
* Description: A simple call-to-action block.
* Block Types: core/group
*/
?>
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:heading -->
<h2>Get started today</h2>
<!-- /wp:heading -->
</div>
<!-- /wp:group -->
The parser regexes are anchored — the doc-comment must lead the file. Everything after the closing ?> (or after the closing */ when there is no PHP tag) becomes the pattern content. Recognized header keys: Title:, Slug:, Categories: (comma-separated), Description:, Block Types: (comma-separated).
Sync semantics
synced distinguishes the two pattern shapes Gutenberg recognizes:
synced = true— surfaced under/blocks(WPwp_blockshape). Editing the pattern updates every occurrence in the editor.synced = false— surfaced under/block-patterns/patterns(WPwp_block_patternshape). Each insertion is a snapshot; later edits to the pattern do not propagate.
Theme patterns are always synced = false. Cross-state conversion happens through dedicated POST flows on each endpoint, not via PUT (a PUT to one endpoint never flips a pattern's synced bit on the other endpoint).
REST endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /blocks |
List user-source synced patterns (wp_block shape). |
| GET | /blocks/{slug} |
Show single synced pattern. |
| POST | /blocks |
Create a synced user pattern. |
| PUT | /blocks/{slug} |
Upsert a synced user pattern. |
| DELETE | /blocks/{slug} |
Delete a synced user pattern. |
| GET | /block-patterns/patterns |
List theme + user-source unsynced patterns merged. |
| GET | /block-patterns/patterns/{slug} |
Show single unsynced pattern (theme or user). |
| POST | /block-patterns/patterns |
Create an unsynced user pattern. |
| PUT | /block-patterns/patterns/{slug} |
Upsert an unsynced user pattern. 403 when targeting a theme slug. |
| DELETE | /block-patterns/patterns/{slug} |
Delete a user pattern. 403 when targeting a theme slug. |
A theme pattern can be cloned to an editable user pattern via PatternResolver::cloneToUser($slug) — used by the admin "edit" affordance on theme patterns. The clone copies title, description, categories, and block types from the theme file into a new user-source DB row.
Resolver
PatternResolver returns ResolvedPattern value objects. Unlike EntityResolver (templates / parts), it surfaces a cloneToUser() affordance and toFilterMap() for filter consumers, but does not implement revert() — patterns have no override-then-revert workflow.
class PatternResolver
{
public function resolve(string $slug): ?ResolvedPattern;
public function all(): array; // map<storage-slug, ResolvedPattern>
public function toFilterMap(): array; // map<storage-slug, array<string, mixed>>
public function cloneToUser(string $themeSlug): BlockPattern;
}
The merged map keys use the storage form: theme patterns under their natural slug (hero), user patterns under user/{slug}. visual-editor's ap.visual-editor.patterns consumer expects this shape.