Skip to main content

The Complete Guide to Developing Plugins & Extensions for Bricks Builder

Bricks is a visual, theme‑based site builder for WordPress. Unlike some other builders, Bricks ships with a genuinely developer‑friendly PHP/JS API. You can extend it by registering custom elements, adding controls to existing elements, creating dynamic data tags, and hooking into dozens of action and filter hooks. This guide walks through every major extension point, with working code, best practices, and distribution advice.

Everything below is based on the official Bricks Academy Developer documentation (academy.bricksbuilder.io/developer).


1. How Bricks Extensibility Works (Mental Model)

Before writing code, understand the three "worlds" your code lives in:

Context What runs there Notes
Builder panel (the editing UI) Vue.js + your PHP set_controls() / set_control_groups() definitions Controls are defined in PHP but rendered by Vue. Optionally you can supply a Vue x-template for live preview.
Canvas (live preview inside builder) Your render() output + frontend scripts The iframe that shows the rendered page while editing.
Frontend (the public page) Your render() output + enqueue_scripts() What visitors actually see.

A Bricks element is a PHP class that extends \Bricks\Element. It's conceptually very similar to a WordPress widget: you declare properties, define settings ("controls"), and implement a render() method that echoes HTML.

Your extension code can live in one of two places:

  • A Bricks child theme — simplest, officially recommended for small customizations.
  • A standalone WordPress plugin — best for distributable/reusable add‑ons (a true "Bricks plugin"). This is the focus of most of this guide.

⚠️ Golden rule: Never edit the Bricks parent theme files directly — updates will wipe your changes. Always use a child theme, a code‑snippets plugin, or your own plugin.


2. Prerequisites

  • A licensed, working copy of the Bricks theme (custom elements require the theme active).
  • Comfort with PHP, WordPress hooks (actions/filters), and basic HTML/CSS. JavaScript is needed for interactive elements.
  • A local dev environment (LocalWP, DevKinsta, wp-env, Docker, etc.).
  • Recommended WordPress settings per Bricks best practices:
    • Permalinks → Post name (Bricks builds builder URLs from permalinks).
    • Upload images ≥ 600px wide (ideally ≥ 1600px), and run Regenerate Thumbnails if adding Bricks to an existing site.

3. Two Ways to Ship Your Code

Option A — Bricks Child Theme (quick start)

  1. Download bricks-child.zip from your Bricks account (my.bricksbuilder.io).
  2. Appearance → Themes → Add New → Upload, then activate Bricks Child Theme.
  3. The child theme already includes a demo custom element and a working functions.php to learn from.

The child theme's functions.php is loaded in addition to (not instead of) the parent theme's, right before it.

Option B — Standalone Plugin (recommended for add‑ons)

A plugin is portable across themes/sites and is how commercial Bricks add‑ons (e.g., Bricksforge, Advanced Themer) are shipped. A minimal plugin scaffold:

my-bricks-addon/
├── my-bricks-addon.php          ← main plugin file (header + bootstrap)
├── elements/
│   └── element-test.php         ← one file per custom element
├── assets/
│   ├── js/
│   │   └── test-element.js
│   └── css/
│       └── test-element.css
└── includes/
    └── dynamic-tags.php

Main plugin file (my-bricks-addon.php):

<?php
/**
 * Plugin Name:       My Bricks Add-on
 * Description:       Custom elements and dynamic data for Bricks Builder.
 * Version:           1.0.0
 * Author:            Your Name
 * Requires PHP:      7.4
 * Text Domain:       my-bricks-addon
 */

if ( ! defined( 'ABSPATH' ) ) exit; // No direct access

define( 'MBA_PATH', plugin_dir_path( __FILE__ ) );
define( 'MBA_URL',  plugin_dir_url( __FILE__ ) );
define( 'MBA_VER',  '1.0.0' );

/**
 * Only boot our Bricks integration if the Bricks theme is active.
 */
