WP Plugin Development
When to use
Use this skill for plugin work such as:
-
creating or refactoring plugin structure (bootstrap, includes, namespaces/classes)
-
adding hooks/actions/filters
-
activation/deactivation/uninstall behavior and migrations
-
adding settings pages / options / admin UI (Settings API)
-
security fixes (nonces, capabilities, sanitization/escaping, SQL safety)
-
packaging a release (build artifacts, readme, assets)
Inputs required
-
Repo root + target plugin(s) (path to plugin main file if known).
-
Where this plugin runs: single site vs multisite; WP.com conventions if applicable.
-
Target WordPress + PHP versions (affects available APIs).
Procedure
- Triage and locate plugin entrypoints
-
Identify the main plugin file (contains Plugin Name: header)
-
Check for existing structure (includes/, admin/, public/ directories)
-
Note any existing hooks or class patterns
- Follow a predictable architecture
Guidelines:
-
Keep a single bootstrap (main plugin file with header).
-
Avoid heavy side effects at file load time; load on hooks.
-
Prefer a dedicated loader/class to register hooks.
-
Keep admin-only code behind is_admin() (or admin hooks) to reduce frontend overhead.
Read:
- references/structure.md
- Hooks and lifecycle (activation/deactivation/uninstall)
Activation hooks are fragile; follow guardrails:
-
Register activation/deactivation hooks at top-level, not inside other hooks
-
Flush rewrite rules only when needed and only after registering CPTs/rules
-
Uninstall should be explicit and safe (uninstall.php or register_uninstall_hook )
Read:
- references/lifecycle.md
- Settings and admin UI (Settings API)
Prefer Settings API for options:
-
register_setting() , add_settings_section() , add_settings_field()
-
Sanitize via sanitize_callback
Read:
- references/settings-api.md
- Security baseline (always)
Before shipping:
-
Validate/sanitize input early; escape output late.
-
Use nonces to prevent CSRF and capability checks for authorization.
-
Avoid directly trusting $_POST / $_GET ; use wp_unslash() and specific keys.
-
Use $wpdb->prepare() for SQL; avoid building SQL with string concatenation.
Read:
- references/security.md
- Custom Post Types and REST API (if needed)
-
Register CPTs/taxonomies on init with show_in_rest for Gutenberg support.
-
Follow REST API conventions: proper permission callbacks, schema, prepared statements.
Read:
- references/rest-api.md
- Hooks and extensibility
-
Add action hooks at key lifecycle points for extensibility.
-
Use filters for modifiable output.
-
Prefix all hook names with plugin slug.
Read:
- references/hooks.md
- Cron and scheduled tasks (if needed)
-
Schedule on activation, clear on deactivation.
-
Critical: Never use same name for cron hook and internal do_action() .
-
Process large datasets in batches.
Read:
- references/cron.md
- Internationalization
-
Use proper text domain matching plugin slug.
-
Load textdomain on plugins_loaded .
-
Use translation functions: __() , _e() , _x() , _n() .
Verification
-
Plugin activates with no fatals/notices.
-
Settings save and read correctly (capability + nonce enforced).
-
Uninstall removes intended data (and nothing else).
-
Run repo lint/tests (PHPUnit/PHPCS if present).
-
Passes Plugin Check plugin (no errors).
Failure modes / debugging
-
Activation hook not firing:
-
Hook registered incorrectly (not in main file scope), wrong main file path, or plugin is network-activated
-
Settings not saving:
-
Settings not registered, wrong option group, missing capability, nonce failure
-
Security regressions:
-
Nonce present but missing capability checks; or sanitized input not escaped on output
-
Cron infinite recursion:
-
Same name used for cron hook and internal do_action() call
Read:
- references/debugging.md
Escalation
For canonical detail, consult the Plugin Handbook and security guidelines before inventing patterns.
-
Plugin Developer Handbook
-
Security Best Practices
-
Settings API