Building Custom Blocks and Templates for MTBuilder

Last updated on Jun 6, 2026 4:36 PM

Core Architecture and Lifecycle

MTBuilder runs on a high-performance Virtual DOM state engine that separates design-time dragging configurations from raw public-facing rendering.

When a user hits Save, two data models are committed to the MySQL database:

  1. Virtual DOM JSON Schema (mtbuilder_data): Stored inside the page/post metadata table (tblblog_page_meta). This preserves the precise nested structure, configurations, and class mappings of all blocks, allowing the engine to reload the visual layout in subsequent editing sessions.
  2. Clean HTML Caching (content): Compiled via a clean parser (generateCleanHtml()), stripping all editor wrappers, dragging overlays, and admin context-menus. The raw production HTML is cached directly into the core tblblog_pages content field, enabling incredibly fast frontend page loads with zero overhead.

---

JS Block Registration API

Developers can extend the MTBuilder canvas by registering custom blocks from independent modules using the JS core API MTBuilder.registerBlock(blockType, config).

Code Example: Creating a "CRM Lead Form" Block

Create a custom JavaScript file (e.g., crm-lead-block.js) inside your plugin assets:

MTBuilder.registerBlock('crm_lead_form', {
    name: 'CRM Lead Form',
    icon: 'fa fa-user-plus',
    group: 'crm_integrations',
    acceptChildren: false,
    
    // Schema definitions for the Sidebar settings panel
    settingsSchema: [
        { 
            key: 'formId', 
            label: 'CRM Form', 
            type: 'select', 
            options: { '1': 'Standard Subscription Form', '2': 'Enterprise Contact Form' }, 
            default: '1' 
        },
        { 
            key: 'bgColor', 
            label: 'Background color', 
            type: 'color', 
            default: '#f8fafc' 
        },
        { 
            key: 'buttonText', 
            label: 'Button label', 
            type: 'text', 
            default: 'Submit Request' 
        }
    ],

    // Render hook for both Editor canvas and Public frontend
    render: function(node, renderChild, isFrontend) {
        const el = document.createElement('div');
        const formId = node.settings.formId || '1';
        const bgColor = node.settings.bgColor || '#f8fafc';
        const btnText = node.settings.buttonText || 'Submit Request';
        
        el.className = 'tw-rounded-lg tw-shadow-sm tw-p-6 tw-w-full';
        el.style.backgroundColor = bgColor;
        
        if (isFrontend) {
            // Frontend actual view: Render the real Perfex CRM web-to-lead form
            el.innerHTML = `
                <div class="perfex-crm-lead-form">
                    <iframe src="/admin/leads/form/${formId}" width="100%" height="450px" frameborder="0"></iframe>
                </div>
            `;
        } else {
            // Editor canvas: Render a light interactive mock preview
            el.innerHTML = `
                <div class="tw-border-2 tw-border-dashed tw-border-slate-200 tw-rounded tw-p-8 tw-text-center">
                    <i class="fa fa-user-plus fa-3x tw-text-indigo-500 tw-mb-2"></i>
                    <h4 class="tw-text-sm tw-font-semibold tw-text-slate-800">Perfex CRM Lead Form</h4>
                    <p class="tw-text-xs tw-text-slate-400 tw-mt-1">Active Form ID: ${formId}</p>
                    <button class="btn btn-primary tw-mt-4">${btnText}</button>
                </div>
            `;
        }
        
        return el;
    }
});

Loading the Custom Block Script in Admin Editor

After registering your block module, enqueue it only on MTBuilder editor routes:

hooks()->add_action('app_admin_footer', function() {
    $CI = &get_instance();
    $uri = $CI->uri->uri_string();

    if (strpos($uri, 'polycms/mtbuilder/editor') !== false) {
        echo '<script src="' . polycms_asset_url('plugins/your-plugin/assets/js/crm-lead-block.js') . '"></script>';
    }
});

---

Integration Flow with Theme and Plugin Scaffolds

When adopting MTBuilder in real projects, keep this integration order:

  1. Start from a theme scaffold to define global layout rules, typography, and full-width landing templates.
  2. Use a plugin scaffold to register custom MTBuilder blocks and optional third-party integrations.
  3. Build pages with MTBuilder Template Parts and your custom blocks for fast iteration.

This approach lets you reuse predesigned assets while still supporting project-specific UI blocks and data behaviors.

Quick references:

---

Developer Hooks & Filters Reference

MTBuilder features a highly extensible event-driven architecture built on top of the PolyCMS Hooks & Filters API. This allows developers to intercept visual layouts, append custom blocks, inject tracking scripts, or dynamic assets seamlessly.

1. PHP Filter Hooks (Data Transformation)

PHP Filters are used to intercept and modify content before it is saved to the database or rendered to the frontend.

polycms_post_content

  • Trigger: Fired immediately before a post or page is rendered on the public-facing frontend.
  • Purpose: Filters the compiled visual layout HTML. MTBuilder uses this filter to inject responsive grid wrappers and block-specific styling.
  • Developer Use Case: Developers can hook into this to dynamically modify elements, inject ad slots, or run custom search-and-replace scripts on the visual canvas output.
  • Code Example:
  hooks()->add_filter('polycms_post_content', function($content, $post) {
      // Append a custom call-to-action banner to pages built with MTBuilder
      $is_builder_page = get_post_meta($post->id, 'mtbuilder_data', true);
      if (!empty($is_builder_page)) {
          $content .= '
          <div class="custom-campaign-banner text-center my-5 p-4 bg-light rounded">
              <h3>Special Limited Offer!</h3>
              <p>Sign up today and get 20% off all subscriptions.</p>
              <a href="/register" class="btn btn-primary">Claim Coupon</a>
          </div>';
      }
      return $content;
  }, 15, 2);