add_action( 'init', function () {

    // Bail early if Bricks isn't present.
    if ( ! class_exists( '\Bricks\Elements' ) ) {
        return;
    }

    // Register our custom elements.
    $element_files = [
        MBA_PATH . 'elements/element-test.php',
    ];

    foreach ( $element_files as $file ) {
        \Bricks\Elements::register_element( $file );
    }

    // Load other integrations (dynamic tags, etc.).
    require_once MBA_PATH . 'includes/dynamic-tags.php';

}, 11 ); // Priority 11 => after Bricks registers its own elements.

The key API call is identical whether you use a child theme or a plugin:

\Bricks\Elements::register_element( $file, $name = null, $class = null );

register_element() accepts up to 3 arguments:

  • $file (required) — absolute server path to the element PHP file.
  • $name (optional) — the element's name string, e.g. prefix-element-test.
  • $element_class (optional) — the PHP class name, e.g. Prefix_Element_Test, which must extend \Bricks\Element.

The class_exists( '\Bricks\Elements' ) guard is what makes a plugin safe: if someone deactivates Bricks or switches themes, your plugin won't fatal‑error.


4. Anatomy of a Custom Element

Custom elements follow a pattern very similar to WordPress widgets: extend \Bricks\Element, fill in properties, define controls, implement render().

4.1 The blank skeleton

Create elements/element-test.php:

<?php
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

class Prefix_Element_Test extends \Bricks\Element {

    // ---- Element properties ----
    public $category     = '';
    public $name         = '';
    public $icon         = '';
    public $css_selector = '';
    public $scripts      = [];
    public $nestable     = false; // true => nestable (@since 1.5)

    // ---- Builder-specific methods ----
    public function get_label() {}
    public function get_keywords() {}
    public function set_control_groups() {}
    public function set_controls() {}

    // ---- Frontend-specific methods ----
    public function enqueue_scripts() {}
    public function render() {}
}

4.2 Element properties reference

Property Required Purpose
$category Lowercase, no spaces. Use a built‑in category (general, media, etc.) or your own. If custom, provide a translatable label via the bricks/builder/i18n filter.
$name Unique identifier, lowercase, no spaces. Always prefix to avoid clashes (e.g. prefix-test). Bricks adds a CSS class .brxe-{name} to your element.
$icon Icon font CSS class shown in the builder panel. Bricks bundles Font Awesome 6, Ionicons 4, and Themify Icons (e.g. ti-bolt-alt, fas fa-anchor).
$css_selector By default, style controls target the element wrapper. Set this to redirect the default CSS selector to a child element.
$nestable false for plain elements; true to allow the element to contain other elements via drag & drop.
$scripts Array of JS function names to run when the element renders on the frontend or updates in the builder. Prefix them (e.g. prefixElementTest).

4.3 Builder methods reference

Method Required Purpose
get_label() Return the localized element name shown in the panel.
get_keywords() Array of search keywords so users can find the element.
set_control_groups() Group your controls into collapsible sections under the Content/Style tabs.
set_controls() Define the element's settings (controls).
enqueue_scripts() Enqueue element‑specific CSS/JS — loaded only on pages using the element (great for performance).
render() Echo the element's HTML.

4.4 Built‑in render helpers

These helper methods (inherited from \Bricks\Element) are the backbone of a clean render():

Helper What it does
set_attribute( $key, $attribute, $value ) Register an HTML attribute for a tag. $key is a unique identifier for that tag ('_root' is special — it holds the element ID/classes). $value can be a string or array.
render_attributes( $key ) Output all attributes previously set for $key.
render_dynamic_data_tag( $tag, $context, $args ) Resolve a single dynamic tag such as {post_title} with the correct post context.
render_dynamic_data( $content ) Resolve any dynamic tags contained inside a string, using the correct post ID for the current render context.

5. A Complete, Working Element (Fully Populated)

Here's the full example from the docs, an "alert box"‑style element with a content field and a type selector:

<?php
if ( ! defined( 'ABSPATH' ) ) exit;

class Prefix_Element_Test extends \Bricks\Element {

    // Element properties
    public $category     = 'general';                // Predefined category
    public $name         = 'prefix-test';            // Always prefix!
    public $icon         = 'ti-bolt-alt';            // Themify icon
    public $css_selector = '.prefix-test-wrapper';   // Default CSS target
    public $scripts      = [ 'prefixElementTest' ];  // JS to run on render

