BlueTheme Docs

Builder internals

How the builder stores pages, renders them and loads element modules. For a step-by-step element, see adding an element.

Where the data lives

Post meta Content
_bluetheme_builder The page document as JSON (below). Kept with every revision.
_bluetheme_builder_active true when the page is shown with the builder.
_bluetheme_builder_css Hash of the page's generated CSS file.

Builder pages get wp-content/uploads/bluetheme/css/post-{id}.css. The file is rebuilt when the document is saved, and again on view when it is missing or out of date.

Document

{
  "version": 1,
  "sections": [
    {
      "id": "a1b2c3d4", "type": "section", "settings": { },
      "children": [
        {
          "id": "e5f6g7h8", "type": "wrap", "settings": { "size": { "desktop": "1/2", "mobile": "1/1" } },
          "children": [
            { "id": "i9j0k1l2", "type": "element", "element": "heading", "settings": { "title": "Hello" } }
          ]
        }
      ]
    }
  ]
}
  • Sections hold wraps. Wraps hold elements and other wraps, nested up to 4 levels deep.
  • Ids are 4–16 lowercase letters or digits, unique within the page.
  • settings stores only values that differ from the defaults. Per-device values are maps such as { "desktop": 40, "mobile": 24 }, and each smaller device inherits from the next larger one.
  • Wrap sizes are 1/6 … 1/1 or a percentage such as "37.5%". On mobile, wraps are full width unless a mobile size is set.
  • Every save goes through BlueTheme\Builder\Data\Sanitizer. It rebuilds the tree, keeps only known settings, and cleans each value with the Theme Options field sanitizer. HTML is filtered unless the user has unfiltered_html.
  • When the format changes, raise Migrations::VERSION and add an upgrade step.

Element modules

An element is a folder, found in bluetheme/elements/, in the child theme's elements/ (which can override a theme element of the same name), or in any folder added with the bluetheme_builder_element_dirs filter.

elements/my-element/
  config.json   required — title, description, category, icon, keywords, summary, fields
  render.php    required — prints the element's inner HTML
  style.css     optional — loaded only on pages that use the element
  script.js     optional — same rule; loaded deferred, in the footer

config.json

