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.
01Shared foundation
- Role
- Design, architecture and engineering
- Stack
- Craft CMS 5.10+
- PHP 8.2+
- Yii module
- Composer
- Status
- In use across the plugin family
- Familiar Shared patterns for tables, filters and settings.
- Reusable Common services maintained in one package.
- Configurable Site preferences carried across the family.
02The problem
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 area | What the family gains |
|---|---|
| Control Panel patterns | Table, 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 language | ColorHelper 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 ranges | DateFormatHelper 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 exports | CsvImportHelper 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 editions | Shared 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 access | CpNavHelper builds permission-aware navigation; GqlHelper supports schema permission checks. Shared bootstrap registers translations and connects log permissions when Logging Library is installed. |
| URLs and content | UrlSafetyHelper 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 devices | GeoHelper, 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 storage | Storage 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 work | Scheduling 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 portability | DbHelper 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 codes | Asset-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 diagnostics | The 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.
04Configuration
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.
// 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.
05Engineering
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.
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.
06Result
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.