    // Localized label
    public function get_label() {
        return esc_html__( 'Test element', 'bricks' );
    }

    // Search keywords
    public function get_keywords() {
        return [ 'alert', 'notice', 'message', 'box' ];
    }

    // Control groups (sections in the panel)
    public function set_control_groups() {
        $this->control_groups['text'] = [
            'title' => esc_html__( 'Text', 'bricks' ),
            'tab'   => 'content', // "content" or "style"
        ];

        $this->control_groups['settings'] = [
            'title' => esc_html__( 'Settings', 'bricks' ),
            'tab'   => 'content',
        ];
    }

    // Controls (the actual settings)
    public function set_controls() {

        $this->controls['content'] = [
            'tab'     => 'content',
            'group'   => 'text',
            'label'   => esc_html__( 'Content', 'bricks' ),
            'type'    => 'text',
            'default' => esc_html__( 'Content goes here ..', 'bricks' ),
        ];

        $this->controls['type'] = [
            'tab'         => 'content',
            'group'       => 'settings',
            'label'       => esc_html__( 'Type', 'bricks' ),
            'type'        => 'select',
            'options'     => [
                'info'    => esc_html__( 'Info', 'bricks' ),
                'success' => esc_html__( 'Success', 'bricks' ),
                'warning' => esc_html__( 'Warning', 'bricks' ),
                'danger'  => esc_html__( 'Danger', 'bricks' ),
                'muted'   => esc_html__( 'Muted', 'bricks' ),
            ],
            'inline'      => true,
            'clearable'   => false,
            'pasteStyles' => false,
            'default'     => 'info',
        ];
    }

    // Enqueue this element's assets (only on pages that use it)
    public function enqueue_scripts() {
        wp_enqueue_script( 'prefix-test-script' );
    }

    // Render the HTML output
    public function render() {

        $root_classes[] = 'prefix-test-wrapper';

        if ( ! empty( $this->settings['type'] ) ) {
            $root_classes[] = "color-{$this->settings['type']}";
        }

        // Attach classes to the special '_root' tag
        $this->set_attribute( '_root', 'class', $root_classes );

        // '_root' is REQUIRED (contains element ID, classes, etc.)
        echo "<div {$this->render_attributes( '_root' )}>";

            if ( ! empty( $this->settings['content'] ) ) {
                echo "<div>{$this->settings['content']}</div>";
            }

        echo '</div>';
    }
}

Key things to notice

  • $this->settings is an associative array keyed by your control IDs (content, type). This is how you read what the user entered.
  • _root is the reserved attribute key for the outer wrapper. You must echo {$this->render_attributes( '_root' )} on your root tag — it injects the element's unique ID and the .brxe-{name} class Bricks needs for styling and functionality.
  • CSS from style controls automatically applies to .brxe-prefix-test (or the $css_selector you set) — so styles "just work" if _root attributes are rendered.

6. Element Controls In Depth

Controls are defined in set_controls(). Each control is an array keyed by a unique control ID.

6.1 Universal control arguments (apply to every control type)

Argument Type Default Description
tab string content content or style.
group string Which control group to display under.
label string Localized label.
type string The control type (see list below).
inline bool false Label and input on the same line.
small bool false Narrow (60px) input.
css array CSS rule(s) auto‑generated from the value. Each rule needs property + selector (and optional important).
default string/array Default value (shape depends on control type).
pasteStyles bool true Set false for content‑producing controls so they're excluded from "Paste Styles".
description string Help text under the control.
required array Conditional display based on another control's value.

6.2 The css argument — auto CSS generation

Many controls can generate CSS for you without touching render(). Example of a color control that writes color onto .content:

$this->controls['testColor'] = [
    'tab'         => 'content',
    'group'       => 'settings',
    'label'       => esc_html__( 'Text color', 'bricks' ),
    'type'        => 'color',
    'inline'      => true,
    'small'       => true,
    'css'         => [
        [
            'property'  => 'color',
            'selector'  => '.content',
            'important' => true, // optional
        ],
    ],
    'default'     => [
        'rgb' => 'rgba(158, 158, 158, .8)',
        'hex' => '#9e9e9e',
    ],
    'pasteStyles' => false,
    'description' => esc_html__( 'Define the content color.', 'bricks' ),
    'required'    => [ 'showText', '!=', '' ],
];

