Contensio logo

Contensio Widgets vs WordPress Widgets

A straightforward comparison of how widgets work in Contensio versus the WordPress classic and block-based widget systems.

Contensio Widgets vs WordPress Widgets

Both systems let admins place reusable UI blocks on the frontend without editing templates or writing code. Beyond that basic premise, the mechanics differ significantly — in registration, configuration, placement, and the underlying philosophy of what a widget is.


WordPress Widgets — Classic System

WordPress's original widget system (pre-5.8) is built around a class hierarchy. To create a widget, you extend WP_Widget and implement four methods: __construct() for registration metadata, widget() to render the frontend output, form() to render the admin configuration form, and update() to sanitize and save settings.

class My_Widget extends WP_Widget {
    public function __construct() {
        parent::__construct('my_widget', 'My Widget', ['description' => '...']);
    }

    public function widget($args, $instance) {
        echo $args['before_widget'];
        echo '<h2>' . esc_html($instance['title']) . '</h2>';
        // ... output
        echo $args['after_widget'];
    }

    public function form($instance) {
        // Manually render HTML input fields for the WP admin
        $title = !empty($instance['title']) ? $instance['title'] : '';
        echo '<p><label>Title: <input class="widefat" name="' . $this->get_field_name('title') . '" value="' . esc_attr($title) . '" /></label></p>';
    }

    public function update($new_instance, $old_instance) {
        return ['title' => sanitize_text_field($new_instance['title'])];
    }
}

register_widget('My_Widget');

Widget areas are called "sidebars" — a name that stuck even though they can appear anywhere on a page. They are registered in functions.php via register_sidebar():

register_sidebar([
    'id'   => 'after-post',
    'name' => 'After Post',
    'before_widget' => '<div class="widget %2$s">',
    'after_widget'  => '</div>',
]);

Templates render an area with dynamic_sidebar('after-post').

The admin Widgets screen provides a drag-and-drop interface where admins drag widget types from a panel into sidebar areas. Each instance has an inline settings form. This system works, but the form() method means every widget author manually writes HTML for their settings form — there is no schema or abstraction.


WordPress Block Widgets — Gutenberg Era

WordPress 5.8 (July 2021) deprecated the classic widget system in favor of a block-based approach. Widget areas are now edited using the full Gutenberg block editor. Instead of a drag-and-drop widget panel, admins edit widget areas exactly as they would edit a post — inserting blocks, configuring them with the block settings sidebar, and nesting them freely.

Any block registered via register_block_type() can be used inside a widget area. This is more powerful than classic widgets: you can place arbitrary blocks, use block patterns, and nest layouts inside a sidebar.

The trade-off is complexity. The block editor is a full React application, and not all themes support it equally. Some themes that worked well with classic widgets render poorly with block-based widget areas because the block editor's wrapper markup differs from what the theme styled. The Classic Widgets plugin (maintained by the WordPress team) exists specifically to restore the old behavior for sites that need it.


Contensio Widgets

Contensio's widget system is designed around a simple interface rather than a class hierarchy, and a config schema approach rather than manual form authoring.

To build a widget, implement Contensio\Contracts\WidgetInterface:

interface WidgetInterface
{
    public static function label(): string;
    public static function icon(): string;
    public static function description(): string;
    public static function configSchema(): array;
    public function render(array $config): string;
}

The configSchema() method returns a structured array describing the configuration fields. The admin form is generated automatically from this schema — there is no form() method to write. Field types include text, textarea, number, toggle, and select.

Widget areas are declared in the theme's theme.json file:

{
    "widget_areas": [
        { "id": "after-post",     "label": "After Post Content" },
        { "id": "footer-widgets", "label": "Footer Widgets" }
    ]
}

Templates render areas with a single call:

{!! \Contensio\Support\WidgetArea::render('after-post') !!}

The admin Widgets page shows one tab per area. Each tab lists active widget instances in order, with up/down arrows to reorder them and a settings button to edit configuration. There is no drag-and-drop — intentionally. The interface stays simple and works well on any screen size.

Plugin developers register custom widget types in their ServiceProvider:

public function boot(): void
{
    WidgetRegistry::register('my-plugin-widget', MyWidget::class);
}

Side-by-Side Comparison

Feature WordPress (classic) WordPress (blocks) Contensio
Widget registration WP_Widget subclass + register_widget() Block type registration via register_block_type() WidgetInterface implementation + WidgetRegistry::register()
Placement UI Drag-and-drop widget panel Full Gutenberg block editor Area tabs with up/down ordering
Area declaration register_sidebar() in functions.php Theme theme.json with supports_widgets Theme theme.json widget_areas array
Template rendering dynamic_sidebar('id') Automatic (block output) WidgetArea::render('id')
Config form Manually authored via WP_Widget::form() Block attributes + Inspector controls (React) Auto-generated from configSchema()
In-content injection Shortcodes or action hooks Inline block insertion contensio/content/body filter hook
Plugin widget type WP_Widget subclass Registered block type WidgetInterface + ServiceProvider
Config persistence WordPress options table (serialized) Post meta (block markup) Dedicated widget_instances table (JSON)
JavaScript required (admin) Minimal React / full block editor No (server-rendered admin form)

Key Differences

1. No class hierarchy

WordPress requires you to extend WP_Widget, tying widget logic to a specific base class with specific method signatures. Contensio uses a plain interface — no parent class, no constructor conventions, no $args array threaded through rendering.

2. Config schema instead of manual form authoring

In WordPress classic, writing a settings form means writing HTML inside the form() method — field names, labels, esc_attr() calls, and matching update() sanitization by hand. In Contensio, you describe the fields in configSchema() and the admin renders the form for you. The config arrives in render() as a clean, typed array.

3. No block editor required

Contensio widgets are not blocks. They do not require a React-based editor and they do not produce block markup. The admin UI for widgets is a simple server-rendered page. This keeps the cognitive overhead low and means widgets behave predictably regardless of the theme's block editor support.

4. Same hook system, no separate API

Plugin developers who already understand Hook::add() and Hook::addFilter() have most of the knowledge they need. Widget types are registered with one call in a ServiceProvider's boot() method — the same place plugin developers already register hooks, custom routes, and event listeners. There is no separate Widget API to learn.

5. In-content injection is explicit and separated

WordPress shortcodes and action hooks can inject content anywhere, including within post content, but the mechanism is the same regardless of context. Contensio deliberately separates structural placement (widget areas via WidgetArea::render()) from in-content injection (the contensio/content/body filter hook). This makes the intent clear in code and avoids the ambiguity of a shortcode that could be placed either inside content or in a template.


When to Use Widgets vs. Hooks in Contensio

Use widgets when:

  • The admin needs to choose which areas receive the content (e.g., a newsletter signup in some areas but not all)
  • The output needs to be configurable per-instance (e.g., "latest posts" with a different count per placement)
  • Multiple instances may coexist (e.g., three different custom-html blocks in the footer)
  • The injection point is a named area, not a specific position within post body HTML

Use hooks when:

  • The plugin always injects at the same location and no admin configuration is needed
  • The injection is conditional based on context that cannot be expressed as an area (e.g., only on 404 pages, only for logged-in users, only when a specific plugin is active)
  • You need to inject inside post body HTML — between paragraphs, after images, or at other positions within rendered content
  • The placement should not be visible or adjustable in the admin UI

The two mechanisms are not mutually exclusive. A plugin might register a widget type for admin-configurable placement and also use a hook for a fixed, unconditional injection — both co-existing in the same plugin.