Create atomic widget
Scope: External — the full documented outcome is shippable from a 3rd-party plugin via
elementor/widgets/register/elementor/elements/elements_registered; no Elementor Core changes required. Changing the built-in element catalog is Core-only. Full split + disclaimer: skills-scope.md.
Implementation location
- PHP: existing or new third-party plugin repository; plugin-owned namespace/module (e.g.
MyPlugin\AtomicWidgets\). - Do not modify Elementor Core. Built-in elements live in Core at
modules/atomic-widgets/elements/— mirror patterns there only as reference. - Runnable reference: examples/example-plugin/ (
Atomic_Greeting_Widget+ Twig). Playground: tests/playwright/blueprints/example-plugin.json.
Prerequisites
- Experiment
e_atomic_elementsactive — see getting-started/experiments.md. - Often also
e_opt_in_v4on sites still on legacy editor UX.
Read first: atomic-widgets/authoring-widgets.md, hooks.md, rendering.md.
Checklist
- Pick base class
- Leaf widget →
Atomic_Widget_Base+Has_Templatewhen using Twig. - Container →
Atomic_Element_Base+Has_Element_Template; set$this->meta( 'is_container', true )in constructor.
- Leaf widget →
- Implement required API
define_props_schema()— prop types with->default(); values are PropValue{ $$type, value }. Most widgets also declareclasses(Classes_Prop_Type) andattributes(Attributes_Prop_Type) — see built-ins undermodules/atomic-widgets/elements/.define_atomic_controls()—Section::make()->set_items([ ... ])with concrete controls fromElementor\Modules\AtomicWidgets\Controls\Types\(e.g.Text_Control,Select_Control) via::bind_to( 'key' ). Enum props: pairString_Prop_Type::make()->enum([...])->default(...)withSelect_Control::bind_to( 'key' )->set_options([ ['value' => 'h1', 'label' => 'H1'], ... ])(options are[['value' => ..., 'label' => ...], ...]).- Widget (
Atomic_Widget_Base):get_element_type()— stable id (e.g.e-my-widget). Container (Atomic_Element_Base):get_element_type()andget_type()(same id);generate()usesget_element_type()on widgets,get_type()on elements.
- Optional layers
- Twig:
get_templates(),.html.twigfiles. define_base_styles()→Style_Definitionmaps.- Container:
define_allowed_child_types(),define_default_children(),define_default_html_tag().
- Twig:
- Register
- Widget:
elementor/widgets/register→$manager->register( new My_Widget() ). - Element:
elementor/elements/elements_registered→$manager->register_element_type( new My_Container() ).
- Widget:
- Verify
- Control keys match schema keys exactly.
- Saved data uses PropValue envelope per prop-value.md.
- Widget appears under v4-elements panel (
Atomic_Widget_Base::get_categories()returnsv4-elementsby default — custom plugins inherit this; no override needed). get_controls()stays empty (legacy v3 controls unused).
Canonical example (use this, not a shortened skeleton)
Full corrected walkthrough: docs/atomic-builder/examples/create-atomic-widget.md.
Key fixes vs older snippets: base classes under Elements\Base\; primitives under PropTypes\Primitives\; Section::make()->set_items() not add_control().
Test/MCP JSON: My_Widget::generate() or My_Container::generate() — see id methods above.
External implementation path
- Plugin owns PHP classes; register on
elementor/widgets/registerorelementor/elements/elements_registered. - Extend schema for all types only via
elementor/atomic-widgets/props-schemawhen type-agnostic — prefer subclassing for type-specific props. - Filter
elementor/atomic-widgets/controlsto tweak control trees per element.
Core reference paths (do not edit)
- Built-ins:
modules/atomic-widgets/elements/— see elements-catalog.md. - Save validation via
Props_Parser/Style_Parser; frontend via rendering.md.
See also
- fundamentals/prop-types.md
- extend-prop-types — custom prop types on widget schema
- dynamic-tags/extending.md — dynamic bindings on props