WordPress Plugin Development for Beginners

Introduction to WordPress plugin development
WordPress extensible plugins are modular add-ons extending WordPress functionality without modifying core code. With over 60,000 plugins in the official WordPress repository, the plugin ecosystem is one of the biggest reasons WordPress is the most-used CMS in the world. Developing your own plugins gives you complete control over site functionality and enables you to create solutions exactly tailored to your clients' needs or your own business.
To start developing a WordPress plugin you need a local development environment or hosting with WordPress installation, knowledge of PHP at basic to intermediate level, and understanding of WordPress architecture especially the hooks system. Tools like Local by Flywheel, XAMPP, or Docker facilitate setting up a local environment for development and testing plugins before publishing to production.
WordPress plugin structure
Every WordPress plugin starts from a single PHP file with a special comment in the header telling WordPress it's a plugin. This comment contains the plugin name, description, version, author, and other metadata. WordPress scans the wp-content/plugins directory and reads these headers to display the list of available plugins in the admin panel.
Minimal plugin structure
The simplest plugin is one PHP file in the wp-content/plugins directory with a proper header. However, for a more serious plugin, organization in a dedicated directory with main file, includes directory for helper classes, admin directory for admin pages, public directory for frontend code, and assets directory for CSS and JavaScript files is recommended.
- plugin-name/plugin-name.php - main plugin file with header and initialization
- plugin-name/includes/ - PHP classes and helper functions
- plugin-name/admin/ - admin pages, settings, metaboxes
- plugin-name/public/ - frontend functionality and templates
- plugin-name/assets/css/ - CSS styles for admin and frontend
- plugin-name/assets/js/ - JavaScript files
- plugin-name/languages/ - localization and translation files
- plugin-name/templates/ - HTML templates that the theme can override
Use namespaces or prefixes for all functions. See the site security guide for additional tips and classes to avoid conflicts with other plugins. For example, instead of generic get_settings() use beo_plugin_get_settings() or better PHP namespace BeoPlugin and classes. This is critical because WordPress loads all active plugins into the same PHP scope and naming conflicts lead to fatal errors.
WordPress hooks system
The hooks system is the heart of WordPress architecture and understanding hooks is absolutely essential for plugin development. WordPress defines hundreds of hooks at key points in the code execution process, and plugins hook onto these to execute their own code at the right moment. There are two types of hooks: actions and filters.
Actions
Actions are hooks that allow you to execute code at a specific point in the WordPress process. For example, the init action fires after WordPress finishes loading but before sending any output, wp_enqueue_scripts fires when CSS and JS files need to be added, and save_post fires when a post is saved. Use the add_action() function to register your callback function on a specific action hook.
Filters
Filters are hooks that allow you to modify data before WordPress uses or displays it. A filter callback function receives a value, modifies it, and returns the modified version. For example, the_content filter allows you to change post content before display, the_title changes the title, and wp_mail filter enables modifying email messages before sending. Use add_filter() to register filter callbacks.
- add_action('hook_name', 'callback', priority, args) - registers a function on an action hook
- add_filter('hook_name', 'callback', priority, args) - registers a function on a filter hook
- do_action('custom_hook') - creates your own action hook other plugins can use
- apply_filters('custom_filter', $value) - creates your own filter hook
- remove_action() / remove_filter() - removes a previously registered function
The priority parameter (default 10) determines execution order when multiple functions are registered on the same hook. Lower number means earlier execution. This is useful when you want your code to execute before or after another plugin's code on the same hook. Always explicitly define priority if order of execution matters because default value can lead to unpredictable results.
Creating admin pages
Most plugins require admin pages for configuring settings. WordPress provides Settings API for creating settings pages that integrate with the WordPress admin panel. Use add_menu_page() to add a main menu item or add_submenu_page() to add a subpage under an existing menu item.
Settings API
WordPress Settings API automates settings storage and validation using the WordPress options table. You register settings with register_setting(), define sections with add_settings_section(), and fields with add_settings_field(). WordPress then automatically generates the form, stores data, and applies sanitization. This is a safer approach than manual POST request handling because Settings API automatically adds nonce verification and user permission check.
For more complex admin interfaces, you can use custom admin pages with your own HTML and JavaScript instead of Settings API. In that case, use wp_ajax_ hooks for AJAX calls and be sure to add nonce verification with wp_nonce_field() and check_admin_referer() for CSRF attack protection. Also check user permissions with current_user_can() at the start of every admin page and AJAX handler.
Admin menu example
In practice, creating an admin page requires two steps. First, register the menu item on the admin_menu hook using add_menu_page with parameters for page title, menu title, required permission (usually manage_options for administrators), page slug, callback function that renders HTML, and optionally an icon. Then in the callback function, generate the HTML page with a form using settings_fields() and do_settings_sections() for automatic rendering of registered settings.
Creating shortcodes
Shortcodes allow users to embed your plugin's functionality directly into post and page content using short tags in square brackets. Register a shortcode with the add_shortcode() function which takes the shortcode name and callback function. The callback receives the attributes the user specified, content between opening and closing tags, and the shortcode name itself.
Implementation example
Imagine creating a plugin for displaying team members. The shortcode [team_member name="Marko" role="Developer" photo="url"] would display a stylized card with photo, name, and team member role. The callback function receives the $atts array, uses shortcode_atts() to define default values, and returns an HTML string with formatted display. Never use echo in shortcode callback but always return a string because echo disrupts content display order on the page.
- Self-closing shortcode - [button text="Click" url="/link"] - no content between
- Enclosing shortcode - [highlight]important text[/highlight] - has content between tags
- Nested shortcodes - do_shortcode($content) in callback for nesting support
Activation, deactivation, and uninstallation
WordPress provides three hooks for plugin lifecycle events. register_activation_hook() is called when the user activates the plugin and there you usually create database tables, set default settings, and register cron tasks. register_deactivation_hook() is called on deactivation and there you clean up timers and temporary data but leave data because the user might reactivate the plugin.
For permanent data deletion on uninstall, use register_uninstall_hook() or uninstall.php file in the plugin's root directory. Here you delete tables from the database, remove options from the wp_options table, and clean up all data the plugin created. Always implement these hooks because a plugin that leaves data in the database after uninstall is bad practice unnecessarily occupying resources and polluting the database.
Security and best practices
Security is critical in plugin development because a vulnerable plugin can compromise the entire WordPress installation and all sites on the same server. Always validate and sanitize all user input with sanitize_text_field(), absint(), esc_url(), and other WordPress sanitization functions. Use prepared statements with $wpdb->prepare() for all database queries to prevent SQL injection attacks.
- Nonce verification - wp_nonce_field() and wp_verify_nonce() for CSRF protection
- Capability checks - current_user_can() before every privileged action
- Output escaping - esc_html(), esc_attr(), esc_url() for all dynamic data in HTML
- Prepared statements - $wpdb->prepare() for all queries with user data
- File upload validation - check MIME type, file size, and extension
- Direct access protection - defined('ABSPATH') || exit; at the start of every PHP file
Follow WordPress Coding Standards for consistent and readable code. Use the WordPress internationalization system with __() and _e() functions for all strings the user sees so your plugin can be translated to other languages. Test the plugin with WP_DEBUG enabled to catch all errors and warnings before publishing. Consider writing PHPUnit tests for critical plugin logic because automated tests prevent regressions during updates.
BeoHosting Team
10+ years of experience — Web hosting and infrastructure specialists
- Web Hosting
- WordPress Hosting
- VPS
- Dedicated Serveri
- Domeni
- SSL
- cPanel
- LiteSpeed
- Linux administracija
- DNS
Last updated: