db48fc7919dc67c915fb8acbd3150b62 1280 80 HUxpwh
TechRadar News

Nvidia throws Windows 10 gamers a lifeline with driver support – but time’s up for the popular GTX 1060 GPU, as support runs out in October 2025 

   

GPU, Computing, Computing Components  Read More

Driver support for Pascal GPUs like the GTX 1060 ends in October 2025, and Windows 10 users have until October 2026. 

Latest from TechRadar in News

 

Nvidia throws Windows 10 gamers a lifeline with driver support – but time’s up for the popular GTX 1060 GPU, as support runs out in October 2025  Read Post »

3bB8mDannREDHGvWRvxaFA 1280 80 vsEW6b
TechRadar News

Apple TV+ Snoopy Presents: A Summer Musical does something I’ve never seen in a Peanuts animated special, and even the trailer gave me chills 

   

Apple TV +, Streaming  Read More

The Peanuts are returning to Apple TV+ in one of their first animated musicals in decades, and the trailer is already giving us all the feels. 

Latest from TechRadar in News

 

Apple TV+ Snoopy Presents: A Summer Musical does something I’ve never seen in a Peanuts animated special, and even the trailer gave me chills  Read Post »

295f0b7d716f1d3f3a1cdf27b8a1616e 1280 80 QTLKXU
TechRadar News

SharePoint-ageddon attacks riddled with free Warlock ransomware – and thousands of services could be compromised 

   

Security, Pro  Read More

SharePoint vulnerability is now being exploited for ransomware, hitting US agencies and 400+ victims, exposing dangerous gaps in hybrid system security practices. 

Latest from TechRadar in News

 

SharePoint-ageddon attacks riddled with free Warlock ransomware – and thousands of services could be compromised  Read Post »

BGraZx9YSg3CWrYFnXm9Un 1280 80 rVGizn
TechRadar Reviews

I test gaming PCs for a living, and I was more impressed by the build and performance of the NZXT Player PC (5070 Intel Edition) than I was expecting

  

​The NZXT Player PC (5070 Intel Edition) is a fantastic gaming PC that is ideal for just about any gamer willing to pay the asking price. 

Read More

​Latest from TechRadar in Reviews

I test gaming PCs for a living, and I was more impressed by the build and performance of the NZXT Player PC (5070 Intel Edition) than I was expecting Read Post »

dtymdfxXpT7stVhG9HdYVn 1280 80 gRW53W
TechRadar News

AMD mulls dedicated NPUs for desktop PCs – like graphics cards, but for AI tasks – and this could be excellent news for PC gamers 

   

GPU, Computing, Computing Components  Read More

AMD could soon make dedicated NPUs in the same vein as discrete graphics cards – and if you think that sounds boring, think again, PC gamers. 

Latest from TechRadar in News

 

AMD mulls dedicated NPUs for desktop PCs – like graphics cards, but for AI tasks – and this could be excellent news for PC gamers  Read Post »

cydfLjqWVNBy46DqhUUKPU 1280 80 Vi5Ydn
TechRadar News

Battlefield 6 multiplayer reveal event live build-up: start time, how to watch, and our moment to moment coverage in the run-up to the event 

   

Gaming  Read More

Live coverage of the Battlefield 6 reveal event, including expert impressions of the multiplayer mode as the show unfolds. 

Latest from TechRadar in News

 

Battlefield 6 multiplayer reveal event live build-up: start time, how to watch, and our moment to moment coverage in the run-up to the event  Read Post »

JHjKeNNsmciWHAq5YkhmyT 1280 80 aoKVFp
TechRadar News

Weapons director assures fans it’s just as good as his first horror movie hit: ‘If you liked Barbarian, this is more – and in a good way’ 

   

Entertainment, Streaming  Read More

Zach Cregger’s Barbarian was a tough act to follow, but the director says he feels confident in the movie he’s made. 

Latest from TechRadar in News

 

