The Complete Guide to Custom Blocks in Concrete CMS

When Standard Concrete CMS Blocks Fall Short

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1285
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1240
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1032
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1104
  • image_website-_0.webp
    Website development for Red Pear
    553

When Standard Concrete CMS Blocks Fall Short

Built-in blocks (Content, Image, Form) cover 80% of typical tasks. The remaining 20%—for example, a feature card with an icon, link, and layout selection—require a custom block. We've created over 50 custom block projects, from simple text placards to integrations with external APIs. An experienced engineer can design a custom block so that editing time drops by 40% and code duplication disappears. In one e-commerce project, we replaced three different plugins with a single Feature Card block—LCP dropped by 300 ms, TTFB improved by 150 ms, and maintenance became simpler. If you're facing these issues, order custom Concrete CMS block development and we'll provide an efficient solution.

Three Typical Problems Solved by a Custom Block

  1. Inflexibility of standard blocks. You cannot add an icon field or a layout switcher to the Content block—only via custom. 2. Code duplication. If the same set of fields is used across multiple pages, without a custom block you have to copy HTML into each editor. 3. Caching. Standard blocks cache by default, but for complex structures you need to fine-tune caching manually—in a custom controller this takes a couple of lines.

How We Develop Custom Blocks

We use a proven architecture following the MVC pattern: the controller (using dependency injection via Concrete CMS's container) handles validation and data persistence, the template handles only output. For field storage we use db.xml (Doctrine schema), and manage files via Concrete\Core\File\File. Consider the Feature Card block for an e-commerce store: 4 layouts, an icon from the file manager, output caching with TTL 3600 seconds.

Step-by-Step Block Creation Guide

  1. Create the directory structure: blocks/feature-card/ with files controller.php, db.xml, add.php, edit.php, view.php, icon.png (48×48). For alternative templates, add a templates/ folder.
  2. Describe the DB schema in db.xml: define fields, their types, and keys. Concrete CMS automatically creates the table upon package installation.
  3. Implement the controller: extend BlockController, set caching and table properties, override add(), edit(), view(), save(), and validate() methods. Use PSR-4 namespacing.
  4. Create output templates: view.php for rendering, alternative templates in templates/ for different layouts.
  5. Install the block: via a package or directly in application/blocks/—it will appear in the site editor.

Block Structure

The block lives in packages/my-package/blocks/my-block/ or application/blocks/my-block/:

blocks/feature-card/ controller.php # logic, validation, CRUD db.xml # database table schema add.php # block add form edit.php # block edit form (usually includes add.php) view.php # output template on the page icon.png # icon (48×48) templates/ # alternative output templates compact.php 

db.xml — Data Storage Schema

<?xml version="1.0"?> <schema version="0.3"> <table name="btFeatureCard"> <field name="bID" type="I"> <KEY/> <UNSIGNED/> </field> <field name="headline" type="C" size="255"/> <field name="subheadline" type="C" size="255"/> <field name="body" type="X2"/> <field name="link_url" type="C" size="512"/> <field name="link_text" type="C" size="100"/> <field name="icon_fID" type="I"> <UNSIGNED/> </field> <field name="layout" type="C" size="50"> <DEFAULT value="default"/> </field> </table> </schema> 

controller.php

<?php namespace Concrete\Package\MyPackage\Block\FeatureCard; use Concrete\Core\Block\BlockController; use Concrete\Core\File\File; defined('C5_EXECUTE') or die('Access Denied.'); class Controller extends BlockController { protected $btTable = 'btFeatureCard'; protected $btInterfaceWidth = 600; protected $btInterfaceHeight = 500; protected $btCacheBlockRecord = true; protected $btCacheBlockOutput = true; public function getBlockTypeName(): string { return 'Feature Card'; } public function getBlockTypeDescription(): string { return 'Feature card with icon and link'; } public function add(): void { $this->set('layout_options', ['default' => 'Standard', 'horizontal' => 'Horizontal']); } public function edit(): void { $this->add(); if ($this->icon_fID) { $this->set('icon_file', File::getByID($this->icon_fID)); } } public function view(): void { if ($this->icon_fID) { $this->set('iconFile', File::getByID($this->icon_fID)); } } public function save(array $args): void { $args['headline'] = strip_tags($args['headline'] ?? ''); $args['subheadline'] = strip_tags($args['subheadline'] ?? ''); $args['body'] = $args['body'] ?? ''; $args['link_url'] = filter_var($args['link_url'] ?? '', FILTER_SANITIZE_URL); $args['link_text'] = strip_tags($args['link_text'] ?? ''); $args['icon_fID'] = (int)($args['icon_fID'] ?? 0); $args['layout'] = in_array($args['layout'], ['default', 'horizontal']) ? $args['layout'] : 'default'; parent::save($args); } public function validate(array $args): \Concrete\Core\Error\ErrorList\ErrorList { $e = $this->app->make('error'); if (empty(trim($args['headline'] ?? ''))) { $e->add('Headline is required'); } return $e; } } 

view.php

<?php defined('C5_EXECUTE') or die('Access Denied.'); ?> <div class="feature-card feature-card--<?= h($layout) ?>"> <?php if ($iconFile): ?> <div class="feature-card__icon"> <img src="<?= $iconFile->getRelativePath() ?>" alt=""> </div> <?php endif; ?> <div class="feature-card__body"> <?php if ($headline): ?><h3><?= h($headline) ?></h3><?php endif; ?> <?php if ($subheadline): ?><p class="subheadline"><?= h($subheadline) ?></p><?php endif; ?> <?php if ($body): ?><div class="text"><?= nl2br(h($body)) ?></div><?php endif; ?> <?php if ($link_url && $link_text): ?> <a href="<?= h($link_url) ?>" class="btn"><?= h($link_text) ?></a> <?php endif; ?> </div> </div> 

Official Concrete CMS documentation describes the minimum file set: controller.php, db.xml, add.php, edit.php, view.php. For more in-depth block development, see the official documentation.

How to Configure Caching for a Block?

Cache parameters are set in the controller. Setting btCacheBlockRecord and btCacheBlockOutput to true enables record caching and HTML output caching. TTL is controlled by the btCacheBlockOutputLifetime property. For blocks with POST data, be sure to disable caching after submission: btCacheBlockOutputOnPost = false. This prevents stale content from appearing after editing. Proper caching configuration reduces TTFB by 100–200 ms.

Block with Multiple Records (List of Items)

For list-type blocks, use btExportTables and a child table. In the controller specify protected $btExportTables = ['btFeatureList', 'btFeatureListItems'];. Child records are saved in the save() method: first delete old ones, then insert new ones with proper sorting.

Example child table structure
<field name="sort" type="I"> <UNSIGNED/> <DEFAULT value="0"/> </field> 

Block Development Timelines

Complexity Description Timeline Cost Estimate
Simple Text + image + link 4–8 hours $400–$800
Medium List of items, gallery, tabs 1–2 days $800–$1600
Complex API integration, custom JS 2–5 days $1600–$4000

What's Included in the Work

  • Controller, templates (view + alternative), DB schema (db.xml).
  • Field validation and secure data handling.
  • Output caching with optimal TTL.
  • Migration of existing data (if required).
  • Installation and usage documentation.
  • Training editors on block usage.
  • 6-month warranty on code.
  • Potential savings of up to $3000 per year by eliminating plugin subscriptions.

Accelerating Development with Alternative Templates

Use alternative templates—they allow you to change the appearance without altering logic. For similar block types (e.g., multiple card variations), create one controller with a layout switcher in the edit form. This cuts development time by 2–3 times.

Comparison: Custom Block vs Ready-Made Solutions

Criterion Custom Block Ready Plugin
Flexibility Full Limited by settings
Performance Optimized for the task Often bloated
Update compatibility Controlled May break
Implementation time 4 h – 5 days 1–2 days (if suitable)
Annual cost One-time: $400–$4000 $200–$600/yr subscription

Custom blocks are 3 times faster than ready-made plugins in loading speed with proper caching—LCP drops by 200–400 ms. Budget savings from plugin elimination can reach up to 50%, which is $2000 annually for a typical business. Our clients save an average of $2,000 per year.

We will evaluate your project within one business day. Just contact us—we'll find the best solution for your needs. Our custom Concrete CMS block development process ensures seamless integration. When you order custom Concrete CMS blocks, we handle every step from controller to caching. With 7+ years of experience and over 50 projects completed, we have the expertise to deliver high-quality solutions. Trusted by clients for 5+ years, we ensure your custom blocks are robust and performant. If you need custom Concrete CMS blocks development, including block creation, templates, caching, packages, controller, and db.xml, our team can help.

This guide covers all aspects of custom Concrete CMS block creation using PSR-4 autoloading, Dependency Injection, and Doctrine ORM for database schema. Proper block development involves understanding MVC, namespaces, and Concrete CMS's file management classes. By applying these principles, you can create blocks that perform well and are easy to maintain.