6.3 The required argument — conditional controls

required shows/hides a control based on another control's value:

'required' => ['layout', '=', ['list', 'grid']],
// Show this control only when the "layout" control equals "list" OR "grid".

Comparison operators: =, !=, >, <, >=, <=.

6.4 Available control types

Content‑producing, CSS‑producing, or both:

Control Output
apply, info Panel only
text, textarea, editor, code, link, icon, svg, image, image-gallery, audio, datepicker, repeater, posts Content
color, background, gradient, border, box-shadow, text-shadow, filters, dimensions, direction, typography, align-items, justify-content, text-align, text-decoration, text-transform CSS
number, select, image, checkbox Content and/or CSS/Conditional

Each type has extra type‑specific arguments documented under academy.bricksbuilder.io/developer/controls/.


7. Adding Frontend JavaScript to Your Element

For interactive elements, register scripts referenced by $scripts and enqueue them in enqueue_scripts().

In the element class:

public $scripts = [ 'prefixElementTest' ]; // JS function name

public function enqueue_scripts() {
    wp_enqueue_script( 'prefix-test-script' );
    wp_enqueue_style(  'prefix-test-style'  );
}

Register the assets (in your plugin bootstrap, hooked to wp_enqueue_scripts):

add_action( 'wp_enqueue_scripts', function () {
    wp_register_script(
        'prefix-test-script',
        MBA_URL . 'assets/js/test-element.js',
        [ 'bricks-scripts' ], // depend on Bricks frontend if needed
        MBA_VER,
        true
    );
    wp_register_style(
        'prefix-test-style',
        MBA_URL . 'assets/css/test-element.css',
        [],
        MBA_VER
    );
} );

The JS file — the function named in $scripts is invoked by Bricks per element instance. A common, robust pattern:

// assets/js/test-element.js
function prefixElementTest() {
  document.querySelectorAll('.brxe-prefix-test').forEach((el) => {
    // Initialize each instance of the element here
    el.addEventListener('click', () => el.classList.toggle('is-open'));
  });
}

// Run on frontend load AND when Bricks re-renders in the builder
document.addEventListener('DOMContentLoaded', prefixElementTest);

Builder vs frontend gotcha: When enqueuing CSS that shouldn't leak into the builder UI, guard with bricks_is_builder_main():

add_action( 'wp_enqueue_scripts', function () {
    if ( ! bricks_is_builder_main() ) {
        wp_enqueue_style( 'my-frontend-only', /* ... */ );
    }
} );

8. Nestable Elements (containers that hold other elements)

Since Bricks 1.5, elements can be nestable — meaning users can drag other elements inside them (like the Accordion, Slider, and Tabs elements). This is what you want for cards, sliders, layout wrappers, etc.

8.1 Enable nesting

public $nestable = true;

This tells Bricks to use the nestable render path and enable drag & drop inside the element.

8.2 Define the default inner structure with get_nestable_children()

Return an array of element definitions that populate the element when it's first dropped in:

public function get_nestable_children() {
    return [
        [
            'name'     => 'block',
            'label'    => esc_html__( 'Slide', 'bricks' ) . ' {item_index}',
            'settings' => [
                '_hidden' => [
                    '_cssClasses' => 'hidden-class', // CSS class, hidden from UI
                ],
            ],
            'children' => [
                [
                    'name'     => 'heading',
                    'settings' => [
                        'text' => esc_html__( 'Slide', 'bricks' ) . ' {item_index}',
                    ],
                ],
                [
                    'name'     => 'button',
                    'settings' => [
                        'text'  => esc_html__( 'I am a button', 'bricks' ),
                        'size'  => 'lg',
                        'style' => 'primary',
                    ],
                ],
            ],
        ],
    ];
}
  • name is a Bricks element name (block, heading, button, etc.).
  • children recursively nests more elements.
  • settings pre‑populates each child.
  • {item_index} is a placeholder token for numbering repeated items.

8.3 Render children in PHP

Use the Frontend::render_children() helper, passing $this:

