Install
$ agentstack add skill-hnikoloski-imlazy-imlazy-wordpress Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Dangerous shell/eval execution.
What it can access
- ● Network access Used
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ● Dynamic code execution Used
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming — see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps — measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
WordPress + ACF Development Skill
Comprehensive guidance for WordPress theme/plugin development and ACF field management.
> This skill incorporates patterns from the official WordPress Agent Skills — see their skills/ directory for deeper dives on specific topics.
Cross-reference: use Skill(imlazy-php-review) for PHP-level code review of any WordPress code encountered.
When to Activate
- Building or modifying WordPress themes (classic or block)
- Creating or reviewing WordPress plugins
- Working with ACF field groups, blocks, or options pages
- Implementing custom REST API endpoints
- Enqueuing scripts/styles or registering blocks
- Performing WordPress security review
- Writing WP_Query loops or custom database queries
WordPress Core
1. Theme Development
Template Hierarchy
WordPress resolves page templates in a strict order. Understand the file that will load:
single-{post-type}-{slug}.php → single-{post-type}.php → single.php → singular.php → index.php
page-{slug}.php → page-{id}.php → page.php → singular.php → index.php
category-{slug}.php → category-{id}.php → category.php → archive.php → index.php
tag-{slug}.php → tag-{id}.php → tag.php → archive.php → index.php
taxonomy-{tax}-{term}.php → taxonomy-{tax}.php → taxonomy.php → archive.php → index.php
archive-{post-type}.php → archive.php → index.php
Use is_*() conditionals in index.php as a fallback, not as a replacement for the hierarchy.
Block Themes (FSE)
// theme.json — the heart of a block theme
{
"version": 2,
"settings": {
"color": {
"palette": [
{ "slug": "primary", "color": "#1d2327", "name": "Primary" },
{ "slug": "accent", "color": "#007cba", "name": "Accent" }
]
},
"typography": {
"fontSizes": [
{ "slug": "small", "size": "13px", "name": "Small" },
{ "slug": "medium", "size": "20px", "name": "Medium" }
]
}
},
"templates": {
"index": { "title": "Index", "postTypes": [ "page", "post" ] }
}
}
Classic Theme Structure
themes/your-theme/
├── style.css # Required: Theme header comment block
├── index.php # Required: Main fallback template
├── functions.php # Theme setup, enqueues, helpers
├── template-parts/ # Reusable partials (header, footer, loops)
├── page-templates/ # Custom page templates (full-width, landing, etc.)
└── assets/ # CSS, JS, images
Style.css header:
/*
Theme Name: Your Theme
Theme URI: https://example.com
Author: You
Description: Description of your theme.
Version: 1.0.0
License: GPL-2.0-or-later
Text Domain: your-theme
*/
2. Plugin Architecture
Main Plugin File
'book',
'posts_per_page' => 10,
'meta_key' => 'rating',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'paged' => get_query_var('paged', 1),
'tax_query' => [
[
'taxonomy' => 'genre',
'field' => 'slug',
'terms' => 'fiction',
],
],
]);
if ($query->have_posts()) :
while ($query->have_posts()) : $query->the_post();
the_title();
endwhile;
wp_reset_postdata();
endif;
WPUserQuery
$user_query = new WP_User_Query([
'role' => 'subscriber',
'number' => 50,
'orderby' => 'registered',
'fields' => ['ID', 'display_name', 'user_email'],
]);
$users = $user_query->get_results();
getposts vs queryposts
// PASS: get_posts() — returns array, no global state pollution
$posts = get_posts([
'post_type' => 'post',
'posts_per_page' => 5,
'meta_key' => 'featured',
'meta_value' => '1',
]);
// FAIL: query_posts() — modifies main query, breaks pagination, pollutes global state
query_posts(['posts_per_page' => 1]);
// NEVER use query_posts(). Use pre_get_posts to alter the main query.
Pre-get_posts to Modify Main Queries
// PASS: Modify main query via pre_get_posts
add_action('pre_get_posts', function (WP_Query $query) {
if (!is_admin() && $query->is_main_query() && is_home()) {
$query->set('posts_per_page', 20);
$query->set('meta_key', 'featured');
$query->set('meta_value', '1');
}
});
5. REST API
Custom Endpoints
// PASS: Register custom REST route
add_action('rest_api_init', function () {
register_rest_route('my-plugin/v1', '/books/(?P\d+)', [
'methods' => 'GET',
'callback' => 'my_plugin_get_book',
'permission_callback' => function (WP_REST_Request $request) {
return current_user_can('edit_posts');
},
'args' => [
'id' => [
'required' => true,
'validate_callback' => function ($param) {
return is_numeric($param);
},
'sanitize_callback' => 'absint',
],
],
]);
});
function my_plugin_get_book(WP_REST_Request $request) {
$post = get_post($request['id']);
if (!$post || $post->post_type !== 'book') {
return new WP_Error('not_found', 'Book not found', ['status' => 404]);
}
return new WP_REST_Response([
'id' => $post->ID,
'title' => $post->post_title,
'meta' => get_post_meta($post->ID),
], 200);
}
Authentication
// Cookie authentication (logged-in users) — automatic for same-origin
// Application password authentication — use for external clients
add_filter('rest_authentication_errors', function ($errors) {
if (!is_user_logged_in()) {
return new WP_Error('rest_not_logged_in', 'Not authenticated', ['status' => 401]);
}
return $errors;
});
// OAuth / JWT — use a dedicated plugin (e.g., WP OAuth Server, JWT Auth)
Permissions Best Practices
// Minimum capability mapping:
// GET items → read / publish_posts
// POST items → edit_posts
// PUT items → edit_others_posts
// DELETE items → delete_posts
register_rest_route('my-plugin/v1', '/settings', [
'methods' => 'GET',
'callback' => 'my_plugin_get_settings',
'permission_callback' => function () {
return current_user_can('manage_options'); // Admin only
},
]);
Controller Classes (WPRESTController)
For non-trivial endpoints, subclass WP_REST_Controller for CRUD consistency:
class My_Plugin_Books_Controller extends WP_REST_Controller {
protected $namespace = 'my-plugin/v1';
protected $rest_base = 'books';
public function register_routes() {
register_rest_route($this->namespace, "/{$this->rest_base}", [
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_items'],
'permission_callback' => [$this, 'get_items_permissions_check'],
],
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'create_item'],
'permission_callback' => [$this, 'create_item_permissions_check'],
],
]);
register_rest_route($this->namespace, "/{$this->rest_base}/(?P\d+)", [
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_item'],
'permission_callback' => [$this, 'get_item_permissions_check'],
'args' => $this->get_endpoint_args_for_item_schema(false),
],
]);
}
public function get_items_permissions_check($request) {
return current_user_can('edit_posts');
}
public function get_items($request) {
$posts = get_posts([
'post_type' => 'book',
'posts_per_page' => $request['per_page'] ?? 10,
'paged' => $request['page'] ?? 1,
]);
$data = [];
foreach ($posts as $post) {
$data[] = $this->prepare_response_for_collection(
$this->prepare_item_for_response($post, $request)
);
}
return rest_ensure_response($data);
}
public function prepare_item_for_response($item, $request) {
$post = $item instanceof WP_Post ? $item : get_post($item);
$data = [
'id' => $post->ID,
'title' => $post->post_title,
'content' => $post->post_content,
'meta' => get_post_meta($post->ID),
];
return rest_ensure_response($data);
}
public function get_item_schema() {
return [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'book',
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'title' => ['type' => 'string'],
'content' => ['type' => 'string'],
],
];
}
}
add_action('rest_api_init', function () {
$controller = new My_Plugin_Books_Controller();
$controller->register_routes();
});
Schema Validation & Discovery
// Validate args against schema automatically
register_rest_route('my-plugin/v1', '/books', [
'methods' => 'GET',
'callback' => 'my_plugin_get_books',
'permission_callback' => '__return_true',
'args' => [
'category' => [
'type' => 'string',
'validate_callback' => function ($value) {
return in_array($value, ['fiction', 'non-fiction', 'sci-fi']);
},
],
'per_page' => [
'type' => 'integer',
'default' => 10,
'minimum' => 1,
'maximum' => 100,
],
'page' => [
'type' => 'integer',
'default' => 1,
'minimum' => 1,
],
],
]);
// Pagination headers (set automatically when using WP_REST_Controller)
// Response headers: X-WP-Total, X-WP-TotalPages
// Discovery: ensure your namespace appears in the /wp-json/ index
// Use rest_get_route_for_post_type_items() for core-compatible route URLs
$route = rest_get_route_for_post_type_items('book');
// Returns: /wp/v2/books (when rest_base is set properly)
// Expose meta in REST responses
register_post_meta('book', 'isbn', [
'show_in_rest' => true,
'type' => 'string',
'single' => true,
'auth_callback' => function () {
return current_user_can('edit_posts');
},
]);
// Support _fields for sparse responses
// GET /wp-json/my-plugin/v1/books?_fields=id,title
6. Block Editor / Gutenberg
block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my-plugin/book-card",
"title": "Book Card",
"category": "widgets",
"icon": "book",
"description": "Display a book with cover, title, and author.",
"keywords": ["book", "card", "library"],
"version": "1.0.0",
"textdomain": "my-plugin",
"editorScript": "file:./index.js",
"style": "file:./style-index.css",
"render": "file:./render.php",
"attributes": {
"bookId": {
"type": "number",
"default": 0
},
"showRating": {
"type": "boolean",
"default": false
}
},
"supports": {
"align": ["wide", "full"],
"color": {
"background": true,
"text": true
}
}
}
registerblocktype
// PASS: Register block from block.json metadata
add_action('init', function () {
register_block_type(__DIR__ . '/build/book-card');
});
// Render callback in render.php (automatically loaded from block.json)
// >
//
// post_title); ?>
// ID, 'author', true)); ?>
//
//
// Dynamic block with PHP render callback (alternative)
register_block_type('my-plugin/book-card', [
'render_callback' => function ($attributes, $content, $block) {
$post = get_post($attributes['bookId']);
if (!$post) return '';
ob_start();
?>
>
post_title); ?>
{props.attributes.title}
);
},
migrate(attributes) {
// Transform old attributes to new format
return {
...attributes,
layout: attributes.layout || 'card',
version: 2,
};
},
},
{
// Even older version
attributes: { bookId: { type: 'number' } },
save: function () {
return ...;
},
},
],
});
PHP-Side Deprecations (Dynamic Blocks)
Dynamic blocks (render.php) don't save markup to post content, so they rarely need deprecated. However, if you change attributes, still add a deprecated entry with save: () => null and a migrate callback:
deprecated: [
{
attributes: { oldAttr: { type: 'string' } },
save: () => null,
migrate(attributes) {
return { newAttr: attributes.oldAttr };
},
},
],
Rules of Deprecation
- Never remove a deprecated entry that still exists in stored post content
- Each deprecation must include the exact old
save()output and attribute shape - Use
migrate()to normalize old attribute values to new schema - Test by creating a post with the old block, switching to the new code, and reloading
- List entries newest-first (WordPress tries them in order)
8. Interactivity API (data-wp-*)
For interactive frontend behavior without full React, use viewScriptModule with the @wordpress/interactivity API.
block.json Setup
{
"apiVersion": 3,
"name": "my-plugin/like-button",
"title": "Like Button",
"viewScriptModule": "file:./view.js",
"render": "file:./render.php",
"attributes": {
"postId": { "type": "number" },
"likes": { "type": "number", "default": 0 }
}
}
view.js (Module Script)
import { store, getContext, getElement } from '@wordpress/interactivity';
const { state } = store('my-plugin/like-button', {
state: {
get likes() {
const context = getContext();
return context.likes;
},
get isLiked() {
const context = getContext();
return context.isLiked;
},
},
actions: {
toggleLike() {
const context = getContext();
context.isLiked = !context.isLiked;
context.likes += context.isLiked ? 1 : -1;
},
},
callbacks: {
logView() {
console.log('Block viewed:', getContext().postId);
},
},
});
render.php
data-wp-interactive="my-plugin/like-button"
$attributes['postId'],
'likes' => $attributes['likes'],
'isLiked' => false,
]); ?>
>
Likes
❤️ Liked!
viewScript vs viewScriptModule
| Feature | viewScript | viewScriptModule | |------------------|----------------------------------|----------------------------------------| | Module type | Regular ES5 JS (IIFE) | ES module (type="module") | | Interactivity API| Manual store setup | Built-in @wordpress/interactivity | | Dependencies | wp-interactivity as dependency | No dependency array needed | | Browser support | All browsers | Modern browsers (ES modules) | | Prefer when | Simple scripts, legacy compat | New development, Interactivity API |
Use viewScriptModule for new development targeting WordPress 6.5+.
9. WP-CLI & Operations
WP-CLI is the command-line tool for WordPress management. Always confirm the environment (dev/staging/prod) before running write commands.
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: hnikoloski
- Source: hnikoloski/imlazy
- License: MIT
- Homepage: https://hnikoloski.github.io/imlazy/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.