polycms_the_content

  • Trigger: Fired before the main page/post content is output by the theme template.
  • Purpose: PolyCMS's native content filter. MTBuilder hooks into this with priority 10 to safely process and expand nested shortcodes or visual blocks, bypassing classic CRM text-processing filters that might strip clean HTML attributes.

---

2. PHP Action Hooks (Event Execution)

PHP Actions are triggered at specific execution points in the CRM lifecycle, enabling plugins to perform background routines or output headers/footers.

polycms_head

  • Trigger: Inside the <head> tag of the active theme frontend.
  • Purpose: Perfect for enqueuing frontend stylesheets or dynamic webfonts. MTBuilder utilizes this to load mtbuilder-frontend.css styles so that buttons, grids, and accordions render correctly for public visitors.

polycms_plugin_activated

  • Trigger: Fired when a PolyCMS plugin is toggled to "Active".
  • Purpose: MTBuilder intercepts this event to scan active databases and seed the 6 Premium Pre-seeded Templates and custom layouts into the tblblog_templates table.
  • Code Example:
  hooks()->add_action('polycms_plugin_activated', function($plugin_file) {
      if (strpos($plugin_file, 'mtbuilder.php') !== false) {
          // Trigger custom table synchronization or directory setup
          require_once(module_dir_path('polycms', 'plugins/mtbuilder/includes/seeder.php'));
          MTBuilder_Preset_Seeder::run();
      }
  });

polycms_plugin_deactivated

  • Trigger: Fired when a PolyCMS plugin is deactivated.
  • Purpose: Useful for garbage collection or cleanup actions (e.g. flushing transient caches).

app_admin_footer

  • Trigger: Fired in the Perfex CRM administrative footer panel.
  • Purpose: MTBuilder utilizes this action to dynamically load the main visual workspace Javascript controller builder.js and active setting schemas, strictly gating the injection to active editor views to avoid bloat across normal CRM screens.
  • Code Example:
  hooks()->add_action('app_admin_footer', function() {
      $CI = &get_instance();
      $uri = $CI->uri->uri_string();
      
      // Inject the visual editor script only during active MTBuilder editing sessions
      if (strpos($uri, 'polycms/mtbuilder/editor') !== false) {
          echo '<script src="' . polycms_asset_url('plugins/mtbuilder/assets/js/builder.js') . '"></script>';
      }
  });

---

3. JavaScript Filters (Workspace Customization)

JavaScript filters run inside the editor workspace to let plugins dynamically reconfigure sidebar sections, extend menus, or modify blocks at design time.

sidebar_groups

  • Trigger: Fired during sidebar tabs initialization inside the MTBuilder DOM engine.
  • Purpose: Allows developer plugins to register entirely new categories under the Blocks/Layouts directory.
  • Code Example:
  MTBuilder.addFilter('sidebar_groups', function(groups) {
      // Add a custom category group inside the dragging toolbar
      groups.push({
          id: 'crm_integrations',
          name: 'CRM Integrations',
          icon: 'fa fa-handshake-o'
      });
      return groups;
  });

sidebar_blocks

  • Trigger: Fired when assembling the registry of draggable elements inside the left editor panel.
  • Purpose: Developers can filter the registered blocks array to append custom elements, override styling schemas, or disable core blocks for specific user roles.
  • Code Example:
  MTBuilder.addFilter('sidebar_blocks', function(registry) {
      // Customize the standard Icon element settings inside the editor workspace
      if (registry['icon']) {
          registry['icon'].settingsSchema.push({
              key: 'customClass',
              label: 'Custom CSS Class',
              type: 'text',
              default: ''
          });
      }
      return registry;
  });

---

Styling Isolation and Link Protection

To prevent active theme global links and visited link states (a:visited) from corrupting custom button contrast and backgrounds, MTBuilder features a strict Style Isolation Engine:

  • Visited Link Isolation: Explicit CSS declarations inside mtbuilder-frontend.css lock down text and background settings under primary action button classes using !important flags:
  a.mtb-button-primary,
  a.mtb-button-primary:visited,
  a.mtb-button-primary:hover {
      color: var(--btn-color) !important;
      background-color: var(--btn-bg) !important;
      text-decoration: none !important;
  }
  • Dynamic Inline Style Injection: When compiling custom buttons, the rendering JS dynamically injects critical dimensions, borders, and color properties using inline style attributes with priority flags, successfully bypassing theme default overrides in all viewports.

---

Webpack Asset Compilation Pipeline

PolyCMS uses a unified Webpack minification pipeline to compile, compress, and serve assets. The build configuration in MTBuilder automatically compresses script resources and minifies stylesheet components using Terser and Clean-CSS respectively.

Webpack Build Commands

If you update CSS or JS components in your plugin's development /assets directory, recompile them via:

cd modules/polycms/plugins/mtbuilder
npm install
npm run build

Loading Compiled Assets Dynamically

To maintain speed and avoid caching lag, always load assets using the core PolyCMS dynamic redirector helper polycms_asset_url(). This automatically redirects queries to the minified bundles under /dist/assets:

// Dynamic asset resolver
echo '<script src="' . polycms_asset_url('plugins/mtbuilder/assets/js/builder.js') . '"></script>';

// Resolves to: /modules/polycms/plugins/mtbuilder/dist/assets/js/builder.min.js

---

Related Documentation

Launch Your CMS for Perfex CRM β€” Lifetime Access for Just $19
Create blogs, pages, themes, plugins, and manage everything from one powerful unified admin panel.

Early Adopter Advantage

Current pricing reflects the module’s present feature set. As additional plugins, themes, multilingual capabilities, builders (MTBuilder), and ecosystem integrations are released, pricing may be adjusted. Early customers secure today’s price while benefiting from future updates.