public function render() {
    $output  = "<div {$this->render_attributes( '_root' )}>";
    $output .= \Bricks\Frontend::render_children( $this ); // renders nested items
    $output .= '</div>';
    echo $output;
}

8.4 (Optional) Live builder preview with a Vue x-template

For a live preview inside the builder, output a Vue template that includes <bricks-element-children>:

public function render_builder() { ?>
    <script type="text/x-template" id="tmpl-bricks-element-custom-nestable">
        <component :is="tag">
            <h2>Title before nestable children</h2>
            <bricks-element-children :element="element"/>
            <p>Text node after nestable children</p>
        </component>
    </script>
<?php }

8.5 (Optional) Repeater for same‑level items

If your nestable structure is a list of same‑level items (like accordion items), add a repeater control bound to children:

public function set_controls() {
    $this->controls['_children'] = [
        'type'          => 'repeater',
        'titleProperty' => 'label',
        'items'         => 'children', // @since 1.5
    ];
}

9. Extending Existing Bricks Elements (without replacing them)

You don't always need a brand‑new element. Since Bricks 1.3.2, you can inject controls into any core element with the filter bricks/elements/{element_name}/controls:

add_filter( 'bricks/elements/posts/controls', function ( $controls ) {
    $controls['ignoreStickyPosts'] = [
        'tab'   => 'content',
        'group' => 'query',
        'label' => esc_html__( 'Ignore Sticky Posts', 'my-bricks-addon' ),
        'type'  => 'checkbox',
    ];
    return $controls;
} );
  • bricks/elements/{element_name}/control_groups — add/modify groups.
  • bricks/element/settings — modify an element's settings just before render.
  • bricks/element/render and bricks/frontend/render_element — filter the rendered HTML.
  • bricks/element/render_attributes — modify attributes.
  • bricks/posts/query_vars (and bricks/{object_type}s/query_vars) — alter the query that a query‑loop element runs.

10. Creating Custom Dynamic Data Tags

Dynamic Data tags (like {post_title}) let content update automatically. You can register your own, e.g. {my_dd_tag}.

Step 1 — Register the tag in the builder UI

add_filter( 'bricks/dynamic_tags_list', function ( $tags ) {
    $tags[] = [
        'name'  => '{my_dd_tag}',            // prefix to keep unique
        'label' => 'My Dynamic Data',
        'group' => 'My Dynamic Data Group',
    ];
    return $tags;
} );

Step 2 — Resolve a single tag with bricks/dynamic_data/render_tag

add_filter( 'bricks/dynamic_data/render_tag', 'get_my_tag_value', 20, 3 );

function get_my_tag_value( $tag, $post, $context = 'text' ) {
    if ( ! is_string( $tag ) ) {
        return $tag;
    }

    $clean_tag = str_replace( [ '{', '}' ], '', $tag );

    if ( $clean_tag !== 'my_dd_tag' ) {
        return $tag; // not ours; pass through untouched
    }

    return run_my_dd_tag_logic();
}

function run_my_dd_tag_logic() {
    return 'My dynamic data value';
}

Step 3 — Resolve tags embedded in larger strings

bricks/dynamic_data/render_content and bricks/frontend/render_data fire when a string may contain multiple tags mixed with HTML:

add_filter( 'bricks/dynamic_data/render_content', 'render_my_tag', 20, 3 );
add_filter( 'bricks/frontend/render_data',        'render_my_tag', 20, 2 );

function render_my_tag( $content, $post, $context = 'text' ) {
    if ( strpos( $content, '{my_dd_tag}' ) === false ) {
        return $content;
    }
    $value = run_my_dd_tag_logic();
    return str_replace( '{my_dd_tag}', $value, $content );
}

Tags with arguments (e.g. {my_dd_tag:foo})

Parse arguments after the colon:

add_filter( 'bricks/dynamic_data/render_tag', function ( $tag, $post, $context = 'text' ) {
    if ( ! is_string( $tag ) ) return $tag;

    $clean = str_replace( [ '{', '}' ], '', $tag );
    if ( strpos( $clean, 'my_dd_tag:' ) !== 0 ) return $tag;

    $argument = str_replace( 'my_dd_tag:', '', $clean );
    return run_my_dd_tag_logic( $argument );
}, 20, 3 );

