Elementor's built-in widgets cover most needs, but sooner or later a project needs something the page builder can't do out of the box — a specific layout, a data-driven component, or a branded element that needs to behave exactly one way every time it's used. That's when a custom Elementor widget makes sense.
This guide walks through the actual structure of a custom Elementor widget, using PHP, the way I build them for client projects.
When a Custom Widget Makes Sense (and When It Doesn't)
Before writing code, it's worth being honest about when this is the right tool:
- Good fit: a component reused across many pages that needs consistent, editable controls (a custom pricing table, a branded testimonial slider, a specific call-to-action block)
- Not a good fit: a one-off layout you'll only use once — that's faster to build with Elementor's existing widgets and containers
- Also worth noting: heavy custom widget libraries add PHP overhead on every page load, so build only what you'll actually reuse — see my guide on WordPress speed optimization for why page builder bloat matters for performance
Prerequisites
- Elementor (free) or Elementor Pro installed and active
- Basic PHP and WordPress plugin structure knowledge
- A custom plugin or your theme's
functions.phpto register the widget (a small dedicated plugin is the safer choice — it survives theme changes)
Step 1: Set Up the Widget File Structure
Create a small custom plugin rather than editing your theme directly — this keeps the widget working even if the theme changes later:
/wp-content/plugins/my-custom-widgets/
my-custom-widgets.php
widgets/
class-custom-cta-widget.php
Step 2: Register the Widget with Elementor
In your main plugin file, hook into Elementor's widget registration action:
add_action( 'elementor/widgets/register', function( $widgets_manager ) {
require_once( __DIR__ . '/widgets/class-custom-cta-widget.php' );
$widgets_manager->register( new \Custom_CTA_Widget() );
});
Elementor changed this hook name across versions — older tutorials reference elementor/widgets/widgets_registered, which is deprecated. Always check the current Elementor developer documentation for the active hook name before building on a live client site.
Step 3: Build the Widget Class
Every custom widget extends Elementor's base widget class and implements a few required methods:
class Custom_CTA_Widget extends \Elementor\Widget_Base {
public function get_name() {
return 'custom-cta-widget';
}
public function get_title() {
return 'Custom CTA Block';
}
public function get_icon() {
return 'eicon-call-to-action';
}
public function get_categories() {
return [ 'general' ];
}
protected function register_controls() {
// Controls go here — Step 4
}
protected function render() {
// Output HTML goes here — Step 5
}
}
Step 4: Add Editable Controls
Controls are what let the client edit the widget from Elementor's visual editor — text fields, colors, images, and so on:
protected function register_controls() {
$this->start_controls_section(
'content_section',
[
'label' => 'Content',
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
$this->add_control(
'cta_heading',
[
'label' => 'Heading',
'type' => \Elementor\Controls_Manager::TEXT,
'default' => 'Ready to get started?',
]
);
$this->add_control(
'cta_button_text',
[
'label' => 'Button Text',
'type' => \Elementor\Controls_Manager::TEXT,
'default' => 'Get a Quote',
]
);
$this->add_control(
'cta_button_link',
[
'label' => 'Button Link',
'type' => \Elementor\Controls_Manager::URL,
]
);
$this->end_controls_section();
}
Elementor provides many control types beyond text fields — colors, typography, image uploads, sliders, and repeaters for lists of items. Match the control type to what the client actually needs to be able to change, and nothing more.
Step 5: Render the Widget Output
The render method reads the saved control values and outputs the actual HTML shown on the page:
protected function render() {
$settings = $this->get_settings_for_display();
?>
<div class="custom-cta-block">
<h3><?php echo esc_html( $settings['cta_heading'] ); ?></h3>
<a href="<?php echo esc_url( $settings['cta_button_link']['url'] ); ?>" class="cta-button">
<?php echo esc_html( $settings['cta_button_text'] ); ?>
</a>
</div>
<?php
}
Always escape output (esc_html, esc_url, esc_attr) — this isn't optional, it's a basic WordPress security requirement for any dynamic output.
Step 6: Style the Widget
Enqueue a CSS file scoped to your widget's class names, rather than relying on Elementor's default styling. This keeps the widget's appearance consistent regardless of which Elementor theme or global styles are active on the site.
Common Mistakes to Avoid
- Skipping output escaping — leaves the site open to XSS if the field is ever exposed to untrusted input
- Hardcoding widget behavior instead of exposing it as a control — defeats the purpose of a page-builder-friendly widget
- Not namespacing PHP classes — can cause fatal errors if another plugin defines a class with the same name
- Building one giant widget instead of several focused ones — harder to maintain and reuse across projects
Custom Widget vs. Just Using Elementor's Built-In Widgets
For most small business sites, Elementor's stock widgets combined with its container/flexbox layout system cover 90% of real needs. Custom widgets are worth the development cost mainly for agencies and larger sites that reuse the same branded component across dozens of pages — the investment pays off through consistency and easier future edits, not through anything a built-in widget can't visually achieve.
Frequently Asked Questions
Do I need Elementor Pro to build custom widgets?
No — the free version of Elementor supports custom widget registration through the same PHP API. Elementor Pro adds more built-in widgets and theme-building features, but isn't required for custom widget development itself.
Will a custom widget break when Elementor updates?
Occasionally, if Elementor changes a hook name or deprecates a method — this has happened a few times historically. Testing on a staging site before applying Elementor updates to a live site is the safest practice, especially for client sites with custom widgets in use.
Is it better to use ACF Blocks or Gutenberg instead of Elementor widgets?
It depends entirely on what the site already uses. If a client's site is built and edited in Elementor, a custom Elementor widget keeps the editing experience consistent for them. If the site is otherwise Gutenberg-based, building a custom block usually makes more sense than introducing Elementor just for one component.
Need a Custom Elementor Widget Built?
If you need a specific branded component that Elementor's default widgets can't handle cleanly, I build custom Elementor widgets as part of full WordPress development projects. See more about hiring an Elementor developer, or get in touch to discuss what you need.