WorkCraft CMS

LindemannRock Base: different plugins, a shared foundation.

Search, redirects and translations solve different problems. The tools around them should still feel familiar. I built LindemannRock Base to give the plugin family shared Control Panel layouts, settings and supporting services. It is a Yii module installed through Composer, with each plugin building its own features on top.

Role
Design, architecture and engineering
Stack
  • Craft CMS 5.10+
  • PHP 8.2+
  • Yii module
  • Composer
Status
In use across the plugin family
  1. Familiar Shared patterns for tables, filters and settings.
  2. Reusable Common services maintained in one package.
  3. Configurable Site preferences carried across the family.

Small differences become a maintenance problem.

A plugin needs more than its main feature. People need to find records, filter a table, understand a date, export data and change settings. Rebuilding those interactions for every plugin creates repeated work and room for subtle differences.

Those differences affect both sides of the product. A person managing a site has to learn another variation of a familiar task. A developer fixing a pagination problem or changing a date format has to find every implementation that needs the same change.

My work was to identify those shared responsibilities and give them a home. Base combines interface patterns with the services that make them behave consistently. It is part of the plugins' implementation, rather than another product for a site owner to configure separately through the Plugin Store.

The examples above show a selection of the shared APIs. The consuming plugin supplies the records, settings and runtime context. The wider module also covers the less visible work that keeps those interfaces dependable.

Shared infrastructure in Base and its value across the plugin family
Shared areaWhat the family gains
Control Panel patternsTable, analytics, settings and utility layouts bring search, filters, pagination and bulk actions into familiar patterns. Teams can move between plugins without learning a new version of each interaction.
Visual languageColorHelper and shared badge components map states to named colour sets. PluginThemeStyleHelper supplies branding variables for setup and documentation surfaces, keeping identity separate from the underlying layout.
Dates and rangesDateFormatHelper handles timezone-aware formatting; DateRangeHelper supplies common ranges and query bounds. Display and export formats have explicit conventions, so each plugin does not have to invent its own date handling.
Imports and exportsCsvImportHelper parses uploads and detects delimiters. ExportHelper supplies CSV, JSON and Excel output, plus file bundles and spreadsheet formula-injection protection. Each plugin remains responsible for its records and domain validation.
Settings and editionsShared traits cover persistence, config overrides, display names and edition checks. Matching settings controls show when configuration owns a value, making the source of a setting easier to understand.
Navigation and accessCpNavHelper builds permission-aware navigation; GqlHelper supports schema permission checks. Shared bootstrap registers translations and connects log permissions when Logging Library is installed.
URLs and contentUrlSafetyHelper checks redirect targets, ContentSafetyHelper detects executable markup in text, and shared validators cover route and storage paths. These give forms and imports common checks while leaving context-specific validation with the plugin.
Location and devicesGeoHelper, lookup and device-detection traits provide reusable location and user-agent handling. AnalyticsIpHelper centralises IP anonymisation, hashing and lookup eligibility, reducing repeated data-handling logic.
Cache storageStorage selection and ScopedCache account for the host's capabilities, finite expiry and isolation between plugins. A plugin can invalidate its own disposable data without clearing the shared cache for everything else.
Background workScheduling helpers calculate recurring runs, manage ownership of pending jobs and support delayed work across queue backends. Plugins share the mechanics of scheduling while defining the work each job performs.
Database portabilityDbHelper supplies expressions for operations that differ between MySQL and PostgreSQL. Column quoting and JSON extraction have one maintained implementation instead of separate fixes throughout the family.
Assets and QR codesAsset-volume checks validate submitted assets and permissions. The QR renderer adapts to Craft's image driver, giving consumers a shared rendering path across supported server configurations.
Testing and diagnosticsThe integration-test base provides fixtures, component restoration and cleanup. SQL dialect checks and shared console help make recurring quality and support work reusable too.

Sharing infrastructure also concentrates responsibility. A correction to a common helper has one implementation to review, but consuming plugins still need integration checks and a compatible release. The benefit is a maintained contract across the family, rather than a promise that every change is automatically safe everywhere.

A familiar way to work with different information.

The shared table layout provides search, filters, sorting, pagination, column visibility and bulk actions. A plugin supplies its records, labels and actions. The layout supplies the surrounding interaction.

This is already used in the source of Redirect Manager and Search Manager. Redirect rules and search indices have different meanings, but the basic task of finding and reviewing a record follows the same pattern.

Shared table layout
{% extends 'lindemannrock-base/_layouts/cp-table' %}

{# The plugin supplies tableConfig and its records. #}
{% block tableRow %}
  <td>{{ item.name }}</td>
  <td>{{ item.status }}</td>
{% endblock %}

This excerpt shows the division of responsibility. The full configuration also declares the columns, page URL, sorting and pagination. Keeping that context together lets the shared layout preserve filters and site context as someone moves through a table.

The same approach extends to analytics pages, settings and utilities. Shared components cover recurring details such as badges and date filters without dictating every screen a plugin can have.

A site preference should survive an export.

Consistency also depends on information. A table and its refreshed results should use the same timezone and display preferences. Downloads need formats suited to spreadsheets or APIs. Base centralises those conventions, the available export formats and the initial date range used by date-filtered views.

Site configuration
// config/lindemannrock-base.php
return [
  'timeFormat' => '24',
  'monthFormat' => 'short',
  'dateOrder' => 'dmy',
  'dateSeparator' => '/',
  'exports' => [
      'excel' => true,
      'csv' => true,
      'json' => false,
  ],
  'defaultDateRange' => 'last30days',
];

These are example overrides from the configuration template. They let a team set its preferences once for the plugins using the shared helpers. Individual calls can still choose the formatting appropriate to their context.

Translations are registered through the common setup, while the messages remain with the plugin that owns them. Shared conventions support localisation without turning every product's language into a single, undifferentiated catalogue.

Make the common setup explicit.

A plugin registers Base during its own initialisation. The bootstrap helper loads the module, registers the plugin's Twig helper and translations, and can connect its log permissions to Logging Library when that library is installed.

Plugin bootstrap
PluginHelper::bootstrap(
  $this,
  'redirectHelper',
  ['redirectManager:viewSystemLogs'],
  ['redirectManager:downloadSystemLogs']
);

The two packages have separate responsibilities. Base handles shared setup and integration; Logging Library handles recording and reviewing logs. Base also provides database helpers, settings traits and cache utilities for recurring implementation work.

This makes the shared package an architectural responsibility. A change can benefit multiple plugins, but it can also affect them together. The module includes reusable testing helpers and SQL dialect checks, and its documentation explains how consumers should use the common patterns.

A maintained foundation beneath the product family.

The result is a working shared module used across the LindemannRock plugin family. Its value is visible in repeated interactions: familiar tables, common settings behaviour and the same formatting tools behind different products.

Centralising those parts gives improvements a clear place to live. It also leaves room for the differences that matter: how a search is configured, how a redirect is resolved or how a translation is reviewed remains the responsibility of its plugin.

Next08Craft CMS 2026Logging Library

Shared logging infrastructure for Craft CMS plugins: dedicated daily files, a Control Panel viewer for Craft, plugin and PHP logs, and a drop-in API. Free, open source, and the foundation under every plugin in the family.

All work