A production-grade WordPress Gutenberg block plugin featuring a fully interactive portfolio block — built with the WordPress Interactivity API, custom post types, SlotFill meta panels, and REST API integration.
Simple Block started as a learning project and grew into a reference implementation for modern WordPress block development. It demonstrates real patterns used in production — not simplified examples.
The centerpiece is a Portfolio Block that showcases:
- Server-side rendering with PHP hydration
- Client-side filtering, search, and pagination via the WP Interactivity API
- A quick-view modal with image gallery
- Custom post type with taxonomy filtering
- Post meta management via SlotFill panels in the block editor
If you're learning Gutenberg block development or want a solid reference for iAPI patterns, this plugin is built for you.
A quick look at the editor experience, frontend UI, live searching, and portfolio modal interactions.
Modern responsive portfolio grid with category filtering, searching, and interactive pagination powered by the WordPress Interactivity API.
Instant client-side searching with taxonomy-aware filtering and dynamic result updates without page reloads.
Interactive portfolio modal featuring image galleries, project metadata, and smooth UX transitions.
Custom Gutenberg editor experience with SlotFill-powered meta panels for managing portfolio project data.
- Filterable portfolio grid — filter by category, search by keyword, paginate with Load More
- Quick-view modal — opens per card, shows gallery, excerpt, and meta — no page reload
- Single project page — standard WP single template for full project detail
- In-memory fetch cache — avoids duplicate REST API calls on repeated filters
- Debounced search — 300ms debounce prevents unnecessary requests on keystroke
- Server-side first — initial posts rendered by PHP, hydrated by JS — no layout shift
- Block editor controls — posts per page, default category, card colors via sidebar
- SlotFill meta panel — client name, completion date, project URL, gallery picker in Document tab
- REST API integration — custom
register_rest_fieldfor gallery image resolution - Unit tested utilities —
formatDate,mapPost,fetchPostscovered by Jest
A dynamic, interactive portfolio grid with filtering, search, modal preview, and load more pagination.
Block Controls (Sidebar)
| Control | Description |
|---|---|
| Posts Per Page | Number of posts to show initially (1–24) |
| Default Category | Pre-filter by a portfolio category |
| Card Background | Card background color |
| Heading Color | Card title color |
Post Meta Panel (Document Tab)
| Field | Description |
|---|---|
| Client Name | Client or company name |
| Completion Date | Project completion date |
| Project URL | Live project link |
| Project Gallery | Multi-image gallery picker |
- WordPress 6.4+
- PHP 8.0+
- Node.js 18+ (for development)
- npm 9+
cd wp-content/plugins
git clone https://github.com/dev-alamin/simple-block.git
cd simple-block
npm install
npm run buildActivate the plugin in WordPress Admin → Plugins.
npm run start # watch mode with hot reloadnpm run buildsimple-block/
├── simple-block.php # Plugin entry — registers CPT, taxonomy, meta
├── src/
│ └── portfolio/ # Portfolio block
│ ├── block.json # Block config, attributes, scripts
│ ├── edit.js # Block editor UI
│ ├── index.js # Block + SlotFill registration
│ ├── render.php # Server-side render (dynamic block)
│ ├── view.js # Frontend iAPI store
│ ├── style.scss # Frontend styles
│ ├── editor.scss # Editor-only styles
│ ├── utils.js # Pure utility functions (mapPost, fetchPosts, formatDate)
│ ├── utils.test.js # Unit tests
│ └── components/
│ ├── PortfolioGallery.js # Gallery media picker component
│ └── PortfolioMeta.js # Meta fields component
├── build/ # Compiled output — do not edit
├── package.json
└── README.md
npm run start # Development build with watch
npm run build # Production build
npm run test # Run unit tests
npm run test:watch # Run tests in watch mode
npm run lint:js # Lint JavaScript
npm run lint:css # Lint CSS/SCSSblock.json → defines attributes, scripts, render file
render.php → PHP query → wp_interactivity_state() → HTML with data-wp-* directives
view.js → iAPI store → hydrates HTML → reactive on user interaction
REST API → powers client-side filtering, search, pagination
1. PHP renders initial posts server-side (fast, SEO-friendly)
2. wp_interactivity_state() seeds global state with posts + config
3. iAPI hydrates the HTML — no re-render, just attaches reactivity
4. User clicks filter → JS fetch → state.posts updates → DOM updates
5. Load More → fetch next page → append to state.posts
6. Search → debounced fetch → replace state.posts
7. Quick view → openModal action → state.activePost set → modal appears
The plugin uses the WP Interactivity API's global state for all UI data:
// Seeded by PHP via wp_interactivity_state()
state.posts // current post list
state.query // { page, category, search }
state.isLoading // loading indicator
state.isLastPage // hides Load More when exhausted
state.activePost // currently open modal post
state.isModalOpen // modal visibility
state.baseUrl // REST API base URL
state.perPage // posts per pageA comprehensive reference document covering every concept used in this plugin — written tutorial-style with real examples, real bugs, and real fixes.
Topics covered:
block.jsondeep dive- Dynamic blocks and
render.phppatterns - Custom Post Type and Taxonomy registration
- Block editor with
InspectorControlsand attributes - SlotFill —
PluginDocumentSettingPanelfor post meta - WP Data layer —
useSelect,useEntityProp,useDispatch - REST API —
register_rest_field, pagination headers, query params - WordPress Interactivity API — state vs context, actions, callbacks, all directives
- Performance — in-memory caching, debounce, WP_Query optimization
- Unit testing with
@wordpress/scripts - 13 real bugs and their fixes
- Golden rules cheatsheet
Contributions, issues, and feature requests are welcome.
- Fork the repo
- Create a branch —
git checkout -b feature/your-feature - Commit your changes
- Push and open a Pull Request
Please run npm run test and npm run lint:js before submitting.
- URL sync — shareable filtered URLs with browser history support
- Infinite scroll —
IntersectionObserveralternative to Load More - Skeleton loading — placeholder cards during fetch
- Sort controls — newest, oldest, alphabetical
- Multiple block instances communicating via shared state
- PHP unit tests with PHPUnit
GPL-2.0 — see LICENSE for details.
Al Amin
WordPress block developer
github.com/dev-alamin
Built block by block — literally.