Weapons director assures fans it’s just as good as his first horror movie hit: ‘If you liked Barbarian, this is more – and in a good way’  Read Post »

getting started wordpress plugin development blog header Cizqqx
WordPress News

Introduction to WordPress Plugin Development: Build Your First Plugin

WordPress powers over 40% of the web, and much of its flexibility comes from plugins. Plugins are self-contained bundles of PHP, JavaScript, and other assets that extend what WordPress can do—powering everything from simple tweaks to complex business features. If you’re a developer new to WordPress, learning how to build plugins is the gateway to customizing and scaling the platform for any need.

In this guide, you’ll learn the essentials of plugin development, set up a local environment using Studio by WordPress.com, and build a fully functional example plugin. By the end, you’ll understand the anatomy of a plugin, how hooks work, and best practices for a maintainable and secure code.

Table of Contents

Setting up a local development environment

Before you write a single line of code, you need a local WordPress environment. WordPress Studio is the fastest way to get started. Studio is open source, maintained by Automattic, and designed for seamless WordPress development.

Example site set up in WordPress Studio.

Follow these steps:

Step 1: Download and install Studio

Visit developer.wordpress.com/studio and download the installer for macOS or Windows.

Step 2: Create your first local site

To create a local site, launch Studio and click Add Site. You’ll see a simple window where you can name your new site. After entering a name and clicking Add Site, Studio automatically configures a complete WordPress environment for you—no command line knowledge needed. Once complete, your new site appears in Studio’s sidebar, providing convenient links to view it in your browser or access the WordPress admin dashboard.

Add Site to WordPress Studio.

Step 3: Open your WordPress site and its admin section

Click the “Open site” link to open your site in the browser. You can also click the “WP Admin” button in Studio to access your site’s dashboard at /wp-admin. You’ll be automatically logged in as an Administrator. This is where you’ll manage plugins, test functionality, and configure settings.

Click Open Site to view site in browser.

Step 4: Open the code in your IDE

Studio provides convenient “Open in…” buttons that detect your installed code editor (like Visual Code or Cursor) and let you open your project in your preferred editor. You can configure your default code editor in Studio’s settings. Once opened in your code editor, you’ll have complete access to browse, edit, and debug the WordPress installation files.

Once you have your local environment for WordPress development set up and running, locate the plugins folder . In your project root, navigate to:

wp-content/
  └── plugins/

This is where all plugins live. To build your own, create a new folder (e.g., quick-reading-time) and add your plugin files there. Studio’s server instantly reflects changes when you reload your local site.

Example of a folder structure, with a folder labelled Quick Reading Time.

Creating your first plugin

Every plugin starts as a folder with at least one PHP file. Let’s build a minimal “Hello World” plugin to demystify the process.

  1. In wp-content/plugins/, create a folder called quick-reading-time.
  2. Inside that folder, create a file named quick-reading-time.php.

Your file structure should look like this:

wp-content/
  └── plugins/
    └── quick-reading-time/
      └── quick-reading-time.php

Add the following code to quick-reading-time.php:

<?php
/*
Plugin Name: Quick Reading Time
Description: Displays an estimated reading-time badge beneath post titles.
Version:     1.0
Author:      Your Name
License:     GPL-2.0+
Text Domain: quick-reading-time
*/

This header is a PHP comment, but WordPress scans it to list your plugin in Plugins → Installed Plugins. Activate it—nothing happens yet (that’s good; nothing is broken).

Tip: Each header field has a purpose. For example, Text Domain enables translation, and License is required for distribution in the Plugin Directory. Learn more in the Plugin Developer Handbook.

Understanding hooks: actions and filters

WordPress plugins interact with core events using hooks. There are two types:

  • Actions: Triggered when WordPress does something (e.g., loading scripts, saving posts).
  • Filters: Allow you to modify data before it’s displayed or saved.

Let’s add a reading-time badge using the the_content filter:

function qrt_add_reading_time( $content ) {
    // Only on single posts in the main loop
    if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
        return $content;
    }

    // 1. Strip HTML/shortcodes, count words
    $plain   = wp_strip_all_tags( strip_shortcodes( get_post()->post_content ) );
    $words   = str_word_count( $plain );

    // 2. Estimate: 200 words per minute
    $minutes = max( 1, ceil( $words / 200 ) );

    // 3. Build the badge
    $badge = sprintf(
        '<p class="qrt-badge" aria-label="%s"><span>%s</span></p>',
        esc_attr__( 'Estimated reading time', 'quick-reading-time' ),
        /* translators: %s = minutes */
        esc_html( sprintf( _n( '%s min read', '%s mins read', $minutes, 'quick-reading-time' ), $minutes ) )
    );

    return $badge . $content;
}
add_filter( 'the_content', 'qrt_add_reading_time' );

This snippet adds a reading time badge to post content using the the_content filter. It checks context with is_singular(), in_the_loop(), and is_main_query() to ensure the badge only appears on single posts in the main loop.

The code strips HTML and shortcodes using wp_strip_all_tags() and strip_shortcodes(), counts words, and estimates reading time. Output is localized with esc_attr__() and _n(). The function is registered with add_filter().

With this plugin activated, each post will now also display the reading time:

AD 4nXfx0p oxtdM jIUgMCDBjkon1 RU6px17uI74So DrIc hlxplkyA7Yq1ayIRlvGxO1DdNP1Xy0MSM9q4M

Loading assets the WordPress way

To style your badge, enqueue a stylesheet using the wp_enqueue_scripts action:

function qrt_enqueue_assets() {
    wp_enqueue_style(
        'qrt-style',
        plugin_dir_url( __FILE__ ) . 'style.css',
        array(),
        '1.0'
    );
}
add_action( 'wp_enqueue_scripts', 'qrt_enqueue_assets' );

Create a style.css file in the same folder:

.qrt-badge span {
    margin: 0 0 1rem;
    padding: 0.25rem 0.5rem;
    display: inline-block;
    background: #f5f5f5;
    color: #555;
    font-size: 0.85em;
    border-radius: 4px;
}

Best practice: Only load assets when needed (e.g., on the front end or specific post types) for better performance.

With this change, the reading time info on each post should look like this:

Reading time info displayed on a blog post.

Optional: Adding a settings screen

To make the average reading speed configurable, let’s add a settings page and connect it to our plugin logic. We’ll store the user’s preferred words-per-minute (WPM) value in the WordPress options table and use it in our reading time calculation.

Step 1: Register the setting

Add this code to your plugin file to register a new option and settings field:

// Register the setting during admin_init.
function qrt_register_settings() {
    register_setting( 'qrt_settings_group', 'qrt_wpm', array(
        'type' => 'integer',
        'sanitize_callback' => 'qrt_sanitize_wpm',
        'default' => 200,
    ) );
}
add_action( 'admin_init', 'qrt_register_settings' );

// Sanitize the WPM value.
function qrt_sanitize_wpm( $value ) {
    $value = absint( $value );
    return ( $value > 0 ) ? $value : 200;
}

This code registers a plugin option (qrt_wpm) for words-per-minute, using register_setting() on the admin_init hook. The value is sanitized with a custom callback using absint() to ensure it’s a positive integer.

Step 2: Add the settings page

Add a new page under Settings in the WordPress admin:

function qrt_register_settings_page() {
    add_options_page(
        'Quick Reading Time',
        'Quick Reading Time',
        'manage_options',
        'qrt-settings',
        'qrt_render_settings_page'
    );
}
add_action( 'admin_menu', 'qrt_register_settings_page' );

This code adds a settings page for your plugin under the WordPress admin “Settings” menu. It uses add_options_page() to register the page, and hooks the function to admin_menu so it appears in the dashboard. The callback (qrt_render_settings_page) will output the page’s content.

