DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk12 min

How to Develop Custom Widgets in WordPress (PHP and Block-Editor Guide)

Learn how to develop a custom WordPress widget from a WP_Widget class, add secure settings, register it on widgets_init, expose a theme widget area, and handle block-based widget editing.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To develop a classic custom WordPress widget, create a PHP class that extends WP_Widget, implement its rendering and settings methods, register the class on widgets_init, and place it in a widget area supplied by the active theme. WordPress 5.8 and later can also edit widget areas with blocks, but a PHP widget remains a separate, supported integration path.

This guide builds a configurable widget from scratch, explains every required method, shows how themes expose widget areas, and covers the differences and failure modes you will encounter on block-based and classic installations.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What a WordPress widget actually is

“A widget adds content and features to a widget area (also called a sidebar).” A widget is the component that produces content; a widget area is the location registered by a theme where site owners can place one or more widgets. The terms are often confused because WordPress historically called many widget areas “sidebars,” even when they appear in a footer, header, or other template region.

Your PHP class does not create a location by itself. The active theme must register at least one sidebar with register_sidebar(), or the Widgets screen will have nowhere to place the new component. A theme can also choose different HTML wrappers, so reliable widgets use the wrapper arguments supplied by WordPress instead of hard-coding a surrounding <aside> or heading structure.

Choose the authoring model

Concern Classic PHP widget Block-based widget editing
Authoring model A PHP class extending WP_Widget. Blocks placed inside a widget area in the block editor.
Registration Attach register_widget() to widgets_init. Register or enqueue blocks through the block APIs; a PHP widget can still appear as a legacy widget block.
Settings interface The class’s form() method renders controls in the Widgets screen. Block controls are provided by the block editor and the individual block.
Rendering path WordPress calls widget() with display arguments and saved instance settings. The editor stores block markup and WordPress renders that block content in the widget area.
Version note The WP_Widget API is the established classic integration. WP_Widget_Block was introduced in WordPress 5.8.0; current sites may expose block-based widget editing alongside classic widgets.
Theme compatibility Requires a registered widget area and should honor that area’s wrappers. Requires a theme and configuration that support block-based widget editing; availability is not identical on every site.

There is no universal feature matrix for third-party themes and plugins. Confirm which editor the active theme exposes before choosing a user interface. If you need a reusable PHP component with its own settings screen, the classic API is the direct route.

Prerequisites and safe placement

  • A WordPress installation with the ability to add PHP through a custom plugin or a site-specific plugin.
  • PHP knowledge sufficient to edit a class and test output safely.
  • A theme with at least one registered widget area.
  • A staging site or backup before changing production code.

For production, put the class in a small plugin rather than the parent theme’s functions.php. A theme change can remove that file’s code and the widget, while a plugin keeps the feature independent of presentation. A child theme is acceptable when the widget is intentionally coupled to that theme.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The three-part classic widget workflow

The WordPress workflow has three essential parts: extend WP_Widget, register the widget, and ensure that the theme provides a widget area. Configurable widgets add a fourth practical concern: validate and sanitize the saved settings before using them.

1. Create a class with a stable identifier

The class must extend WP_Widget. Call the parent constructor with a stable base ID, a human-readable name, and optional documentation such as a description. The base ID is stored with widget instances, so changing it after deployment can make existing settings appear to disappear. Use a project-specific prefix to reduce collisions with other plugins.

<?php
class Custom_Featured_Notice_Widget extends WP_Widget {
    public function __construct() {
        parent::__construct(
            'custom_featured_notice',
            'Featured notice',
            array(
                'description' => 'Displays a configurable notice and optional link.',
            )
        );
    }

    public function widget( $args, $instance ) {
        // Front-end output goes here.
    }

    public function form( $instance ) {
        // Administration controls go here.
    }

    public function update( $new_instance, $old_instance ) {
        // Validation and sanitization go here.
        return $old_instance;
    }
}

Use a unique class name as well as a unique base ID. A duplicate class or ID can cause a fatal error, overwrite another widget’s settings, or make the wrong widget appear in the admin interface.

2. Render the front end with widget()

WP_Widget::widget() receives two arguments: $args, containing the active area’s display wrappers, and $instance, containing the saved settings for this particular widget instance. The method must be overridden to output the component.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The following implementation uses the four wrapper keys supplied by the theme: before_widget, before_title, after_title, and after_widget.

public function widget( $args, $instance ) {
    $title   = isset( $instance['title'] ) ? $instance['title'] : '';
    $message = isset( $instance['message'] ) ? $instance['message'] : '';
    $url     = isset( $instance['url'] ) ? $instance['url'] : '';

    echo $args['before_widget'];

    if ( '' !== $title ) {
        echo $args['before_title'];
        echo esc_html( $title );
        echo $args['after_title'];
    }

    echo '<p>' . wp_kses_post( $message ) . '</p>';

    if ( '' !== $url ) {
        echo '<p><a href="' . esc_url( $url ) . '">Learn more</a></p>';
    }

    echo $args['after_widget'];
}

Escape at output time for the context in which a value is used. esc_html() is appropriate for plain text, esc_url() for an HTML URL attribute, and wp_kses_post() when you deliberately permit the HTML elements allowed in post content. Do not print a setting directly merely because it was sanitized when saved; settings can be altered by imports, database edits, or older versions of your code.

3. Add administrator controls with form()

The form() method creates fields shown when an administrator adds or edits the widget. Generate field IDs and names with the widget helpers so multiple instances do not share DOM identifiers.

public function form( $instance ) {
    $title   = isset( $instance['title'] ) ? $instance['title'] : '';
    $message = isset( $instance['message'] ) ? $instance['message'] : '';
    $url     = isset( $instance['url'] ) ? $instance['url'] : '';
    ?>
    <p>
        <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">Title</label>
        <input class="widefat"
               id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
               name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
               type="text"
               value="<?php echo esc_attr( $title ); ?>" />
    </p>
    <p>
        <label for="<?php echo esc_attr( $this->get_field_id( 'message' ) ); ?>">Message</label>
        <textarea class="widefat"
                  id="<?php echo esc_attr( $this->get_field_id( 'message' ) ); ?>"
                  name="<?php echo esc_attr( $this->get_field_name( 'message' ) ); ?>"
                  rows="5"><?php echo esc_textarea( $message ); ?></textarea>
    </p>
    <p>
        <label for="<?php echo esc_attr( $this->get_field_id( 'url' ) ); ?>">Link URL</label>
        <input class="widefat"
               id="<?php echo esc_attr( $this->get_field_id( 'url' ) ); ?>"
               name="<?php echo esc_attr( $this->get_field_name( 'url' ) ); ?>"
               type="url"
               value="<?php echo esc_attr( $url ); ?>" />
    </p>
    <?php
}

Use esc_attr() for values in attributes and esc_textarea() for a textarea’s contents. The helper methods produce the indexed names WordPress expects when several copies of the widget are saved.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

4. Validate and sanitize with update()

WordPress passes the submitted values as $new_instance and the previous values as $old_instance. Return the cleaned array that should be stored. Sanitization should match the intended data type rather than applying one filter to every field.

public function update( $new_instance, $old_instance ) {
    $instance = array();

    $instance['title'] = isset( $new_instance['title'] )
        ? sanitize_text_field( $new_instance['title'] )
        : '';

    $instance['message'] = isset( $new_instance['message'] )
        ? wp_kses_post( $new_instance['message'] )
        : '';

    $instance['url'] = isset( $new_instance['url'] )
        ? esc_url_raw( $new_instance['url'] )
        : '';

    return $instance;
}

sanitize_text_field() removes tags and normalizes a plain-text title. wp_kses_post() preserves the limited HTML permitted in post content for the message. esc_url_raw() prepares a URL for storage; escape it again with esc_url() when printing it into markup. If a field is required, validate that it is non-empty and decide whether to retain the old value or return an error-compatible fallback.

5. Register the class at the correct time

Attach registration to widgets_init. Core initializes its default widgets and fires this action during widget setup, so registering earlier can fail or be overwritten.

function custom_register_featured_notice_widget() {
    register_widget( 'Custom_Featured_Notice_Widget' );
}
add_action( 'widgets_init', 'custom_register_featured_notice_widget' );

register_widget() accepts either the name of a WP_Widget subclass or an already-created widget object. Passing the class name lets WordPress instantiate it at the appropriate point. Keep the callback and class loaded before the action runs; a file included conditionally after widgets_init will not register the widget on that request.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make sure the theme exposes a widget area

A registered widget can exist without being placeable if the active theme has no sidebars. A theme typically registers areas during widgets_init as well:

function custom_theme_register_sidebars() {
    register_sidebar(
        array(
            'name'          => 'Footer column',
            'id'            => 'footer-column',
            'description'   => 'Widgets displayed in the footer column.',
            'before_widget' => '<section id="%1$s" class="widget %2$s">',
            'after_widget'  => '</section>',
            'before_title'  => '<h2 class="widget-title">',
            'after_title'   => '</h2>',
        )
    );
}
add_action( 'widgets_init', 'custom_theme_register_sidebars' );

The id must be stable and unique. The wrapper strings may contain the %1$s and %2$s substitutions WordPress uses for the widget ID and CSS classes. Your widget should output the supplied wrappers unchanged so the theme controls semantics and styling.

Install and place the widget

  1. Put the class and registration callback in a site-specific plugin, activate it, and check that the PHP file has no syntax errors.
  2. Open the administrator area and go to the Widgets screen. On classic screens, the new widget appears in the available widgets list under its human-readable name.
  3. Open a registered widget area, add the widget, fill in its fields, and save.
  4. Visit a page that uses that area. Confirm the title, message, link, wrappers, and CSS classes match the active theme.
  5. Edit the same widget again and verify that each saved value loads into its own field. Add a second instance to confirm that IDs and settings remain independent.

