WM Gutenberg Assessment
WordPress Gutenberg assessment plugin with filterable, paginated posts grid and REST-powered inter-block communication.
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/innaplx/wm-gutenberg-assessment/archive/refs/heads/main.zipWordPress plugin for a take-home Gutenberg assessment: a filterable, paginated posts grid with inter-block communication via URL query parameters and REST-powered updates without full page reloads.
Overview
The plugin registers three dynamic blocks:
- Posts Filter — category and tag chip controls
- Posts Grid — server-rendered post cards (featured image, title, excerpt) with configurable columns and posts per page
- Posts Pagination — nested inside Posts Grid as an InnerBlock
On activation, the plugin seeds demo categories, tags, posts, and a demo page that combines the filter and grid blocks.
Requirements
- WordPress with the block editor (Gutenberg)
- PHP compatible with your Local/site environment (WordPress 6.x recommended)
- Node.js and npm (for building block assets)
Installation
- Copy this folder into
wp-content/plugins/wm-gutenberg-assessment. - From the plugin directory, run
npm install. - Run
npm run buildto compile blocks intobuild/blocks/. - In Plugins, activate WM Gutenberg Assessment.
- Open the seeded demo page The Internet Survival Guide (created on first activation).
Development
npm run start— watch mode for block JavaScript/CSS during development.npm run build— production build; required before testing PHP-rendered pages that load scripts frombuild/blocks/.
Blocks
| Block name | Purpose |
|---|---|
wm/posts-filter |
Toggle categories/tags; Reset clears filters |
wm/posts-grid |
Displays filtered posts; provides postsPerPage to inner blocks |
wm/posts-pagination |
Previous / numbered pages / Next; InnerBlock of Posts Grid only |
Pagination is not a standalone sibling on the demo page—it lives inside the grid block in the editor and in saved markup.
Inter-block communication
Filter and Grid are independent sibling blocks on the page. They do not nest inside each other. Shared state is the URL query string:
| Parameter | Meaning |
|---|---|
wm_categories |
Comma-separated category term IDs |
wm_tags |
Comma-separated tag term IDs |
wm_page |
Current page (omitted when page 1) |
Filter semantics (implemented in WM_Query):
- OR among selected categories
- OR among selected tags
- AND between the category group and the tag group
The URL is the source of truth for filters and pagination. Server-side rendering on first load reads the same parameters from $_GET.
REST API
Endpoint: GET /wp-json/wm/v1/posts
Used by the filter block’s frontend script to refresh the grid and pagination after filter or pagination interactions without reloading the page.
| Parameter | Description |
|---|---|
categories |
Comma-separated category IDs (optional) |
tags |
Comma-separated tag IDs (optional) |
page |
Page number (default 1) |
per_page |
Posts per page (1–20, should match grid attribute) |
context_url |
Required. Current page URL without wm_* params; used to build correct pagination links |
Response (JSON):
grid_html— server-rendered post cardspagination_html— server-rendered pagination markup (empty when one page)current_page,total_pages
HTML fragments are returned so post cards and pagination reuse the same PHP rendering and escaping as the initial page (WM_Posts_Render), instead of duplicating WordPress template logic in JavaScript.
The endpoint is read-only, returns published posts only, and uses a public permission_callback appropriate for that scope.
History / AJAX behavior
- Filter changes and pagination clicks fetch the REST endpoint and swap grid/pagination HTML.
- Successful updates use
history.pushStateso the address bar matches the new filters/page. - Browser Back and Forward use
popstateto fetch the state implied by the URL. - The first paint is fully server-rendered; JavaScript enhances behavior after load.
If a REST request fails, the visible grid and URL stay aligned with the last successful state (see limitations in tradeoffs).
Seeder
On plugin activation (WM_Seeder):
- Creates demo categories and tags (idempotent by slug)
- Creates 12 demo posts with excerpts, categories/tags, and featured images (idempotent by
wm-demo-*slugs) - Creates demo page The Internet Survival Guide with filter + grid (including pagination InnerBlock)
- Stores
wm_demo_page_idandwm_seeder_version
Re-activation does not duplicate posts or terms. Existing demo page content is not rewritten if the page already exists.
Architecture
WM_Query → filter state, tax_query, WP_Query args (SSR + REST)
WM_Posts_Render → grid cards + pagination HTML
Block render.php → Gutenberg composition and attributes
WM_REST_API → AJAX transport; returns HTML fragments
posts-filter/view.js → REST fetch, History API, DOM updates
Frontend interaction logic is enqueued with the Posts Filter block (viewScript); it also drives grid and pagination on pages that include both filter and grid (as on the demo page).
Tradeoffs / limitations
- Filter script dependency — AJAX filtering/pagination requires the filter block (and its script) on the same page as the grid.
- REST returns HTML fragments — avoids duplicating rendering in JS; tradeoff is replacing
innerHTMLrather than fine-grained DOM diffing. - Server-rendered first load — good for progressive enhancement; crawlers that do not run JS see the initial filter/page state only.
- Filter UI requires JavaScript — no non-JS fallback for chip toggles.
- SSR pagination count — grid and pagination inner block each run a query on full page load (pagination runs before the grid render callback in the block tree); REST reuses
max_num_pagesfrom the main query to avoid a duplicate count query.