Step 3: Render the settings page

Display a form for the WPM value and save it using the Settings API:

function qrt_render_settings_page() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    ?>
    <div class="wrap">
        <h1><?php esc_html_e( 'Quick Reading Time Settings', 'quick-reading-time' ); ?></h1>
        <form method="post" action="options.php">
            <?php
            settings_fields( 'qrt_settings_group' );
            do_settings_sections( 'qrt_settings_group' );
            $wpm = get_option( 'qrt_wpm', 200 );
            ?>
            <table class="form-table" role="presentation">
                <tr>
                    <th scope="row">
                        <label for="qrt_wpm"><?php esc_html_e( 'Words Per Minute', 'quick-reading-time' ); ?></label>
                    </th>
                    <td>
                        <input name="qrt_wpm" type="number" id="qrt_wpm" value="<?php echo esc_attr( $wpm ); ?>" class="small-text" min="1" />
                        <p class="description"><?php esc_html_e( 'Average reading speed for your audience.', 'quick-reading-time' ); ?></p>
                    </td>
                </tr>
            </table>
            <?php submit_button(); ?>
        </form>
    </div>
    <?php
}

This function renders the plugin’s settings page, displaying a form to update the WPM value. It checks user permissions with current_user_can(), outputs the form using settings_fields(), do_settings_sections(), and retrieves the saved value with get_option(). The form submits to the WordPress options system for secure saving.

Step 4: Use the setting in your plugin logic

Update your reading time calculation to use the saved WPM value:

function qrt_add_reading_time( $content ) {
    if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
        return $content;
    }
    $plain   = wp_strip_all_tags( strip_shortcodes( get_post()->post_content ) );
    $words   = str_word_count( $plain );
    $wpm     = (int) get_option( 'qrt_wpm', 200 );
    $minutes = max( 1, ceil( $words / $wpm ) );
    $badge = sprintf(
        '<p class="qrt-badge" aria-label="%s">%s</p>',
        esc_attr__( 'Estimated reading time', 'quick-reading-time' ),
        esc_html( sprintf( _n( '%s min read', '%s mins read', $minutes, 'quick-reading-time' ), $minutes ) )
    );
    return $badge . $content;
}

This function adds a reading time badge to post content. It checks context with is_singular(), in_the_loop(), and is_main_query() to ensure it runs only on single posts in the main loop. It strips HTML and shortcodes using wp_strip_all_tags() and strip_shortcodes()), counts words, and retrieves the WPM value with get_option(). The badge is output with proper escaping and localization using esc_attr__(), esc_html(), and _n()).

With these changes, your plugin now provides a user-friendly settings page under Settings → Quick Reading Time. Site administrators can set the average reading speed for their audience, and your plugin will use this value to calculate and display the estimated reading time for each post.

Complete plugin code

Before we wrap up with best practices, let’s review the complete code for the “Quick Reading Time” plugin you built in this guide. This section brings together all the concepts covered—plugin headers, hooks, asset loading, and settings—into a single, cohesive example. Reviewing the full code helps solidify your understanding and provides a reference for your own projects.

At this stage, you should have a folder named quick-reading-time inside your wp-content/plugins/ directory, and a file called quick-reading-time.php with the following content:

<?php
/*
Plugin Name: Quick Reading Time
Description: Displays an estimated reading-time badge beneath post titles.
Version:     1.0
Author:      Your Name
License:     GPL-2.0+
Text Domain: quick-reading-time
*/

// Register the WPM setting during admin_init.
function qrt_register_settings() {
    register_setting( 'qrt_settings_group', 'qrt_wpm', array(
        'type' => 'integer',
        'sanitize_callback' => 'qrt_sanitize_wpm',
        'default' => 200,
    ) );
}
add_action( 'admin_init', 'qrt_register_settings' );

// Sanitize the WPM value.
function qrt_sanitize_wpm( $value ) {
    $value = absint( $value );
    return ( $value > 0 ) ? $value : 200;
}