On a site using block-based widget editing, the Widgets screen may show a block editor instead of the older drag-and-drop interface. A classic PHP widget can appear through the editor’s legacy widget mechanism, while ordinary blocks are edited directly inside the widget area.

Designing a dependable widget

Keep the data model small

Store only settings the widget needs to render. A title, message, and URL are enough for the example. Avoid storing generated HTML, transient API responses, or values that can be derived from other settings. Smaller instance arrays are easier to migrate and validate.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use stable names and prefixes

Prefix class names, functions, option keys, and base IDs with a project-specific string. A generic name such as Notice_Widget can collide with another plugin. Once users have saved instances, changing the base ID effectively creates a different widget type.

Respect capability and trust boundaries

Only users who can manage widgets should reach the normal settings form, but never treat administrator access as a reason to print unsanitized values. Sanitize on update and escape on output. If your widget adds custom AJAX actions or settings outside the normal WP_Widget flow, add the appropriate capability checks and nonces for those actions.

Keep presentation theme-aware

Use the four wrapper arguments instead of assuming a specific heading level or container element. Do not enqueue a large stylesheet globally for a small widget; scope styles to the widget class and load assets only where your architecture permits. Test with themes that use different wrapper elements and title markup.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Classic widgets and the block editor

WordPress 5.8.0 introduced the core WP_Widget_Block class and block-based editing for widget areas. This does not erase the classic API. A site can expose a block editor while still loading PHP widgets registered through widgets_init. In that arrangement, the editor provides a block-shaped interface for inserting the legacy widget, and the widget’s own form() controls remain the settings UI.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If you need content that editors compose from paragraphs, buttons, images, and other blocks, a native block may be a better authoring model. If you need a self-contained PHP renderer, a compact settings form, or compatibility with installations that retain the classic Widgets screen, WP_Widget remains appropriate. The active theme and site configuration determine which interface an administrator sees.

Testing checklist before release

  • Activate the plugin on a staging site running the minimum WordPress version you support.
  • Confirm the widget is listed once, with the expected name and description.
  • Add two instances to different areas and verify that their settings do not leak into each other.
  • Save empty, long, malformed, and HTML-containing values and inspect the stored and rendered results.
  • Check that plain text is escaped, permitted message markup is filtered, and URLs are escaped in attributes.
  • Test the widget in every theme area you claim to support, including areas with custom wrappers.
  • Try the classic Widgets screen and, where available, the block-based Widgets screen.
  • Deactivate the plugin and confirm that the rest of the site remains usable; reactivate it and verify saved instances return.
  • Review PHP logs for notices caused by missing array keys or assumptions about wrapper arguments.

Troubleshooting common failures

The widget does not appear in the Widgets screen

Check that the file defining the class is loaded, the class name exactly matches the name passed to register_widget(), and registration is attached to widgets_init. A PHP parse error or a duplicate class declaration can stop execution before registration. Also confirm the plugin is active and that you are viewing the active site’s Widgets screen.

There is no place to add it

The active theme may not register any widget area, or it may use block template areas rather than classic sidebars. Inspect the theme’s sidebar registration and templates. Adding a widget class cannot create a visual location without a theme area that calls the sidebar.

Saved values vanish or every instance shares one value

Do not change the base ID after deployment. In the form, use get_field_id() and get_field_name() instead of hand-written names. In update(), return a complete sanitized instance array and use the keys that widget() reads.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The title or link breaks the page

Check context-specific escaping. Use esc_html() for a text title, esc_attr() in an HTML attribute, esc_url() for a printed URL, and esc_textarea() in a textarea. Never concatenate an unfiltered URL or raw administrator input into markup.

The widget output looks different in another theme

That is expected when the theme supplies different wrapper elements, classes, or title markup. Keep the wrapper arguments in your output and scope any additional CSS to your widget’s own class. Avoid assuming that every theme uses an <aside>, an <h2>, or the same sidebar ID.

The block editor does not show the expected control

Determine whether you are editing a native block or a legacy PHP widget inserted into a widget area. Native block controls do not call your class’s form() method. A theme or configuration may also disable block-based widget editing, in which case the classic Widgets screen is the relevant interface.

When to move beyond a widget

A widget is a good fit for content placed in a theme-defined area. Consider a block, shortcode, template part, or full plugin screen when the feature must appear inside post content, be composed visually from multiple blocks, expose complex data management, or operate independently of sidebars. The right choice depends on where editors need to place the feature and whether its settings are per-instance or global.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a conventional configurable sidebar component, the pattern is dependable: extend WP_Widget, render through widget(), provide controls through form(), sanitize with update(), register on widgets_init, and test against the active theme’s widget areas and editor mode.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.