WP Plugins by AI
AI generated WP plugins (Opus + ChatGPT)
oEmbed JS
A plugin that lets you define your own oEmbeds for WordPress. It also works in page builders like BricksBuilder and Divi.
oEmbed JS (v1.2.0) — Documentation
Update 1.3.0: You can now change the order of providers and disable WordPress's built-in oEmbed.
Introduction
oEmbed JS is a WordPress plugin that transforms plain URLs in your content into rich embedded media — videos, audio players, social media posts, images, and more — entirely using client-side JavaScript. Unlike WordPress's built-in oEmbed system, which renders embeds during page generation on the server, oEmbed JS operates in the browser. This makes it particularly well-suited for sites built with page builders like Bricks Builder, Elementor, Divi, and others where you may not have direct control over how post content is rendered by PHP.
The plugin ships with 15 pre-configured providers including YouTube, Vimeo, Spotify, SoundCloud, X (Twitter), TikTok, and more. You can add any oEmbed-compatible provider manually or import from the official oEmbed directory, which contains over 300 providers.
What is oEmbed?
oEmbed is an open standard that allows websites to display embedded representations of a URL. Instead of requiring you to copy and paste complex <iframe> code, oEmbed lets a consuming site (yours) ask a provider site (like YouTube) for the embed code automatically.
The protocol works through a simple request-response cycle:
-
Discovery: Your site recognizes that a URL belongs to a specific provider (e.g.,
https://www.youtube.com/watch?v=dQw4w9WgXcQbelongs to YouTube). -
Request: Your site sends a request to the provider's oEmbed endpoint — a special API URL — asking "How should I display this URL?" The request looks something like:
https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ&format=json -
Response: The provider returns a JSON object containing metadata and embed code:
{ "type": "video", "title": "Rick Astley - Never Gonna Give You Up", "html": "<iframe width=\"480\" height=\"270\" src=\"https://www.youtube.com/embed/dQw4w9WgXcQ\" ...></iframe>", "width": 480, "height": 270, "provider_name": "YouTube" } -
Rendering: Your site takes the
htmlfield from the response and inserts it into the page, replacing the original link.
The oEmbed response type field can be one of four values:
- video — Typically returns an
<iframe>embed for video or audio players. - rich — Returns arbitrary HTML, used by services like Twitter, Instagram, and TikTok for their post embeds.
- photo — Returns a direct image URL rather than HTML.
- link — A simple hyperlink with title metadata; no visual embed.
Why a Proxy is Needed
Modern browsers enforce a security policy called CORS (Cross-Origin Resource Sharing). When JavaScript running on your site tries to fetch data from a different domain (like youtube.com/oembed), the browser blocks the response unless the remote server explicitly allows it with CORS headers. Many oEmbed providers do not include these headers.
oEmbed JS solves this by routing oEmbed requests through your own WordPress server. The frontend JavaScript sends its request to a proxy endpoint on your site (/wp-json/oembed-js/v1/proxy), which then fetches the data from the provider server-to-server, where CORS restrictions do not apply, and passes the response back to the browser.
How oEmbed JS Works
The plugin operates in two distinct phases:
Phase 1: Configuration (Admin)
When you visit Settings → oEmbed JS in your WordPress admin, the plugin reads its configuration from a JSON file stored in the plugin directory. The admin page is a single-page application that communicates with the WordPress REST API to save settings, reset defaults, and browse the oEmbed directory. No settings are stored in the WordPress database — everything lives in a config.json file.
Phase 2: Embedding (Frontend)
When a visitor loads a page on your site, the frontend script (included via the snippet you paste into your page builder) performs the following sequence:
-
Fetches configuration from the public REST endpoint (
/wp-json/oembed-js/v1/public-config), which returns only the enabled providers and display settings. -
Compiles URL patterns from the provider list into regular expressions.
-
Scans the DOM for
<a>(anchor) elements within the CSS selectors you have configured (e.g.,.entry-content,.brxe-text). -
Matches each link against the compiled provider patterns to determine if it should be embedded.
-
Replaces matching links with embed wrappers and fetches the oEmbed data through the server-side proxy.
-
Renders the embed HTML returned by the provider, applying your display settings (responsive wrapping, dimensions, lazy loading, etc.).
-
Executes embedded scripts if the provider's HTML includes
<script>tags (required by Twitter, Instagram, TikTok, and similar services that use JavaScript-based embed rendering).
Installation
- Upload the
oembedjs_1.2.0folder to your/wp-content/plugins/directory, or install through the WordPress plugin installer. - Activate the plugin through the Plugins menu in WordPress.
- Navigate to Settings → oEmbed JS to configure the plugin.
- Copy the generated snippet and paste it into your page builder's footer scripts section.
Requirements
- WordPress 5.0 or higher
- PHP 7.0 or higher
- The plugin directory must be writable by the web server (for storing
config.json)
Getting Started
After activation, the basic setup requires two steps:
-
Define CSS Selectors: Tell the plugin which parts of your pages contain links that should be converted to embeds. This is done in the "CSS Selectors to Scan" field under Global Display Settings.
-
Install the Snippet: Copy the snippet from the top of the settings page and paste it into your page builder's footer scripts area.
Once these two steps are complete, any plain link in your content that matches a configured provider will automatically be replaced with a rich embed when visitors view the page.
The Snippet
At the top of the settings page, you will find a Page Builder Snippet box containing a <script> tag. This is the frontend loader that makes embedding work. It looks like this:
<script src="https://yoursite.com/wp-content/plugins/oembedjs_1.2.0/frontend/oembed-js-front.js" data-oembed-api="https://yoursite.com/wp-json/oembed-js/v1/public-config" defer></script>
How the Snippet Works
- The
srcattribute points to the frontend JavaScript file that performs all the link scanning and embedding logic. - The
data-oembed-apiattribute tells the script where to fetch the plugin's configuration. The script reads this attribute from its own<script>tag at runtime. - The
deferattribute ensures the script loads without blocking page rendering and executes after the HTML document has been parsed.
Where to Place It
Place this snippet in your page builder's footer scripts section. The exact location depends on your builder:
- Bricks Builder: Settings → Custom Code → Body (footer) scripts
- Elementor: Settings → Custom Code (or use the wp_footer hook)
- Divi: Theme Options → Integration → Add code to the body
- GeneratePress / Astra / other themes: Use a child theme's
footer.phpor a custom code snippets plugin
The snippet does not need to be wrapped in additional <script> tags — it is already a complete script tag.
Use the Copy Snippet button to copy the text to your clipboard.
Global Display Settings
These settings control the default appearance and behavior of all embeds. Individual providers can override some of these values.
Maximum Width
Sets the CSS max-width property on embed wrappers. Accepts any valid CSS length value.
- Default:
100% - Examples:
100%,640px,50vw,40rem
This constrains the embed to never exceed the specified width while allowing it to be smaller when the container is narrower.
Maximum Height
Sets the CSS max-height property on embed wrappers. Content exceeding this height will be clipped.
- Default: Empty (unlimited)
- Examples:
480px,60vh
Leave empty to allow embeds to take their natural height.
Fixed Width
When set, applies a CSS width (not max-width) to the embed wrapper. This overrides the Maximum Width setting.
- Default: Empty (auto)
- Examples:
640px,50vw
Use this when you want all embeds to be exactly a specific width regardless of their container.
Fixed Height
When set, applies a CSS height (not max-height) to the embed wrapper. This overrides the Maximum Height setting.
- Default: Empty (auto)
- Examples:
480px,360px
Important: When fixed dimensions are set, the responsive aspect-ratio wrapping is disabled because you are explicitly controlling the size.
Wrapper CSS Class
The CSS class applied to the <div> element that wraps each embed.
- Default:
jsemb
You can target this class in your theme's CSS to style all embeds globally. For example:
.jsemb {
margin: 20px auto;
border-radius: 8px;
overflow: hidden;
}
CSS Selectors to Scan
A comma-separated list of CSS selectors. The frontend script will only scan for links inside elements matching these selectors.
- Default:
.entry-content, .post-content, .page-content, .brxe-text, .et_pb_text_inner, article
This is a critical security and performance measure. By restricting scanning to specific containers, the plugin avoids converting links in navigation menus, sidebars, footers, or other areas where you would not want embeds to appear.
If this field is empty, no embeds will be generated. This is by design — the plugin requires you to explicitly define where embeds should appear.
To find the correct selectors for your theme, use your browser's developer tools (right-click an element → Inspect) to identify the class or ID of the container that holds your post or page content.
Loading Strategy
Controls when embed content (particularly iframes) is loaded:
-
Lazy (default): Uses the browser's native
loading="lazy"attribute on iframes. The embed content is only fetched when the user scrolls it into or near the viewport. This improves initial page load performance. -
Eager: Loads all embed content immediately when the page loads. Use this if embeds appear above the fold and you want them visible as quickly as possible.
-
Click to load: Displays a placeholder box with the provider name and URL. The actual embed is only fetched and rendered when the user clicks the placeholder. This is the most performance-friendly option and also provides a measure of privacy, since no third-party content is loaded until the user explicitly requests it.
Responsive Embeds
When enabled (default), the plugin wraps iframes in an aspect-ratio-preserving container using the "padding-bottom percentage" technique. This ensures video embeds scale fluidly to fill their container width while maintaining the correct height ratio (typically 16:9).
The technique works as follows:
- The plugin reads the
widthandheightattributes from the iframe (or from the oEmbed response data). - It calculates the aspect ratio:
(height / width) × 100to get a percentage. - It creates a wrapper
<div>withposition: relative,height: 0, andpadding-bottomset to that percentage. - The iframe is positioned absolutely within this wrapper to fill it completely.
This approach is widely used across the web for responsive video embeds and works in all modern browsers.
When fixed width or fixed height values are set, responsive wrapping is automatically disabled because explicitly sized embeds should not scale.
Providers
Providers are the services whose URLs the plugin recognizes and converts to embeds. Each provider entry has the following properties:
Name
A human-readable label displayed in the admin interface. This has no effect on functionality.
URL Pattern
A text pattern used to match URLs in your content against this provider. This can be either a regular expression or a plain text substring, controlled by the Regex checkbox.
As a regular expression (Regex checked):
https?://(?:www\.)?youtube\.com/watch\?v=([a-zA-Z0-9_-]+)
This uses standard JavaScript regex syntax. The i (case-insensitive) flag is automatically applied. The pattern is tested against the full href value of each anchor tag.
As a plain pattern (Regex unchecked):
youtube.com/watch
The plugin checks if the URL contains this substring anywhere within it.
Regular expressions are more precise and prevent false matches. All pre-configured providers use regex patterns.
Endpoint URL
The oEmbed API URL for the provider. This is where the plugin sends requests (via the server-side proxy) to retrieve embed data.
For example, YouTube's endpoint is https://www.youtube.com/oembed. When the plugin encounters a matching YouTube URL, it constructs a request like:
https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ&format=json
Regex
A checkbox indicating whether the URL Pattern should be treated as a JavaScript regular expression (true) or a plain substring match (false).
Enabled
A checkbox to quickly enable or disable a provider without deleting it. Disabled providers are excluded from the public configuration served to the frontend and are not checked by the proxy's allowlist validation.
Embed Type
A classification hint for the provider:
- iframe (video/audio): The provider typically returns an
<iframe>element. Used for YouTube, Vimeo, Spotify, etc. - Rich HTML: The provider returns arbitrary HTML that may include
<script>tags. Used for Twitter, Instagram, TikTok, Reddit, etc. - Photo: The provider returns an image URL rather than HTML.
This field is informational and used by the admin interface. The frontend rendering logic primarily checks for the presence of html in the oEmbed response, the type field, and the url field for photos, rather than relying on this admin-side classification.
Managing Providers
-
Add Provider: Click the "+ Add Provider" button to create a blank provider entry. Fill in the name, URL pattern, endpoint, and other fields.
-
Delete Provider: Click the × button on any provider's header row. You will be asked to confirm.
-
Reorder: Providers are matched in order from top to bottom. The first matching provider wins. Currently, reordering requires deleting and re-adding providers.
-
Collapse/Expand All: Utility buttons to collapse or expand all provider panels at once for easier navigation.
Remember: After making any changes to providers or settings, you must click Save Settings at the bottom of the page.
The oEmbed Directory Browser
The plugin includes a built-in browser for the official oEmbed provider directory hosted at oembed.com. This directory contains over 300 providers maintained by the oEmbed community.
Accessing the Directory
Click the 📂 Browse oEmbed Directory button in the Providers section. A modal window opens with a searchable list of all available providers.
How It Works
-
When you first open the directory, the plugin fetches the provider list from
https://oembed.com/providers.jsonthrough your WordPress server (via the REST endpoint/wp-json/oembed-js/v1/oembed-directory). -
The server transforms each entry from the official format into the plugin's internal format. This transformation includes:
- Converting oEmbed wildcard scheme patterns (which use
*as a wildcard) into regular expressions (replacing*with.+). - Normalizing endpoint URLs by replacing
{format}placeholders withjson. - Generating stable IDs from provider names.
- Converting oEmbed wildcard scheme patterns (which use
-
The transformed data is cached as a WordPress transient for 24 hours, so subsequent opens of the directory do not re-fetch from oembed.com.
-
The directory list is displayed in the browser. Providers that you have already added (matched by endpoint URL) are visually dimmed and labeled "Already added." By default, already-added providers are hidden; uncheck the "Hide already added" checkbox to show them.
Importing Providers
- Search or scroll through the list to find providers you want.
- Click on a provider row (or its checkbox) to select it. You can select multiple providers.
- Click Import Selected to add the selected providers to your configuration.
- The modal closes and you will see the imported providers in your provider list.
- Click Save Settings to persist the changes.
Imported providers are pre-configured with the URL patterns and endpoints from the official directory. You may need to adjust patterns or settings for specific use cases.
Per-Provider Display Overrides
Each provider has an "overrides" section that lets you customize display settings for that specific provider, overriding the global values. Any override field left empty falls back to the corresponding global setting.
Available overrides:
- Max Width: Override the global maximum width for this provider's embeds.
- Max Height: Override the global maximum height.
- Fixed Width: Override the global fixed width.
- Fixed Height: Override the global fixed height.
- Wrapper Class: Use a different CSS class for this provider's embed wrappers.
This is useful when different content types need different treatment. For example, you might want Twitter embeds to be narrower than YouTube videos:
- Global Max Width:
100% - Twitter override Max Width:
550px - Spotify override Fixed Height:
352px
How the Frontend Script Works
The frontend script at frontend/oembed-js-front.js is the core of the plugin's client-side functionality. Here is a detailed walkthrough of its operation:
1. Self-Discovery
The script finds its own <script> tag in the DOM by querying for script[data-oembed-api]. It reads the data-oembed-api attribute to determine the URL of the configuration endpoint. It also derives the proxy URL by replacing public-config with proxy in the API URL path.
2. Configuration Fetch
The script makes a fetch() request to the public configuration endpoint. This endpoint returns only the data the frontend needs: the display settings and the list of enabled providers. Disabled providers are filtered out on the server side.
3. Pattern Compilation
Each provider's URL pattern is compiled into a JavaScript RegExp object (if it is a regex pattern) or stored as a plain string for substring matching. Invalid regex patterns are caught and logged as warnings without crashing the script. This compilation happens once and the compiled patterns are reused for all URL checks.
4. DOM Scanning
The script queries the DOM using the CSS selectors defined in your settings. Within each matched container, it finds all <a> elements with an href attribute. Each anchor is processed exactly once — a data-oejs-processed attribute is set on each processed link to prevent duplicate handling.
5. URL Matching
For each anchor, the script tests the href against all compiled provider patterns in order. The first match wins, and the corresponding provider configuration is used.
6. Wrapper Creation
A <div> element is created with the configured wrapper class and dimension styles. This wrapper replaces the original <a> element in the DOM. Depending on the loading strategy:
- Lazy/Eager: A "Loading embed…" placeholder is shown, and the oEmbed fetch begins immediately.
- Click to load: A styled placeholder with the provider name and URL is shown. The oEmbed fetch only begins when the user clicks it.
7. oEmbed Fetching
The script requests embed data through the server-side proxy:
/wp-json/oembed-js/v1/proxy?endpoint=<provider_endpoint>&url=<page_url>
Responses are cached in an in-memory JavaScript object keyed by the full proxy URL. If the same URL appears multiple times on a page, only one network request is made.
8. Rendering
Based on the oEmbed response:
- If
htmlis present (video/rich types), the HTML is inserted directly. - If the type is
photoand aurlfield is present, an<img>tag is created. - Otherwise, a fallback link is displayed.
After HTML insertion:
- Responsive wrapping: If enabled and no fixed dimensions are set, any
<iframe>found in the embed HTML is wrapped in an aspect-ratio container. - Loading attribute: The
loadingattribute is set on iframes tolazyoreagerper your configuration. - iframe permissions: The
allowfullscreenandallowattributes are set to enable fullscreen, autoplay, encrypted media, and picture-in-picture.
9. Script Execution
Some providers (Twitter, Instagram, TikTok) return HTML that includes <script> tags. These scripts are responsible for rendering the embed's visual appearance. Since scripts inserted via innerHTML do not execute automatically in browsers, the plugin explicitly re-creates each <script> element:
- For external scripts (
srcattribute): A new<script>tag with the samesrcis appended to<body>, but only if a script with thatsrcdoes not already exist (avoiding duplicate loading). - For inline scripts: A new
<script>tag with the sametextContentis appended to<body>.
The original <script> elements are removed from the embed wrapper.
The Server-Side Proxy
The proxy endpoint at /wp-json/oembed-js/v1/proxy is a critical component that bridges the gap between browser security restrictions and oEmbed providers.
Request Flow
Browser → Your WordPress Server (/wp-json/oembed-js/v1/proxy) → Provider's oEmbed Endpoint → Response flows back
Security: Allowlist Validation
To prevent your server from being used as an open proxy (which could be abused to make arbitrary web requests through your server), the proxy validates every request against your configured providers. Specifically:
- The proxy receives
endpointandurlparameters. - It reads the current plugin configuration.
- It compares the requested
endpointagainst every enabled provider's endpoint URL (normalized: trailing slashes removed, case-insensitive comparison, query parameters stripped before comparison). - If no match is found, the request is rejected with a
403 Forbiddenerror.
This means only endpoints that you have explicitly configured and enabled in the plugin can be accessed through the proxy.
Caching
The proxy sets a Cache-Control: public, max-age=3600 response header, instructing browsers and intermediate caches to cache the oEmbed response for one hour. This reduces the number of requests to provider endpoints for frequently viewed pages.
Error Handling
- If the provider returns a non-2xx HTTP status, the proxy returns the same status code with an error message.
- If the provider returns invalid JSON, a
502 Bad Gatewayerror is returned. - If the WordPress HTTP request fails (network error, timeout), a
502error is returned with the error message.
The proxy uses a 15-second timeout for requests to provider endpoints.
Configuration Storage
oEmbed JS stores all configuration in a single file: config.json, located in the plugin's root directory alongside defaults.json.
Why a JSON File Instead of the Database?
This design choice has several implications:
- Portability: The configuration can be easily backed up, version-controlled, or transferred between environments by copying a single file.
- Performance: Reading a small JSON file from disk is fast and does not require a database query.
- Simplicity: No database tables need to be created or migrated.
File Permissions
The web server process (typically running as www-data, apache, or nginx) must have write permission to the plugin directory to create and update config.json. If the file cannot be written, save operations will fail with an error message indicating the file path.
How Configuration Flows
-
Admin page load: PHP reads
config.jsonand passes it to the JavaScript admin interface viawp_localize_script()as theOEJS.configobject. -
Admin save: The JavaScript gathers all form values, constructs a JSON object, and POSTs it to
/wp-json/oembed-js/v1/config. The PHP handler writes the data toconfig.jsonand returns the saved configuration for verification. -
Frontend load: The frontend script fetches
/wp-json/oembed-js/v1/public-config, which readsconfig.jsonand returns a filtered version containing only enabled providers.
defaults.json
The defaults.json file is a read-only template containing the factory default configuration. It is used when:
- The plugin is activated for the first time and no
config.jsonexists. - You click "Reset to Defaults" in the admin interface.
- The
config.jsonfile becomes corrupted or unreadable.
This file should never be edited. It ships with the plugin and is overwritten on updates.
Resetting to Defaults
Clicking Reset to Defaults in the admin interface:
- Displays a confirmation dialog.
- Sends a POST request to
/wp-json/oembed-js/v1/reset. - The server copies the contents of
defaults.jsonoverconfig.json. - The admin page re-populates all fields with the default values.
This action cannot be undone. Any custom providers or settings modifications will be lost.
Uninstallation
When you delete the plugin through the WordPress admin (not just deactivate — actually delete):
- WordPress executes the
uninstall.phpfile. - The plugin deletes the
oejs_oembed_directorytransient from the WordPress database (the cached oEmbed directory data). - The plugin's files, including
config.json, are removed by WordPress's standard plugin deletion process.
Since the plugin stores no other data in the WordPress database (no options, no custom tables), uninstallation is clean and complete.
Note: Deactivating the plugin (without deleting) preserves all files including your config.json, so reactivating will restore your settings.
Troubleshooting
Embeds are not appearing
-
Check CSS Selectors: The most common issue. If the "CSS Selectors to Scan" field is empty, no links will be processed. Open your browser's developer tools, inspect the element containing your links, and note the CSS class of the content container. Enter it in the selectors field.
-
Verify the snippet is installed: View your page source (Ctrl+U in most browsers) and search for
oembed-js-front.js. If it is not present, the snippet has not been correctly added to your page builder. -
Check the browser console: Open the developer tools console (F12 → Console tab). The plugin logs informational messages:
"oEmbed JS: No CSS selectors configured."— The selectors field is empty."oEmbed JS: Failed to load config."— The public-config endpoint is unreachable."oEmbed JS: Invalid regex for ..."— A provider has a malformed URL pattern."oEmbed JS: Failed to fetch embed for ..."— The oEmbed request failed.
-
Check that the link is a plain anchor: The plugin only processes
<a>elements with anhrefattribute. If your page builder wraps URLs in other elements or the links are inside iframes, they will not be detected. -
Check provider is enabled: In the admin, verify the checkbox next to the provider name is checked.
"Error saving settings" message
The web server cannot write to the config.json file. Check that the plugin directory has appropriate write permissions. On most Linux servers:
chown -R www-data:www-data /path/to/wp-content/plugins/oembedjs_1.2.0/
chmod 755 /path/to/wp-content/plugins/oembedjs_1.2.0/
chmod 644 /path/to/wp-content/plugins/oembedjs_1.2.0/config.json
Instagram embeds require a Facebook App token
Instagram's oEmbed endpoint (graph.facebook.com/v18.0/instagram_oembed) requires an access_token parameter linked to a Facebook App. The pre-configured Instagram provider will not work without this token. You need to create a Facebook App, obtain an access token, and append it to the endpoint URL:
https://graph.facebook.com/v18.0/instagram_oembed?access_token=YOUR_TOKEN
Embeds appear but look broken or unstyled
Some providers (Twitter, Instagram, TikTok) rely on their own JavaScript libraries to render embeds. If your site has a Content Security Policy (CSP) that blocks scripts from third-party domains, these embeds will not render correctly. Check your browser console for CSP violation warnings.
Embeds load slowly
- Switch the loading strategy to Lazy so embeds below the fold are not loaded until needed.
- Consider using Click to load for pages with many embeds.
- The server-side proxy caches responses for 1 hour via Cache-Control headers. Your server or a CDN may provide additional caching.
Technical Reference
REST API Endpoints
All endpoints are registered under the namespace oembed-js/v1.
| Endpoint | Method | Auth Required | Description |
|---|---|---|---|
/config |
POST | Yes (manage_options) |
Save the full plugin configuration |
/reset |
POST | Yes (manage_options) |
Reset configuration to defaults |
/public-config |
GET | No | Retrieve display settings and enabled providers |
/proxy |
GET | No | Proxy oEmbed requests to provider endpoints |
/oembed-directory |
GET | Yes (manage_options) |
Fetch and transform the oembed.com directory |
File Structure
oembedjs_1.2.0/
├── oembed-js.php — Main plugin file, PHP logic, REST endpoints
├── defaults.json — Factory default configuration (read-only)
├── config.json — Active configuration (created at runtime)
├── providers.json — Local copy of the oembed.com directory
├── uninstall.php — Cleanup on plugin deletion
├── admin/
│ ├── admin-page.php — Admin page HTML template
│ ├── admin.js — Admin interface JavaScript
│ └── admin.css — Admin interface styles
└── frontend/
└── oembed-js-front.js — Frontend embedding script
Configuration Schema
The config.json (and defaults.json) file follows this structure:
{
"settings": {
"max_width": "100%",
"max_height": "",
"fixed_width": "",
"fixed_height": "",
"wrapper_class": "jsemb",
"css_selectors": ".entry-content, .post-content",
"loading_strategy": "lazy",
"responsive": true
},
"providers": [
{
"id": "youtube",
"name": "YouTube",
"url_pattern": "https?://(?:www\\.)?youtube\\.com/watch\\?v=...",
"endpoint": "https://www.youtube.com/oembed",
"regex": true,
"enabled": true,
"embed_type": "iframe",
"override": {
"max_width": "",
"max_height": "",
"fixed_width": "",
"fixed_height": "",
"wrapper_class": ""
}
}
]
}
Pre-Configured Providers
The plugin ships with the following 15 providers enabled by default:
| Provider | Endpoint | Embed Type |
|---|---|---|
| YouTube | youtube.com/oembed | iframe |
| YouTube Shorts | youtube.com/oembed | iframe |
| Vimeo | vimeo.com/api/oembed.json | iframe |
| Dailymotion | dailymotion.com/services/oembed | iframe |
| Spotify | open.spotify.com/oembed | iframe |
| SoundCloud | soundcloud.com/oembed | rich |
| Mixcloud | app.mixcloud.com/oembed/ | rich |
| X (Twitter) | publish.twitter.com/oembed | rich |
| graph.facebook.com/v18.0/instagram_oembed | rich | |
| TikTok | tiktok.com/oembed | rich |
| CodePen | codepen.io/api/oembed | rich |
| Flickr | flickr.com/services/oembed/ | photo |
| reddit.com/oembed | rich | |
| SlideShare | slideshare.net/api/oembed/2 | rich |
| Giphy | giphy.com/services/oembed | photo |
WordPress Hooks
The plugin uses the following WordPress hooks:
Browser Compatibility
The frontend script uses fetch(), document.querySelectorAll(), Element.closest(), and classList. These are supported in all modern browsers (Chrome, Firefox, Safari, Edge). Internet Explorer is not supported.
Skill: oEmbed JS Provider Configuration Assistant
Purpose: When a user wants to add a new oEmbed provider to the oEmbed JS WordPress plugin, help them determine the correct values for each field based on whatever information they provide (a URL, an iframe embed code, a provider website, an API documentation link, or just a service name).
Context: The oEmbed JS plugin allows users to configure custom oEmbed providers. Each provider has the following fields that need to be filled in:
| Field | Description | Expected Format | Required |
|---|---|---|---|
| Name | Human-readable name of the provider (e.g., "YouTube", "Spotify") | Plain text | Yes |
| URL Pattern | A regex pattern that matches URLs from this provider that should be converted to embeds | Regular expression (without delimiters), case-insensitive matching is applied automatically | Yes |
| Endpoint URL | The oEmbed API endpoint URL where the plugin sends requests to retrieve embed data | A full HTTPS URL, must return JSON | Yes |
| Regex | Whether the URL Pattern field is a regular expression | Checkbox (true/false). Almost always true. | Yes |
| Enabled | Whether this provider is active | Checkbox (true/false) | Yes |
| Embed Type | The type of embed this provider returns | One of: "iframe" (for video/audio players), "rich" (for HTML embeds like tweets, posts), or "photo" (for images) | Yes |
| Max Width (override) | Per-provider maximum width override | CSS value (e.g., "640px", "100%") or leave empty to use global setting | No |
| Max Height (override) | Per-provider maximum height override | CSS value (e.g., "480px") or leave empty to use global setting | No |
| Fixed Width (override) | Per-provider fixed width override | CSS value or leave empty | No |
| Fixed Height (override) | Per-provider fixed height override | CSS value or leave empty | No |
| Wrapper Class (override) | Per-provider CSS class override for the embed wrapper | CSS class name or leave empty to use global setting | No |
How to derive the values from user input:
-
If the user provides a URL they want to embed (e.g., "https://www.example.com/video/12345"):
- Identify the provider/service from the domain.
- Search for whether this provider has a known oEmbed endpoint. Check oembed.com/providers.json or the provider's documentation.
- Construct a regex URL pattern that matches all embeddable URLs from this provider. Use capturing groups and character classes as needed. Escape dots with
\\.in the regex. - If no oEmbed endpoint exists, inform the user that this provider does not support oEmbed and suggest alternatives (e.g., using an iframe embed directly via a custom solution, or checking if the provider offers oEmbed through a third-party service).
-
If the user provides an iframe embed code (e.g.,
<iframe src="https://player.example.com/embed/12345">):- Extract the iframe src URL to understand the embed URL structure.
- Work backwards from the embed/player URL to identify the provider.
- Look up whether the provider has an oEmbed endpoint.
- Note: The oEmbed endpoint URL is NOT the iframe src. The endpoint is an API URL that accepts a content URL and returns embed HTML (which typically contains the iframe). These are different things.
- If the provider has an oEmbed API, determine the correct content URL pattern (the URL a user would share, not the embed URL) and the API endpoint.
-
If the user provides just a service name (e.g., "Vimeo"):
- Look up the provider in the known oEmbed providers directory.
- Provide the standard configuration.
-
If the user provides a provider's API documentation link:
- Read the documentation to extract the oEmbed endpoint URL and supported URL schemes.
- Convert the URL schemes to regex patterns.
Converting oEmbed scheme patterns to regex:
Many providers document their supported URLs using wildcard patterns like https://www.example.com/video/*. Convert these to regex:
- Replace
*with[a-zA-Z0-9_-]+for path segments, or.+for broader matching. - Escape literal dots:
.becomes\\. - Use
https?://to match both HTTP and HTTPS if both are supported. - Use
(?:www\\.)?to optionally match the www subdomain if appropriate. - Combine multiple scheme patterns using
|(alternation) within a non-capturing group(?:...|...)if needed, or use a single broader pattern if all schemes can be covered.
Choosing the Embed Type:
- Use "iframe" for video and audio players (YouTube, Vimeo, Spotify, Dailymotion, etc.) — services whose oEmbed response contains an
<iframe>in thehtmlfield. - Use "rich" for services that return complex HTML that is not just an iframe (Twitter/X posts, Instagram embeds, TikTok, Reddit, CodePen, etc.) — these often include
<blockquote>elements with<script>tags. - Use "photo" for services that return image data (Flickr photos, Giphy, etc.) — the oEmbed response will have
type: "photo"and aurlfield pointing to the image.
Response format: Present the recommended configuration in a clear table. Example:
For a user who says: "I want to embed Vimeo videos"
| Field | Value |
|---|---|
| Name | Vimeo |
| URL Pattern | https?://(?:www\.)?vimeo\.com/(\d+) |
| Endpoint URL | https://vimeo.com/api/oembed.json |
| Regex | ✅ Checked |
| Enabled | ✅ Checked |
| Embed Type | iframe |
| Max Width | (leave empty — uses global setting) |
| Max Height | (leave empty — uses global setting) |
| Fixed Width | (leave empty) |
| Fixed Height | (leave empty) |
| Wrapper Class | (leave empty — uses global setting) |
Additional guidance to provide the user:
- After adding the provider, remind the user to click "Save Settings" in the plugin admin page.
- If the provider requires authentication (e.g., Instagram requires a Facebook App access token), warn the user and explain that the oEmbed endpoint may need an
access_tokenparameter appended. The endpoint field can include query parameters, e.g.,https://graph.facebook.com/v18.0/instagram_oembed?access_token=YOUR_TOKEN. - If the provider is already in the plugin's built-in defaults or can be imported from the oEmbed Directory browser (the "Browse oEmbed Directory" button in the admin), suggest that simpler path instead of manual configuration.
- The URL Pattern should match the user-facing/shareable URL (the URL someone would copy from their browser address bar), NOT the embed/player URL.
- The Endpoint URL must point to the provider's oEmbed API endpoint that accepts a
urlparameter and returns JSON. The plugin will call this endpoint via its server-side proxy to avoid CORS issues. - Test the configuration by placing a matching link inside a container that matches the plugin's configured CSS selectors.
When oEmbed is not available:
If the provider does not offer an oEmbed endpoint, inform the user clearly and suggest:
- Check if a third-party oEmbed proxy service (like Iframely or Embedly) supports the provider.
- The provider may support oEmbed discovery — look for
<link type="application/json+oembed">in the HTML head of content pages. - If none of these work, this plugin cannot embed content from that provider, as it relies on the oEmbed protocol.
BricksBuilder Plugins
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)
- Download
bricks-child.zipfrom your Bricks account (my.bricksbuilder.io). - Appearance → Themes → Add New → Upload, then activate Bricks Child Theme.
- The child theme already includes a demo custom element and a working
functions.phpto 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->settingsis an associative array keyed by your control IDs (content,type). This is how you read what the user entered._rootis 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_selectoryou set) — so styles "just work" if_rootattributes 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',
],
],
],
],
];
}
nameis a Bricks element name (block,heading,button, etc.).childrenrecursively nests more elements.settingspre‑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/renderandbricks/frontend/render_element— filter the rendered HTML.bricks/element/render_attributes— modify attributes.bricks/posts/query_vars(andbricks/{object_type}s/query_vars) — alter the query that a query‑loop element runs.
10. Creating Custom Dynamic Data Tags
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,$scriptsfunction 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(): useesc_html(),esc_attr(),esc_url(), andwp_kses_post()as appropriate. Never echo raw$this->settingsvalues 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, thenwp_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
initat priority 11 (after Bricks loads its own). - Declare
Requires PHPand 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
- Enable debugging in
wp-config.php:define( 'WP_DEBUG', true ); define( 'WP_DEBUG_LOG', true ); define( 'SCRIPT_DEBUG', true ); // load unminified assets - Verify registration: open the builder, search your element by label/keywords in the elements panel.
- Check both contexts: confirm the element renders correctly in the canvas and on the live frontend (they can differ).
- Inspect the wrapper: confirm your root tag has the
.brxe-{name}class and unique ID — if not, you likely forgotrender_attributes( '_root' ). - Watch the console for JS errors when your
$scriptsfunction runs. - 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.