// Add a settings page under Settings.
function qrt_register_settings_page() {
    add_options_page(
        'Quick Reading Time',
        'Quick Reading Time',
        'manage_options',
        'qrt-settings',
        'qrt_render_settings_page'
    );
}
add_action( 'admin_menu', 'qrt_register_settings_page' );

// Render the settings page.
function qrt_render_settings_page() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    ?>
    <div class="wrap">
        <h1><?php esc_html_e( 'Quick Reading Time Settings', 'quick-reading-time' ); ?></h1>
        <form method="post" action="options.php">
            <?php
            settings_fields( 'qrt_settings_group' );
            do_settings_sections( 'qrt_settings_group' );
            $wpm = get_option( 'qrt_wpm', 200 );
            ?>
            <table class="form-table" role="presentation">
                <tr>
                    <th scope="row">
                        <label for="qrt_wpm"><?php esc_html_e( 'Words Per Minute', 'quick-reading-time' ); ?></label>
                    </th>
                    <td>
                        <input name="qrt_wpm" type="number" id="qrt_wpm" value="<?php echo esc_attr( $wpm ); ?>" class="small-text" min="1" />
                        <p class="description"><?php esc_html_e( 'Average reading speed for your audience.', 'quick-reading-time' ); ?></p>
                    </td>
                </tr>
            </table>
            <?php submit_button(); ?>
        </form>
    </div>
    <?php
}

// Add the reading time badge to post content.
function qrt_add_reading_time( $content ) {
    if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
        return $content;
    }
    $plain   = wp_strip_all_tags( strip_shortcodes( get_post()->post_content ) );
    $words   = str_word_count( $plain );
    $wpm     = (int) get_option( 'qrt_wpm', 200 );
    $minutes = max( 1, ceil( $words / $wpm ) );
    $badge = sprintf(
        '<p class="qrt-badge" aria-label="%s">%s</p>',
        esc_attr__( 'Estimated reading time', 'quick-reading-time' ),
        esc_html( sprintf( _n( '%s min read', '%s mins read', $minutes, 'quick-reading-time' ), $minutes ) )
    );
    return $badge . $content;
}
add_filter( 'the_content', 'qrt_add_reading_time' );

// Enqueue the plugin stylesheet.
function qrt_enqueue_assets() {
    wp_enqueue_style(
        'qrt-style',
        plugin_dir_url( __FILE__ ) . 'style.css',
        array(),
        '1.0'
    );
}
add_action( 'wp_enqueue_scripts', 'qrt_enqueue_assets' );

You should also have a style.css file in the same folder with the following content to style the badge:

.qrt-badge span {
    margin: 0 0 1rem;
    padding: 0.25rem 0.5rem;
    display: inline-block;
    background: #f5f5f5;
    color: #555;
    font-size: 0.85em;
    border-radius: 4px;
}

This plugin demonstrates several foundational concepts in WordPress development:

  • Plugin Header: The block comment at the top registers your plugin with WordPress, making it discoverable and manageable from the admin dashboard.
  • Hooks: The plugin uses both actions (admin_init, admin_menu, wp_enqueue_scripts) and a filter (the_content) to integrate with WordPress at the right moments.
  • Settings API: By registering a custom option and rendering a settings page, the plugin allows site administrators to configure the average reading speed, making the feature flexible and user-friendly.
  • Sanitization and Security: All user input is sanitized, and output is escaped, following best practices to prevent security vulnerabilities.
  • Asset Loading: Styles are loaded using WordPress’s enqueue system, ensuring compatibility and performance.
  • Internationalization: All user-facing strings are wrapped in translation functions, making the plugin ready for localization.

By bringing these elements together, you have a robust, maintainable, and extensible plugin foundation. Use this as a template for your own ideas, and continue exploring the WordPress Plugin Developer Handbook for deeper knowledge.

Best practices for plugin development

