Theme Settings Engine
Purpose
The Shop3 theme needs one maintainable admin centre for theme configuration without making the storefront read many database settings on every request.
This document describes the architecture for a future Theme Settings Engine. It is a design document only; it does not introduce the implementation.
Reference UX Notes
Centralised settings UX patterns from reference themes were reviewed as internal research only. Useful generic patterns:
- one central theme settings page;
- left sidebar sections for fast navigation;
- grouped settings instead of scattered module pages;
- current-page module placement overview;
- clear actions for save, reset/back, and cache clearing.
Do not copy third-party code, internal storage, routes, naming, or vendor-specific implementation. The Shop3 implementation should follow canonical OpenCart conventions and keep the architecture lightweight.
Goals
- Keep all theme settings available from one admin controller/page.
- Split settings into independent sections.
- Save only the current section.
- Keep settings namespaced with the
shop3_prefix. - Avoid repeated database reads on the storefront.
- Support OpenCart multi-store configuration.
- Keep the schema, naming, section model, and cache strategy portable to a future OpenCart 4 layer.
Sections
Initial sections:
generalheaderfooterhomecategoryproductcheckoutstickerstypographycolorsperformancecustom_css_jsintegrations
The key colors stays as a technical section identifier for compatibility with the approved design. Prose may use British English such as colours.
Each section should have its own schema, defaults, validation rules, and save flow.
Namespaced Keys
Settings use the shop3_ prefix and section-based keys:
shop3_theme_general
shop3_theme_header
shop3_theme_footer
shop3_theme_home
shop3_theme_category
shop3_theme_product
shop3_theme_checkout
shop3_theme_stickers
shop3_theme_typography
shop3_theme_colors
shop3_theme_performance
shop3_theme_custom_css_js
shop3_theme_integrations
These keys are stored per store_id using OpenCart's settings system where appropriate.
Data Model
Use OpenCart's setting/setting model as the default persistence layer for OC3.
Recommended stored value per section:
{
"version": 1,
"updated_at": "2026-06-08T00:00:00Z",
"settings": {}
}
Conceptual fields:
store_id: OpenCart store id.code:shop3_themeor a section-specific code if OC3 constraints make that cleaner.key: section key, for exampleshop3_theme_header.value: serialized payload for that section.serialized:1when using OC3 serialized array storage.version: payload schema version inside the stored value.settings: section settings only, not the whole theme payload.
Defaults should live in code, not as duplicated database rows. The database should store only explicit section values for each store.
Admin UI Structure
Canonical future route:
extension/theme/shop3
Canonical OC3 controller file:
admin/controller/extension/theme/shop3.php
Recommended UI:
- standard OpenCart admin header/footer and breadcrumbs;
- left sidebar with section navigation;
- main panel showing one active section form;
- save current section button;
- reset current section button;
- dirty state indicator for unsaved changes;
- preview/storefront link;
- optional cache clear action when a setting affects cached storefront output;
- module placement overview as a separate read-only or clearly separated panel.
The page may use tabs or sidebar navigation, but only the active section should be submitted for persistence.
Save Strategy
Use a single controller action with a section parameter unless implementation proves separate endpoints are simpler:
admin/index.php?route=extension/theme/shop3/save§ion=header&user_token=...
Save flow:
- verify
user_tokenthrough the normal OC3 admin flow; - verify the user has modify permission for
extension/theme/shop3; - read
store_idfrom request, defaulting to0; - read only the submitted section payload;
- validate the section using its schema;
- merge submitted values with section defaults;
- persist only that section key for that
store_id; - invalidate cache for that section/store only;
- redirect back to the same section with success or error state.
Do not resave unrelated sections on submit.
Partial Persistence
Partial save is mandatory. If the header section is saved, only shop3_theme_header for the selected store_id changes.
To avoid losing settings:
- each form posts only fields for its own section;
- missing fields are interpreted by schema rules, not by deleting unrelated sections;
- validation errors keep the submitted values visible in the active section;
- existing stored section values remain unchanged when validation fails;
- reset current section deletes or restores only the selected section.
Validation
Each section defines field-level validation. Examples:
- status toggles: boolean or
0/1; - colours: hex colour format or approved token name;
- typography: approved font family/token and numeric sizes within bounds;
- performance: boolean toggles and bounded numeric cache TTLs;
- custom CSS/JS: length limits and admin permission checks;
- integrations: URL, ID, or token-like values validated by shape but not executed;
- image paths: existing image manager path or empty value;
- layout/module references: valid OpenCart ids when the setting crosses into module placement.
Errors should be keyed by field and rendered inside the active section. Global errors should appear in the standard OpenCart alert area.
Read Strategy
The storefront should not query many individual settings on every request.
Recommended strategy:
- load all
shop3_theme_*section values for the currentstore_idonce; - merge them with defaults into one aggregate config array;
- expose the aggregate through a small config service/helper;
- cache the aggregate per store;
- invalidate cache only after relevant section save.
The storefront API should look conceptually like:
shop3_config.get('header.logo_mode')
shop3_config.section('performance')
Implementation can be a model/helper in OC3. Avoid global functions unless they match a clear OpenCart convention.
Cache Strategy
Cache key should include at least:
shop3.theme_config.store_{store_id}
Add language_id or currency only for settings whose resolved values actually differ by language or currency. Do not over-segment cache keys without need.
Cache invalidation:
- saving a section invalidates aggregate cache for that
store_id; - resetting a section invalidates aggregate cache for that
store_id; - global cache clear may delete all
shop3.theme_config.*keys; - no storefront request should rebuild cache more than once per cache miss.
Multi-store Awareness
The admin page must include store selection using OpenCart stores.
Rules:
store_id = 0is the default store;- each saved section belongs to one
store_id; - unsaved stores inherit code defaults unless an explicit inheritance feature is added later;
- the UI should clearly show which store is being edited;
- switching store should load that store's section values;
- partial save must not overwrite another store's values.
Future inheritance can be introduced explicitly, but should not be implicit or surprising.
File Structure Proposal
Future OC3 implementation:
admin/controller/extension/theme/shop3.php
admin/language/en-gb/extension/theme/shop3.php
admin/view/template/extension/theme/shop3.twig
catalog/model/extension/theme/shop3.php
catalog/controller/extension/theme/shop3.php
Notes:
admin/controller/extension/theme/shop3.phpowns admin rendering, validation, and save actions.admin/language/en-gb/extension/theme/shop3.phpowns admin labels/errors.admin/view/template/extension/theme/shop3.twigowns the settings page UI.catalog/model/extension/theme/shop3.phpcan load and cache aggregate storefront settings if a model is cleaner than a helper.catalog/controller/extension/theme/shop3.phpshould only exist if controller behaviour is genuinely needed.
A future helper/service may be introduced when it reduces duplication across admin and catalog, but avoid unnecessary abstraction before the first concrete implementation.
Compatibility With OpenCart 3
Use canonical OC3 conventions:
- route/controller under
extension/theme/shop3; - language files under
admin/language/en-gb/extension/theme/shop3.php; - Twig admin view under
admin/view/template/extension/theme/shop3.twig; model_setting_settingfor settings persistence where appropriate;- events/modifications only when a real integration need exists.
No OpenCart core edits are allowed.
Future OC4 Portability
Keep these parts portable:
- section ids and section schema names;
shop3_naming convention for current OC3 entities;- payload shape with
versionandsettings; - defaults and validation rules;
- partial save semantics;
- aggregate config cache strategy.
The future OC4 layer may use different controller namespaces and framework services, but the conceptual section model should remain stable.
Module Placement Boundary
A module placement overview is useful UX, but it must not blur responsibilities.
Theme settings may display layout/module placement as an overview or navigation aid. Actual OpenCart module placement should remain compatible with OpenCart layouts/modules unless a separate architecture PR defines a theme-specific placement engine.
Do not mix module placement data into the same unstructured blob as visual theme settings.
Anti-patterns
Do not:
- create one huge unstructured setting blob;
- save all sections on every submit;
- read dozens of database settings repeatedly on storefront requests;
- copy third-party internals or vendor-specific implementation;
- edit OpenCart core files;
- introduce events/modifications without a real need;
- mix module placement with theme configuration without a clear boundary;
- add dependencies without justification in PR Notes;
- add a build pipeline as part of the settings engine design.
Open Questions
- Should defaults live in a PHP schema map, a model method, or a small shared service once implementation begins?
- Should admin section navigation use one rendered page with tabs or lazy-load one section at a time?
- Should multilingual text settings be stored inside the section payload by
language_id, or separated into language-aware section values? - Should cache use OpenCart's cache abstraction only, or also generate a PHP config artifact for very hot storefront paths?