For content strings containing argument‑style tags, use a regex to find and replace each occurrence:

preg_match_all( '/{(my_dd_tag:[^}]+)}/', $content, $matches );
foreach ( $matches[1] as $i => $match ) {
    $full  = $matches[0][ $i ];
    $value = get_my_tag_value( $match, $post, $context );
    $content = str_replace( $full, $value, $content );
}

11. Hooks Reference (Actions & Filters)

Bricks exposes a large hook API. Below are the most useful ones for plugin developers, grouped by purpose.

11.1 Action hooks (structure & lifecycle)

Hook Fires…
bricks_before_site_wrapper / bricks_after_site_wrapper Around the whole site wrapper.
bricks_before_header / bricks_after_header Around the header template.
bricks_before_footer / bricks_after_footer Around the footer template.
bricks_body Just inside <body>.
bricks_meta_tags In the <head> for meta output.
bricks/load_elements/before / bricks/load_elements/after Around element registration — handy for late registration.
bricks/frontend/before_render_data / after_render_data Around content rendering.
bricks/query/before_loop / after_loop Around query‑loop iterations.
bricks/form/custom_action Run custom logic on a Form element submission.
bricks/dynamic_data/tags_registered After all dynamic tags register.

11.2 Filter hooks (behavior & data)

Filter Use it to…
bricks/builder/i18n Provide a translatable label for a custom element category.
bricks/elements/{name}/controls Add controls to an existing element.
bricks/elements/{name}/control_groups Add control groups to an existing element.
bricks/element/settings Modify element settings before render.
bricks/element/render / bricks/frontend/render_element Filter rendered element HTML.
bricks/element/render_attributes Modify HTML attributes.
bricks/dynamic_tags_list Register a custom dynamic data tag.
bricks/dynamic_data/render_tag / render_content Resolve custom tags.
bricks/dynamic_data/register_providers Register a whole dynamic data provider class.
bricks/posts/query_vars / bricks/{type}s/query_vars Modify query‑loop queries.
bricks/query/result / result_count Alter query results.
bricks/code/allow_execution / disable_execution / disallow_keywords Control the Code element's PHP execution (security).
bricks/svg/allowed_tags / allowed_attributes / bypass_sanitization Control SVG sanitization.
bricks/builder/supported_post_types Enable Bricks editing on custom post types.
bricks/conditions/groups / options / result Extend the conditional‑visibility system.
bricks/setup/control_options Add reusable global control option sets.

The full documented list contains 200+ filters and ~35 actions. Browse them at academy.bricksbuilder.io/developer/hooks/.

11.3 Registering a custom element category with a translatable label

If you use a non‑built‑in $category, register its label:

add_filter( 'bricks/builder/i18n', function ( $i18n ) {
    $i18n['myaddon'] = esc_html__( 'My Add-on', 'my-bricks-addon' );
    return $i18n;
} );

Then set public $category = 'myaddon'; on your element.


12. Reading & Generating Bricks Data (Advanced)

Bricks stores page structure as an array of element definitions in post meta. The Schema section of the docs describes this data model. This matters when you:

  • Programmatically generate templates or elements.
  • Build import/export tooling.
  • Provide default nestable children (as shown earlier).

Each element node generally looks like:

[
    'id'       => 'abc123',          // unique element id
    'name'     => 'heading',         // element type
    'parent'   => 'parentId',        // parent element id (0 for top-level)
    'children' => [ /* child ids */ ],
    'settings' => [ /* control values */ ],
]

Helper functions worth knowing (documented under /developer/functions/):

  • bricks_render_dynamic_data( $content, $post_id, $context ) — resolve dynamic tags in an arbitrary string outside an element.
  • bricks_is_builder_main() / bricks_is_builder() — detect the builder context (crucial for asset loading).

13. Best Practices, Security & Conventions

Naming & prefixing

  • Prefix everything: element $name, $scripts function names, control IDs where collisions are likely, dynamic tags, and hook callbacks. This prevents conflicts with core and other add‑ons.

Internationalization

  • Wrap all user‑facing strings in esc_html__() / __() with your text domain.

