wordpress
How to Build a Custom WordPress Plugin: Structure, Hooks, Security and Testing
A practical walkthrough of custom WordPress plugin development: structure, hooks, security rules, REST endpoints and testing, based on what we ship in production.
By AYM FlowPublished: Updated: 6 min read
A good custom WordPress plugin is small, predictable and safe. A bad one is a pile of functions that breaks on the next update and opens a security hole. This guide covers the decisions that separate the two: when a plugin is the right tool, how to structure it, how to use hooks, how to secure input and output, how to expose a REST API, and how to test before release.
Decide whether you need a plugin
Put functionality in a plugin when it should survive a theme change: custom post types, integrations, business logic, shortcodes, blocks and anything tied to your data rather than your design. Presentation belongs in the theme. A must-use plugin suits site-wide rules that nobody should be able to deactivate. Before building, check whether a lean, well-maintained plugin already does the job, because every dependency has a maintenance cost either way.
Start with a clean plugin structure
WordPress only needs one PHP file with a header comment, but a maintainable plugin is organised from day one. A layout we use as a baseline:
- plugin-slug.php: the main file with the header, constants and a bootstrap call. Keep it thin.
- src/ or includes/: namespaced classes, one responsibility each, loaded through a Composer or PSR-4 style autoloader.
- assets/: scripts and styles, enqueued only on the screens that need them.
- languages/: translation files, with a consistent text domain.
- uninstall.php: removes options, tables and scheduled events when the plugin is deleted.
Add a guard at the top of every PHP file so it cannot be run directly, prefix or namespace everything to avoid collisions, and follow the WordPress PHP coding standards so any WordPress developer can read your code.
Use hooks, not hacks
Everything in WordPress extensibility runs on hooks. Actions let you run code at a point in the request, and filters let you modify a value before it is used. The rules that matter in practice:
- Attach code to the earliest sensible hook, and no earlier. Admin code should not load on the front end.
- Never edit core, theme or other plugin files. If a hook does not exist, wrap the behaviour or ask the author for one.
- Use activation hooks for one-time setup such as creating tables, but do not rely on them for upgrades. Store a version number and run migrations on load.
- Expose your own hooks so other developers can extend the plugin without forking it.
- Keep callbacks light. Defer heavy work to scheduled events or background processing.
Security: sanitise, validate, escape, authorise
Most plugin vulnerabilities are the same four mistakes. The plugin security handbook covers each, and the habits below prevent the bulk of them.
- Validate and sanitise input as early as possible, using the function that matches the data type.
- Escape output as late as possible, with the escaping function for the context: HTML, attribute, URL or JavaScript.
- Check capabilities with current_user_can before any action that changes data. Being logged in is not authorisation.
- Verify nonces on every form and AJAX action to prevent cross-site request forgery.
- Prepare database queries with the wpdb prepare method, never by concatenating user input into SQL.
Also avoid storing secrets in plain text options when you can, limit file uploads to what you need, and never trust data just because it came from your own database.
Expose data with the REST API
If your plugin feeds a block, a dashboard or an external app, register custom routes with the REST API rather than writing ad hoc AJAX handlers. Use your own namespace with a version, such as vendor/v1, declare the accepted arguments with types and sanitise callbacks, and always provide a permission callback. A route with no permission check is public by design, so make that a deliberate choice. Return proper HTTP status codes and error objects so clients can react predictably.
Test, optimise and release
Automated checks catch what code review misses. Run PHP_CodeSniffer with the WordPress ruleset, write PHPUnit tests for business logic using the WordPress plugin unit test setup, and run the Plugin Check tool before distribution. Then look at performance: avoid autoloading large options, cache expensive queries with transients or an object cache, and load assets conditionally.
Finally, test on the oldest PHP and WordPress versions you claim to support, with the page builders your customers use, and with your uninstall routine. The official plugin developer handbook is the reference for submission rules if you plan to publish on WordPress.org.
A few housekeeping habits separate professional plugins from hobby code, and they cost very little when done from the start:
- Version and migrate. Store a plugin version in the database and run idempotent upgrade routines when it changes, so customers can skip releases safely.
- Make it translatable. Wrap user-facing strings in translation functions with one text domain, even if you launch in a single language.
- Fail quietly and log usefully. Catch errors around remote requests, show admin notices for actionable problems, and never expose stack traces to visitors.
- Clean up after yourself. Remove options, tables and scheduled events on uninstall, and do not leave orphaned data on customer sites.
- Document the extension points. A short readme listing hooks, filters and REST routes saves hours for the next developer, which is often you in a year.
When to bring in a developer
A simple shortcode or custom post type is a fine learning project. Anything touching payments, user data, licensing or integrations deserves an experienced hand, because mistakes are expensive. Our WordPress plugin development service covers architecture, security review and maintenance, and AYM Multilingual is a production plugin we build and support ourselves. If your plugin will be sold, read how a licensing system fits in before you design the architecture.
Frequently asked questions
What do I need to build a WordPress plugin?
A local WordPress install, PHP knowledge, and one PHP file with a plugin header. For anything beyond a prototype, add version control, a coding standards checker and automated tests.
Is a custom plugin better than installing an existing one?
When existing plugins do not fit your workflow, or you would need several to approximate it, a purpose-built plugin is usually faster and easier to secure. If one maintained plugin does the job, use it.
How do I keep a plugin secure?
Validate and sanitise input, escape output, check user capabilities, verify nonces and prepare every database query. Run static analysis and review before each release.
Can a custom plugin break during WordPress updates?
Plugins that use public hooks and APIs rarely break. Plugins that edit core files or rely on internal functions do. Testing against upcoming WordPress and PHP versions catches issues early.
Related articles
wordpress
WooCommerce Speed and Conversion Optimization: A Practical Guide
A faster WooCommerce store converts better, but only if checkout is also easy. Cover caching, product pages, scripts, database health and checkout friction in the right order.
wordpress
WordPress Plugin Licensing System: How to Design a Licence Server
Selling a WordPress plugin means deciding how keys are activated, limited, revoked and renewed without punishing paying customers. Lessons from running our own licence server.
wordpress
How to Improve Core Web Vitals on WordPress: LCP, INP and CLS Fixes That Work
Passing Core Web Vitals on WordPress is mostly about images, JavaScript and layout discipline. Here is how to measure, prioritise and fix LCP, INP and CLS.
