Skip to content

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:

  • general
  • header
  • footer
  • home
  • category
  • product
  • checkout
  • stickers
  • typography
  • colors
  • performance
  • custom_css_js
  • integrations

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_theme or a section-specific code if OC3 constraints make that cleaner.
  • key: section key, for example shop3_theme_header.
  • value: serialized payload for that section.
  • serialized: 1 when 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&section=header&user_token=...

Save flow:

  1. verify user_token through the normal OC3 admin flow;
  2. verify the user has modify permission for extension/theme/shop3;
  3. read store_id from request, defaulting to 0;
  4. read only the submitted section payload;
  5. validate the section using its schema;
  6. merge submitted values with section defaults;
  7. persist only that section key for that store_id;
  8. invalidate cache for that section/store only;
  9. 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:

  1. load all shop3_theme_* section values for the current store_id once;
  2. merge them with defaults into one aggregate config array;
  3. expose the aggregate through a small config service/helper;
  4. cache the aggregate per store;
  5. 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 = 0 is 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.php owns admin rendering, validation, and save actions.
  • admin/language/en-gb/extension/theme/shop3.php owns admin labels/errors.
  • admin/view/template/extension/theme/shop3.twig owns the settings page UI.
  • catalog/model/extension/theme/shop3.php can load and cache aggregate storefront settings if a model is cleaner than a helper.
  • catalog/controller/extension/theme/shop3.php should 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_setting for 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 version and settings;
  • 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?