{
  "title": "Heading",
  "description": "A heading (H1–H6) or a line of text, with an optional link.",
  "category": "typography",
  "icon": "bi:type-h1",
  "keywords": [ "title" ],
  "summary": "title",
  "textdomain": "bluetheme",
  "fields": [
    { "type": "text", "id": "title", "label": "Title", "default": "Heading", "translate_default": true },
    {
      "type": "number", "id": "size", "label": "Font size", "default": "", "responsive": true,
      "unit": "px", "tab": "style",
      "css": { "selector": "{{WRAPPER}} .bt-heading", "property": "font-size", "unit": "px" }
    }
  ]
}
  • description: one sentence, shown as the tooltip in the Elements tab and in the elements reference.
  • category: one of typography, media, boxes, interactive, sliders, dynamic, post, woocommerce, product, header, other.
  • icon: any icon reference the Theme Options icon picker accepts, as pack:name.
  • summary: the field whose text the backend builder shows on the element's card.
  • fields: use the Theme Options field types: text, textarea, html, url, number, select, switch, color, image, icon, dimensions, background, code and the others.
    • Extra keys:
      • tab: content, style or advanced.
      • responsive: true for per-device values.
      • show_if: shows the field only under a condition.
      • translate_default: the default text is translated.
    • Shared fields: every element also gets padding, margin, background, text color, radius, CSS id and classes, hide on device, and custom CSS.
  • css: turns a field into CSS. It can be one rule or a list of rules.
    • Each rule has a selector (where {{WRAPPER}} stands for the element's own class), plus either a property or a CSS custom property in var.
    • Optional keys: a unit, and a map that translates stored values into CSS values.
    • Output: CSS is produced only for values that differ from the defaults, so put the defaults in style.css.
  • Translations: labels are written in English. pnpm i18n runs tools/element-strings.mjs, which lists them for translators. Third-party elements set their own textdomain.

Libraries (requires)

An element that needs a shared library lists it in config.json:

"requires": [ "leaflet" ]

The library loads only on pages that use the element, before the element's script.js. Every library is bundled with the theme (pnpm vendor copies them into assets/vendor/); nothing loads from a CDN.

Elements for a plugin (needs)

An element that only works with a plugin names it, and is left out of the builder (and not rendered) while that plugin is off:

"needs": "woocommerce"

woocommerce is the one key the theme knows; the bluetheme_builder_element_plugin_active filter (active, key) answers for others.

Reusing another element's assets (uses)

An element that reuses the stylesheet and script of another element names it in config.json, for example Archive Posts, which uses the Blog post cards:

"uses": [ "blog" ]
Name Library Used by
lightbox The theme's own lightbox (src/frontend/lightbox.js) Image, Image Gallery, Zoom Box
countup The theme's count-up and progress script (src/frontend/countup.js) Counter, Quick Fact, Progress Bars
leaflet Leaflet 1.9 Map Advanced
lottie lottie-web (light build) Lottie
chartjs Chart.js 4 Chart
swiper Swiper (through slider)
slider The theme's slider set-up (src/frontend/slider.js); loads swiper first All slider elements

Add more with the bluetheme_builder_vendors filter.

Lightbox: any link with data-bt-lightbox="group" opens its href in the lightbox. Links with the same group form a gallery. The caption comes from data-bt-caption, else from the image's alt text.

Repeaters and galleries

  • Repeater fields: their fields and default rows are translated like other fields.
    • Default rows need a fixed _id, so saved settings that equal the defaults are recognized and dropped.
    • Mark row fields with translate_default to translate the texts of the default rows.
  • gallery field: stores a list of { id, url } images.
  • Site-dependent choices:
    • A select field with "choices_from": "forms" gets its choices from the bluetheme_builder_choices filter.
    • Built in: forms, the Contact Form 7 and WPForms forms, as cf7:ID and wpforms:ID.
  • Translatable defaults: defaults marked translate_default are listed for translators even when they have no letters, such as a currency sign.

Element scripts

  • Element scripts are plain JavaScript; they are not bundled, and pnpm lint:js checks them.
  • After the live builder replaces a section, it fires bluetheme:render. Scripts that set things up per element should listen for it.
  • Skip clicks that would leave the page while body.bt-canvas is set (the live builder canvas).

Helpers for render.php

  • Elements\Parts: escaped markup for the pieces most elements repeat:
    • images from an image field;
    • headings with an allowed tag, linked or not;
    • textarea text, links and theme buttons;
    • tel: links from phone numbers typed with Persian digits;
    • count-up numbers (Parts::count), and number or label lists separated by commas, Persian commas included (Parts::numbers, Parts::labels);
    • the live-builder hint (Parts::hint).
  • Elements\Embed: reads YouTube, Vimeo and Aparat links or ids and builds player addresses (YouTube uses its no-cookie domain).
  • Elements\Maps:
    • the tile server, attribution and click-to-load settings from Theme Options → Global → Addons;
    • a coordinate parser that accepts Persian digits.
  • Renderer::$editing: true inside the live builder. Elements use it to show a hint instead of nothing when required content is missing.

render.php

These variables are available:

  • $settings: the saved values merged over the defaults.
  • $node: the element's id, type and element name.
  • $element: the BlueTheme\Builder\Elements\Element object.

Print only the element's inner markup and escape everything. The renderer wraps it in <div class="bt-el bt-el-{name} bt-n-{id}">.

REST API (bluetheme/v1, needs edit_post)

Route Purpose
GET /builder/{id} Document and active flag
POST /builder/{id} Save { document, active } and record a revision
POST /builder/{id}/render HTML and CSS of one node (previews)
GET /builder/{id}/revisions Revisions that contain builder data
GET /builder/{id}/revisions/{rev} One revision's document

The block editor and the classic editor save through the post itself; they don't use these routes.

Shared Style and Advanced settings

Every section, wrap and element gets these settings (StyleFields, AdvancedFields):

  • Style: typography, text color and text color on hover, background and background color on hover, padding and margin (per device), border style, width and color (with a hover color), corner radius (per device), and box shadow (with a hover shadow).
  • Advanced: CSS id and classes, an entrance animation with delay and duration, sticky (top or bottom, with an offset), z-index, hide on device, display conditions, and custom CSS.
  • Display conditions: show to everyone, only to logged-in users (optionally only some roles), or only to visitors who are not logged in; and show from / until dates in the site's time zone. Conditions are checked when the page renders, so a page cache must not serve cached pages to logged-in users.

An element can point a shared style at an inner part with style_selectors in config.json:

"style_selectors": {
  "typography": "{{WRAPPER}} .bt-heading",
  "text_color": "{{WRAPPER}} .bt-heading",
  "hover_color": "{{WRAPPER}}:hover .bt-heading"
}

Section overlay, video, dividers and slider

Sections have these settings too (Data\SectionFields, rendered by Render\SectionExtras):

  • Overlay (Style): a color, gradient or image over the background and the video, under the content, with its own opacity (default 60%).
  • Background video (Style):
    • Sources: a video file from the Media Library, or an Aparat, YouTube or Vimeo link. It plays muted, in a loop, without controls.
    • The poster image shows until the video plays. It is also all that shows:
      • on phones, unless Play the video on phones is on;
      • for visitors who prefer less motion;
      • for embedded players while the cookie notice waits for consent, when Theme Options → GDPR blocks external embeds. The video starts on the next page view after consent.
    • The video is added by a script when the section comes near the screen.
    • Whether Aparat honors autoplay and mute inside a frame is up to Aparat's player.
  • Shape dividers (Style): top and bottom, in ten shapes (Render\Dividers), each with a color (usually the next section's background), a height per device and a flip.
    • The shapes are drawn for the bottom edge; the top one is turned upside down.
    • Right-to-left pages mirror them, so a shape leans the same way relative to the reading direction.
  • Slider (Content): the section's wraps become Swiper slides, with slides at once (per device), slide or fade, arrows, position indicator, autoplay with a pause button, and loop. The space between slides is the section's Space between wraps.
    • In the live builder the wraps stay side by side, marked with ⇆, so they can still be edited.

Layers inside the section, in order: .bt-section__video, .bt-section__overlay, the top divider, .bt-section__inner (the content, above the others), and the bottom divider.

Internal modules: the video and slider scripts load like element assets. Tree::element_names() lists the internal section-video and section-slider modules when a section needs them, so every page, template and preview that renders such a section loads them. Modules with "internal": true in config.json are not offered in the Elements panel.

Pre-built sections

The builder's Pre-built sections tab offers 30 ready-made sections in ten categories (three each): Hero, About, Services, Features, Team, Pricing, Testimonials, Call to action, Contact, and Numbers & clients.

  • Inserting: clicking a card adds an editable copy at the end of the page; Undo removes it. The tab has category chips and a search box (by section name).
  • Previews: each card shows the real section rendered with the site's styles at desktop width, scaled down. It uses the preset preview (?bt_preset=section:{id}, for users who can publish pages), loaded when the card scrolls into view.
  • Following the site:
    • Colors refer to the palette (palette:primary, var(--bt-palette-secondary) in gradients), so sections take Theme Options → Colors.
    • Images are the theme's own illustrations in assets/images/library/.
    • All texts are translatable. The sample price currency is translated too (in Persian: «هزار تومان»).
  • Code:
    • Library\Library: the registry and routes (GET bluetheme/v1/library, GET bluetheme/v1/library/{id}, needs edit_posts).
    • Library\Kit: the shared building blocks.
    • Library\Sections\*: one class per category.
    • Plugins and child themes add sections with the bluetheme_library_sections filter (id => [ label, category, section node ]).
  • Dark sections:
    • Kit::dark() sets white text and lightens --bt-color-muted. It also gives slider arrows a see-through background.
    • Sections with light cards on a dark background (pricing) color only their intro.
    • Eyebrow labels use the accent color on dark backgrounds.
  • Readable widths: centered intros keep their content at 800 px with selector > .bt-wrap__inner. The wrap itself stays full width, so the next wrap starts a new line.
  • bt-buttons-inline: this CSS class on a wrap (Advanced → CSS classes) puts its buttons side by side. The hero sections use it, and it works on any wrap.
  • No letter spacing on labels: it would break the joined letters of Persian text.

Live builder

  • Where it is: admin.php?page=bluebuilder&post={id}. It's linked from the block editor ("Edit live"), the backend builder, the posts list and the admin bar.
  • The canvas: the real page in an iframe, in canvas mode (?bluebuilder_canvas=1&_wpnonce=…, only for users who can edit the post).
    • Nodes carry data-bt-id and data-bt-type.
    • Display conditions are ignored, so editors see every item.
    • Every element's assets are loaded.
  • Rendering changes: after each change, the builder sends only the sections that changed to POST /builder/{id}/render-sections and swaps the returned HTML and CSS into the canvas.
  • Element scripts: after a section is replaced, the canvas dispatches a bluetheme:render event on it. Element scripts should listen for it and re-initialize, for example sliders.
  • Saving:
    • Update saves through POST /builder/{id} and records a revision.
    • Autosave runs every 60 seconds while there are unsaved changes, into the user's WordPress autosave revision.
    • Preview autosaves, then opens ?bluebuilder_preview=1&_wpnonce=…, which shows that autosave to the current user only.

Sliders and post types

  • Slider elements: they use Elements\Slider. In render.php, call open( $settings ), then slide( $html ) for each slide, then close( $settings ).
    • Shared settings: per_view and gap (per device), arrows, dots, autoplay, delay, loop, speed and effect.
    • Breakpoints: per-device values become Swiper breakpoints built from the theme's device breakpoints.
    • Pause button: autoplay always comes with one.
    • Reduced motion: for visitors who ask for less motion, autoplay and sliding are off.
  • Post types: Portfolio (portfolio), Clients (bt_client), Testimonials (bt_testimonial), Offers (bt_offer) and Slides (bt_slide), each with a category taxonomy.
    • Extra fields: the extra fields are post meta named _bluetheme_{field} (PostTypes\Fields). They are edited in a "Details" box.
    • Turning them off: each type can be turned off in Theme Options → Global → Performance → "Disable features".
  • Items from either source: Elements\Items::get( $kind, $settings ) returns the same item shape whether the element reads posts (source = posts, with category, count, orderby) or items entered in the element (source = manual).
  • Cards: Elements\Cards prints post and project cards for sliders and lists.

Lists

  • Paginated list elements (Blog, Portfolio) use Lists\Pager.
    • Each list reads its page from its own query argument, bt_p_{node id}, so several lists on one page are independent.
    • The controls are plain links, so they work without JavaScript.
  • The lists library (src/frontend/lists.js) loads the next page in place for "Load more" and infinite scroll. It filters items by data-terms, and swaps the list for data-ajax filter links (portfolio archive).
  • Mark-up a list needs: a wrapper with data-bt-list="{key}", the items inside [data-bt-list-items], and the parts printed by Pager::controls() and Pager::status().
  • Portfolio grids: Portfolio\Grid prints them, for both the Portfolio element and the portfolio archive. Their styles are in the main stylesheet (_portfolio.scss).

Templates

  • Post type and edit screen: templates are posts of type bt_template. They are built with BlueBuilder; "Edit" in the Templates list opens the live builder.
  • Types: stored in _bluetheme_template_type and listed in Templates\Types. The bluetheme_template_types filter adds types.
  • Front address (/bt-template/…): it exists only for the builder canvas. Visitors get a 404, and search engines a noindex.
  • Display conditions:
    • Stored in _bluetheme_conditions (JSON) and _bluetheme_priority.
    • Rules and specificity are documented in Templates\Conditions.
    • Templates\Resolver::get( $type ) picks the template for the current request. The order is: the template's own address, then the page's own choice (_bluetheme_template_{type}: an id, or -1 for none), then the best match. The bluetheme_template filter changes the result.
  • Locations:
    • bluetheme_location( 'header' | 'footer' ) prints Elementor Pro's location or the matching template.
    • Templates\Locations::html( $id ) prints any template. It adds editing markers only for the template open in the builder, and it guards against a template embedding itself.
  • Global sections:
    • The Template element shows a Section template.
    • The templates a page embeds are queued with the page's assets, through the bluetheme_builder_enqueued action.
  • REST (bluetheme/v1/templates…): list, create ("Save as template"), a template's layout, display settings, and condition rules and search.
  • Import and export: Templates\Transfer reads and writes .json files. Imported layouts are cleaned with the importing user's permissions.

Header templates

  • Rows and zones: a header is ordinary sections. A section's "Wrap layout" set to zones (row_layout) puts the first wrap at the start, the last at the end and a middle one in the center, with the zone's elements side by side. Rows for one device use "Hide on" (hide_on); "Hide this row when the header sticks" (sticky_hide) suits top bars.
  • Starter layout: a new, empty header gets Templates\Starters::document( 'header' ): a top bar and a main row for desktop and laptop, and a logo, search and burger row for tablet and mobile.
  • Behavior settings: stored in _bluetheme_header (JSON) and documented in Templates\Header. They cover sticky (off, sticky, smart), the offset, shrink, shadow, sticky colors, and transparent with its text color. They are edited in the builder's template settings tab and saved through the template settings REST route. Header::attributes() turns them into classes and data-bt-sticky* attributes for src/frontend/header.js.
  • Header elements (category header): Logo, Menu, Burger Menu, Search, Cart, Account, Wishlist, Language Switcher, Social Icons, Header Text, Copyright, Dark Mode Toggle. The burger and search buttons open the shared #bt-menu-panel and #bt-search-panel, printed once by Header::panels(). Parts::icon_link() renders the icon links (cart, account, wishlist).
  • Ready-made templates: Templates\Presets\Headers (16) and Templates\Presets\Footers (11) write builder documents with the helpers in Presets\Build (row(), section(), wrap(), el(), mobile_row() and so on). Presets\Presets lists them by type, with gallery categories, and the bluetheme_template_presets filter adds or removes presets. A header preset may carry header settings (side layout, transparent).
  • Gallery: the "Add template" page shows the gallery of the picked type (Presets\Gallery, src/admin/templates.js). Each card loads a live preview, /?bt_preset=header:classic (Presets\Preview, for users who can publish pages), in a scaled frame once the card is on screen. "Blank" starts with an empty template; new headers and footers made elsewhere start from the default preset (Presets::DEFAULTS, through Starters).
  • Footer settings: stored in _bluetheme_footer (Templates\Footer). The style setting can be inherit (Theme Options → Footer), default, sticky or reveal, and it reaches the existing footer body classes through the bluetheme_footer_style filter. Footer templates also print the back-to-top button.
  • Side header: the header setting layout can be top, side-start or side-end, with a side_width. From 992px up the header is a fixed vertical bar and the page is padded on that side; its last section is pushed to the bottom. Below 992px it is an ordinary top header.
  • Row text color: a section's or wrap's text color also sets --bt-color-heading and --bt-color-menu-link, so headings and menus follow it. Footer templates color header-text links with the row's text color too.

Mega menus and side panels

  • Mega menu: a top-level menu item can open a Mega menu template. The template is picked under Appearance → Menus → the item → "Mega menu", stored in the item meta _bluetheme_megamenu. In horizontal desktop menus, Template\MenuWalker prints the template's panel (MegaMenu::panel()) in place of the item's submenu, with the same toggle button, so src/frontend/nav.js opens it by click, hover, keyboard and Escape like a submenu. Vertical menus and mobile menus keep the normal submenu.
  • Panel width: the template setting width (meta _bluetheme_megamenu on the template) can be inherit (Theme Options → Menu → Mega menu width), full, container, auto or fixed (fixed_width). Full and container panels line up with the header row: the item and the menu's builder wrappers stop being positioned (src/scss/_megamenu.scss). Fixed and auto panels hang from the item and flip at the screen edge. Header row colors are reset inside the panel (--bt-root-heading, --bt-root-menu-link).
  • Side panels: a Burger Menu element's "Opens" setting picks the mobile menu or a Side panel template. Offcanvas::request() queues the template, and the panel is printed once, in wp_footer, as a Bootstrap offcanvas with side (start or end) and width (meta _bluetheme_offcanvas).
  • Late styles: mega menu and side panel templates are queued while the page body is rendered, so their CSS loads in the footer. The panels are closed until opened.
  • Settings: each template type's own settings travel through the template settings REST route under the type's key (Rest::BEHAVIOR), and the builder's Template settings tab shows them, with a save button, for types without display conditions too.
  • Starters: new mega menu templates start with three link columns and a call-to-action box. New side panels start with the logo, a vertical menu, contacts and social icons.

Popups

  • Which popups print: every published popup whose display conditions match the page (Popups::matching(), all of them, not just the best match), plus any popup a Popup Trigger element asks for (Popups::request()). They print in wp_footer as <dialog class="bt-popup">, never in the live builder canvas or on a template's own address. Their styles load in the head; requested popups load theirs late.
  • Settings: stored in _bluetheme_popup and documented in Templates\Popups. They cover the trigger (load, delay, scroll, exit intent, click only), the frequency (always, once per visit, every N days, once), position, animation, width, the overlay (modal or not), its color, closing on an outside click, the close button, and devices it never opens on by itself. They are edited in the builder's Template settings tab.
  • Script: src/frontend/popups.js (its own bundle, loaded only on pages with popups) opens and closes the dialogs.
    • Modal popups use showModal(), so focus stays inside. Popups without an overlay use show(), so the page stays usable.
    • Frequency is kept in sessionStorage or localStorage, not in cookies.
    • Popups open from [data-bt-popup-open="bt-popup-ID"] and from links to #bt-popup-ID. They close from [data-bt-popup-close], from links to #bt-popup-close, and with Escape.
  • Popup Trigger element: a button that opens a chosen popup (and makes it print on that page), or closes the popup it is in.
  • Canvas: CSS that template settings add through bluetheme_builder_post_css is now printed in the live builder canvas too, for example the popup width and the sticky header colors.

Single, archive and 404 templates

  • Where they apply: Single post, Single project, Blog archive (the blog page, categories, tags, authors and dates) and 404 templates replace the theme layout where their display conditions match (Templates\Dynamic). The template_include filter switches to template-dynamic.php, which prints a single template once per post in the loop, and the others once.
  • Elements: the "Post & archive" category has Post Title, Post Content, Post Excerpt, Featured Image, Post Meta, Post Terms, Author Box, Post Navigation, Related Posts, Comments, Share Buttons, Breadcrumbs, Archive Posts, Archive Description, Project Media and Project Details. They read the post in context or the main query, reuse the theme's own parts (author box, related posts, share, breadcrumbs, portfolio details), and show a builder hint when there is nothing to show.
  • Preview in the builder: while a template is edited, Dynamic::enter() swaps in a sample: the "Preview with" post (or the latest post of the type) for single templates, and the latest posts as the main query for archives. The canvas page (single-bt_template.php) and the builder's REST renders, through the bluetheme_builder_setup_post action, both use it.
  • Settings: stored in _bluetheme_dynamic. preview is the sample post, and subheader keeps the theme's page title bar, which is hidden above these templates by default.
  • Empty elements: an element whose render prints nothing now leaves no wrapper on the site. The builder keeps the wrapper so the element can still be selected.
  • Starters: each type starts with a full layout. Single post: categories, title, meta, image, content, tags, share, author, navigation, related posts, comments. Single project: media beside a sticky details column. Blog archive: title bar and post grid. 404: a large 404, title, search and home button.
  • Shop: Single product and Shop archive templates work the same way; see woocommerce.md. The bluetheme_dynamic_before and bluetheme_dynamic_after actions (template type as argument) run around each template, in template-dynamic.php and on the canvas page; the shop uses them to wrap a product in its div.product.