Security

  • Add if ( ! defined( 'ABSPATH' ) ) exit; at the top of every PHP file.
  • Escape output in render(): use esc_html(), esc_attr(), esc_url(), and wp_kses_post() as appropriate. Never echo raw $this->settings values into HTML without escaping.
  • Sanitize any data you save.
  • Respect Bricks' Code‑element security filters if your add‑on executes user PHP.

Performance

  • Enqueue assets in the element's enqueue_scripts() so they load only on pages that use the element.
  • Register (wp_register_script/style) globally, then wp_enqueue_* within the element.
  • Guard builder‑unfriendly CSS with bricks_is_builder_main().

Compatibility & robustness

  • Always guard integration code with class_exists( '\Bricks\Elements' ).
  • Register elements on init at priority 11 (after Bricks loads its own).
  • Declare Requires PHP and test against the Bricks versions you support (note version‑gated features like nestable @since 1.5).

Don't edit core

  • Never modify the parent theme. Use a plugin, child theme, or code‑snippet plugin.

14. Testing & Debugging Workflow

  1. Enable debugging in wp-config.php:
    define( 'WP_DEBUG', true );
    define( 'WP_DEBUG_LOG', true );
    define( 'SCRIPT_DEBUG', true ); // load unminified assets
    
  2. Verify registration: open the builder, search your element by label/keywords in the elements panel.
  3. Check both contexts: confirm the element renders correctly in the canvas and on the live frontend (they can differ).
  4. Inspect the wrapper: confirm your root tag has the .brxe-{name} class and unique ID — if not, you likely forgot render_attributes( '_root' ).
  5. Watch the console for JS errors when your $scripts function runs.
  6. Study the source: the Bricks theme's own element files (accordion-nested.php, slider-nested.php, tabs-nested.php, etc.) are the best reference for advanced patterns. The child theme also ships a demo element.

15. Packaging & Distributing a Bricks Add‑on

  • Ship as a standard WordPress plugin (portable across themes).
  • Keep the Bricks guard so the plugin degrades gracefully without Bricks.
  • Follow WordPress plugin structure and headers; add an update mechanism (e.g., your own update server, or freemius/EDD for commercial).
  • Version your elements and consider a settings page to toggle which elements load.
  • Document required Bricks version, especially for version‑gated APIs (nestable @since 1.5, add‑controls filter @since 1.3.2).
  • If you want a no‑/low‑code path, tools like Bricksforge, WDesignKit, and Advanced Themer provide visual "element/widget builders" that generate Bricks elements — useful for prototyping, but hand‑coded elements give the most control.

Quick‑Reference Checklist

Task API / Hook
Register a custom element \Bricks\Elements::register_element( $file, $name, $class ) on init (priority 11)
Base class to extend \Bricks\Element
Required methods get_label(), set_controls(), render()
Required render output render_attributes( '_root' ) on the root tag
Group controls set_control_groups()
Auto‑generate CSS from a control css argument on the control
Conditional control required argument
Make element a container $nestable = true + get_nestable_children() + Frontend::render_children($this)
Add controls to core elements bricks/elements/{name}/controls
Custom dynamic tag bricks/dynamic_tags_list + bricks/dynamic_data/render_tag + render_content
Custom element category label bricks/builder/i18n
Detect builder for asset loading bricks_is_builder_main()
Resolve dynamic data in a string bricks_render_dynamic_data() / $this->render_dynamic_data()

Official Resources

  • Developer hub: academy.bricksbuilder.io/developer/
  • Create elements: /developer/elements/create-your-own-elements/
  • Nestable elements: /developer/elements/nestable-elements/
  • Controls reference: /developer/controls/
  • Hooks (actions & filters): /developer/hooks/
  • Dynamic data tags: /developer/dynamic-data/create-your-own-dynamic-data-tag/
  • Child theme guide: /developer/guides/child-theme/

With the element API, the controls system, nestable containers, dynamic data tags, and the extensive hook library, you have everything needed to build a full‑featured, distributable Bricks Builder add‑on. Start from the child‑theme demo element, graduate to a standalone plugin structure, and layer in nestable behavior, dynamic data, and hooks as your add‑on grows.