Building a WordPress plugin is more than just making something work—it’s about creating code that is robust, secure, and maintainable for years to come. As your plugin grows or is shared with others, following best practices becomes essential to avoid pitfalls that can lead to bugs, security vulnerabilities, or compatibility issues. The habits you form early in your development journey will shape the quality and reputation of your work.

Let’s explore the foundational principles that set apart professional WordPress plugin development.

  • Prefix everything (e.g., qrt_) to avoid name collisions. WordPress is a global namespace, so unique prefixes for functions, classes, and even option names help prevent conflicts with other plugins or themes.
  • Escape and sanitize all output and input to prevent XSS and security issues. Always validate and clean data before saving it to the database or displaying it in the browser. Use functions like esc_html(), esc_attr(), and sanitize_text_field() to keep your plugin safe.
  • Translate strings using __(), and _n() for localization. Internationalization (i18n) ensures your plugin is accessible to users worldwide. Wrap all user-facing text in translation functions and provide a text domain.
  • Use version control (Git) and WP-CLI helpers (wp scaffold plugin, wp i18n make-pot). Version control is your safety net, allowing you to track changes, collaborate, and roll back mistakes. WP-CLI tools can automate repetitive tasks and enforce consistency.
  • Ship a readme.txt for the Plugin Directory and changelog. A well-written readme helps users understand your plugin’s features, installation steps, and update history. It’s also required for distribution on WordPress.org.
  • Debugging: Enable WP_DEBUG and use tools like Query Monitor for troubleshooting. Proactive debugging surfaces issues early, making them easier to fix and improving your plugin’s reliability.
  • Follow the Plugin Developer Handbook and WordPress Coding Standards. These resources are the gold standard for WordPress development, offering guidance on everything from code style to security.

Tip: Adopt these habits early—retrofitting best practices later is much harder. By making them part of your workflow from the start, you’ll save time, reduce stress, and build plugins you can be proud of.

Next steps and resources

You now have a working plugin that demonstrates the three “golden” hooks:

Where you go next is up to you—try adding custom post types (init), REST API endpoints (rest_api_init), scheduled events, or Gutenberg blocks (register_block_type). The mental model is the same: find the hook, write a callback, let WordPress run it.

Your plugin journey starts here

Every plugin—whether 40 KB or 40 MB—starts with a folder, a header, and a hook. Master that foundation, and the rest of the WordPress ecosystem opens wide. Experiment locally, keep your code readable and secure, and iterate in small steps. With practice, the leap from “I wish WordPress could…” to “WordPress does” becomes second nature.

Ready to build your own plugin? Try the steps above, share your results in the comments, or explore more advanced topics in our developer blog. Happy coding!

 [#item_full_content]

Introduction to WordPress Plugin Development: Build Your First Plugin Read Post »

bBcK2WTMCPfHFFpC2rf2ah 1280 80 hmVl2S
TechRadar Reviews

I tested DJI’s portable power station – and there’s a good reason why drone operators are going to want to check it out

  

​DJI, known for their drones, has recently entered the power station market. This may well be the power station DJI drone operators want, and for good reason. 

Read More

​Latest from TechRadar in Reviews

I tested DJI’s portable power station – and there’s a good reason why drone operators are going to want to check it out Read Post »

5j8yuYUGbcKmi4vUhEuCB7 1280 80 FUJsnU
TechRadar Reviews

Apart from minor flaws, the ViWoods AiPaper is the best E Ink writing tablet I’ve tested – and that distinction comes at a steep price

  

​It’s got a few issues but I’d still call the ViWoods AiPaper an ‘outstanding’ E Ink writing tablet. If money is no object and productivity is your focus, I can’t recommend it highly enough. 

Read More

​Latest from TechRadar in Reviews

Apart from minor flaws, the ViWoods AiPaper is the best E Ink writing tablet I’ve tested – and that distinction comes at a steep price Read Post »

Scroll to Top