# Overview & Extension-Point Map

> The orientation guide for the whole customization track. It explains what the customization layer is, the one rule that governs almost all of it , every exte…

Source: CBX documentation, version 4.0 preview (unreleased). Canonical page: https://docs.configbox.at/docs/4.0-preview/customization/customization_overview. Last updated 2026-08-11.

---
The orientation guide for the whole customization track. It explains **what the customization layer is**,
**the one rule that governs almost all of it** (your file shadows the core file; yours wins), **every
extension point** CBX exposes and where each resolves in code, and a **decision guide** that maps
"I want to change X" to the right mechanism. Read this first; the other guides in this folder are deep
dives into the individual mechanisms listed here.

All paths below are relative to the component root `docroot/components/com_configbox/` unless stated
otherwise. Source references are point-in-time (component **3.4.1**) — verify against the code.

---

## 1. What the customization layer is

`data/customization/` is CBX's **supported extension point**. Files you place there:

- **shadow their core counterparts** — the framework looks in `data/customization/` *before* the core
  location and uses the first file it finds, so your file pre-empts the shipped one;
- **survive component updates** — updates overwrite core files (`controllers/`, `models/`, `views/`,
  `external/kenedo/`, `helpers/`), but never touch `data/customization/`;
- **are local code, not part of this repository** — the directory is **gitignored** (it is the
  integrator's own code). These guides therefore describe the *contracts you build against*, not
  checked-in examples.

### 1.1 Where the layer lives, per platform

Every mechanism in this document is anchored to **one method**,
`KenedoPlatform::p()->getDirCustomization()` — and each platform adapter resolves it to a location
that its host's update mechanism cannot touch. The docs in this track write paths as
`data/customization/<x>` (the Joomla spelling); **resolve `<x>` against your platform's directory**:

| Platform | `getDirCustomization()` resolves to | Source |
|---|---|---|
| **Joomla** | `components/com_configbox/data/customization/` | `external/kenedo/platforms/joomla/general.php` |
| **WordPress** | `wp-content/plugins/configbox-customization/` — a **sibling directory of the `configbox` plugin**, deliberately outside it | `external/kenedo/platforms/wordpress/general.php` |
| **Magento 2** | the **`Rovexo_ConfigboxCustomizations` module's** `view/customizations/` directory — conventionally `app/code/Rovexo/ConfigboxCustomizations/view/customizations/` | `external/kenedo/platforms/magento2/general.php` |
| **Standalone** | `data/customization/` in the component | `external/kenedo/platforms/standalone/general.php` |

Platform particulars:

- **WordPress:** plugin updates replace the whole `configbox` plugin folder, so the layer must live
  outside it. `configbox-customization` is **not a real WordPress plugin** — no plugin header, no
  activation; ConfigBox reads the directory itself. (The in-plugin `app/data/customization/` is the
  pre-3.3.0 **legacy** location, still exposed as `getOldDirCustomization()` for the 3.3.0 movers.)
- **Magento 2:** the layer sits inside a real (but skeletal) Magento module,
  `Rovexo_ConfigboxCustomizations`, which declares `<sequence>Rovexo_Configbox</sequence>` so it
  loads after the base module; the module also carries any Magento-level overrides
  (routes/blocks/templates), while ConfigBox auto-discovers its own subtree at
  `view/customizations/` by the folder conventions in this track (`view/customizations/` is a
  ConfigBox convention, not a Magento area). If the module is not installed,
  `getDirCustomization()` falls back to a tmp path — i.e. the layer is effectively off. Assets are
  the one relocation: they live at the module's `view/base/web/` (Magento's static-content path),
  not under `view/customizations/assets/`.
- **Joomla:** `data/customization/` sits inside the component dir but is preserved across updates
  (the installer does not clear it).

**The golden rule:** never edit core files. Anything you change in `controllers/`, `models/`, `views/`,
`external/kenedo/` or `helpers/` is lost on the next update. Put your change in `data/customization/`
instead, using the matching mechanism below.

---

## 2. The one resolution rule (and its variations)

Almost every mechanism is the **same idea**: build two candidate paths — a customization path and a core
path — and use one if it exists. The mechanisms differ only in **which path is preferred**. A
customization-first loader (e.g. PSP connectors, `helpers/psp.php:33`) checks `data/customization/` first
so your file *shadows* core; a core-first loader (the class loaders) checks core first so your file only
loads as a *new* addition. The shape is otherwise identical:

```php
$regularPath = KenedoPlatform::p()->getComponentDir($component) .'/controllers/'. $filename;  // core
$customPath  = KenedoPlatform::p()->getDirCustomization()       .'/controllers/'. $filename;  // customization

if ($path == '') {
    if (is_file($regularPath)) {                // class loaders are CORE-first:
        $path = $regularPath;                   //   core wins …
    }
    elseif (is_file($customPath)) {             //   … customization is the fallback (new files only)
        $path = $customPath;
    }
}
require_once($path);
```

> The example above is the controller loader (`KenedoController::getController()`,
> `external/kenedo/classes/KenedoController.php:101-108`). **All three class loaders — controllers, models,
> and view classes — are core-first**, so the customization `controllers/`/`models/`/`views/` folders add
> *new* classes and do not shadow existing core ones. To shadow an existing core class, see the
> `system_overrides/` escape hatch (§3, last rows). Customization-*first* loaders (properties, rule
> conditions, PSP connectors, and the template chain) flip the two `is_file` checks.

The mechanisms differ in **two dimensions**:

- **Resolution mode** — how the customization version takes effect:
  - **Customization-first shadow** (properties, rule conditions, PSP connectors): the loader checks
    `data/customization/` *first*, so your file is picked *instead of* the core file — you can shadow an
    existing core file **or** add a new one.
  - **Core-first / new-only** (controllers, models, view classes): the loader checks the **core** path
    first and only falls back to `data/customization/` when no core file of that name exists. This slot is
    for **new** controllers/models/views; it does **not** shadow an existing core one. See the ⚠ note after
    the table.
  - **Template fallback chain** (view templates): three candidates tried in order — Joomla template
    override → customization template → core template (first that exists is used).
  - **Merge / append** (model property injection, language overrides): your data is *combined* with the
    core data, not substituted for it.
  - **Eager pre-emption at boot** (system overrides, boot/settings hooks): your files are `require_once`'d
    early so a class/function you define is in memory *before* the core would lazily load its version. This
    is how you change an *existing* core class — including a core controller, model, or view.
  - **Registration** (events/observers): your code is *added to* a dispatch list and runs alongside core
    observers.
- **Naming contract** — what the file must be called and (for class-based mechanisms) what class/function
  it must declare. These are the contracts that trip people up; each guide states its contract precisely.

Keep these two questions in mind for any mechanism: *does mine replace or combine with the core's?* and
*what exactly must I name the file/class/function so the loader finds it?*

---

## 3. The extension-point map

Every supported extension point, the directory it lives in, how it resolves, and the dedicated guide.

| You want to … | Put it in `data/customization/` … | Resolution | Contract (file / class / fn) | Guide |
|---|---|---|---|---|
| Change a screen's HTML | `templates/<view>/<tmpl>.php` | Fallback chain (`KenedoView.php:482-486`) | file named like the core template | `com_configbox_overriding_views_and_templates.md` |
| Add a **new** view's class logic | `views/<view>/view.html.php` (etc.) | New views only — see ⚠ note (`KenedoView.php:202-207`) | class `ConfigboxView<Name>` | `com_configbox_overriding_views_and_templates.md` |
| Add a **new** controller | `controllers/<name>.php` | New controllers only — see ⚠ note (`KenedoController.php:101-108`) | class `ConfigboxController<Name>` | `com_configbox_overriding_controllers_and_models.md` |
| Add a **new** model | `models/<name>.php` | New models only — see ⚠ note (`KenedoModel.php:168-176`) | class `ConfigboxModel<Name>` | `com_configbox_overriding_controllers_and_models.md` |
| Add a new field type (widget) | `properties/<type>.php` + `properties/tmpl/<type>.php` | First-match shadow (`KenedoModel::getPropertyObject`) | class `KenedoProperty<Type>` | `com_configbox_custom_properties.md` — but check `../technical/com_configbox_property_types.md` first |
| Add a field to a **stock** model | `model_property_customization/<model>.php` | Merge (`KenedoModel.php:802-836`) | fn `customPropertyDefinitions<Model>()` | `com_configbox_extending_stock_models.md`; type from `../technical/com_configbox_property_types.md` |
| Add a new **question type** | `views/question_<type>/` (+ optional `question_types/ConfigboxQuestion<Type>.php`) | The **view folder** registers the type; the class is first-match (`ConfigboxQuestion.php:79-91`) | class `ConfigboxQuestion<Type>` | `com_configbox_custom_question_types.md` |
| Change what a selection *means* (its output value, comparable value, **order-line SKU**, validation) | `question_types/ConfigboxQuestion<Type>.php` | Sub-class, per question type | overridable methods on `ConfigboxQuestion` | `com_configbox_custom_question_types.md` §3 |
| Add a new rule condition | `rule_condition_types/<name>.php` | First-match (`ConfigboxCondition.php:79-112`) | condition-type class | `com_configbox_custom_rule_conditions.md` |
| React to a domain event | `custom_observers/<File>.php` (+ DB connector) | Registration, before/after core (`KenedoObserver.php:29-62`) | class `Observer<File>` | `com_configbox_events_and_observers.md` |
| Add/replace a payment method | `psp_connectors/<name>/` | First-match shadow (`helpers/psp.php:9`) | connector file layout | `com_configbox_payment_connectors.md` |
| Change an **existing** core class' behavior (controller / model / view / framework / helper) | `system_overrides/<Class>.php` | Eager pre-emption at boot (`helpers/overrides.php:24-39`) | same class name as core | `com_configbox_system_overrides_and_boot_hooks.md` |
| Run code/SQL at boot or DB-connect | `system_overrides/` (incl. `getInitQueries.php`) | Eager include (`KenedoDatabase.php:83-90`) | fn `getPostDbConnectQueries()` etc. | `com_configbox_system_overrides_and_boot_hooks.md` |
| Change displayed text | `language_overrides/<tag>/overrides.ini` | Merge (`KText.php:195`) | INI keys | `com_configbox_language_overrides.md` |
| Add CSS / JS modules | `assets/css/…`, `assets/javascript/…` | `configbox/custom` AMD namespace + `customPaths` (`assets/main.js:43,100`) | AMD `define(...)` | `com_configbox_assets_and_amd.md` |
| Add a site-specific CLI command | `cli/commands.php` | Lazy dispatch (`ConfigboxCliHelper::loadCustomCliCommands()`) | file returns name → `callback` defs | `com_configbox_custom_cli_commands.md` |
| Add a DB column/table a customization needs | `updates/<version>.php` | Customization migration track | versioned, idempotent script | `technical/com_configbox_migrations.md` |

> ⚠️ **The class loaders are "core wins" — they add new classes, they don't shadow core.** All three
> class loaders check the **core** path first and fall back to `data/customization/` only when **no core
> file of that name exists**:
> - **Controllers** — `KenedoController.php:101-108`. The customization `controllers/` slot is for **new
>   controllers** only; it does **not** shadow an existing core controller.
> - **Models** — `KenedoModel.php:168-176`. The customization `models/` slot is for **new models** only;
>   it does **not** shadow an existing core model.
> - **Views (classes)** — `KenedoView.php:202-207`. For **new views** only; does **not** shadow an
>   existing core view.
>
> So to change an **existing core controller, model, or view** you cannot drop a same-named file in the
> customization `controllers/`/`models/`/`views/` folder. Instead: for a **model**, use
> `model_property_customization/` (data shape) or, for behavior, a `system_overrides/` class override; for
> a **view/screen**, use a **template** override (markup) or change the **model/controller** behind it; for
> a **controller**, use a `system_overrides/` class override. **Templates** (not view classes) *do* follow
> the "customization wins" fallback chain. See `com_configbox_overriding_controllers_and_models.md` and
> `com_configbox_overriding_views_and_templates.md` §3.1.

> **System overrides are an escape hatch, not the default.** Prefer the targeted mechanism (override the
> specific controller/model/view/template). Reach for `system_overrides/` only when the thing you must
> change is a framework or helper class with no per-type loader — and know that replacing the *whole*
> class means you inherit the maintenance of keeping it in sync with core updates.

---

## 4. Decision guide — pick the smallest mechanism

Work top-down; stop at the first row that fits. Smaller mechanisms are less code to maintain across
updates.

1. **Only the wording/labels change?** → `language_overrides/` (no PHP at all).
2. **Only the markup/layout of one screen?** → a **template** override (`templates/<view>/…`). No class.
3. **A field needs adding to an existing admin form/list?** → **model property injection**
   (`model_property_customization/`) + a migration for the column. No core edits. Pick its `type`
   from the shipped catalogue: `../technical/com_configbox_property_types.md` §4.
4. **A field type/widget that doesn't exist yet?** → a **custom property** (`properties/`). Check
   the catalogue first — `../technical/com_configbox_property_types.md`, one article per type under
   `../technical/property-types/`. One of the ~28 shipped types usually fits, and a `string` with
   `USE_TEXTAREA`, a `dropdown` fed by a `modelMethod`, or a `json` column covers most of what people
   write a new class for.
5. **Behavior of one controller action or model method?** → **override the class via `system_overrides/`**,
   subclassing the core class and overriding only the method you need (the per-type `controllers/`/`models/`
   folders can't shadow an existing core class — they're core-first).
6. **Conditional-logic vocabulary (rules)?** → a **custom rule condition type**.
7. **You need to *react* to something happening (order placed, record stored, user logs in)?** →
   an **event observer** (`custom_observers/`). This is additive and the safest way to inject logic.
8. **A payment integration?** → a **PSP connector**.
9. **Custom front-end interactivity/styling?** → **assets** (CSS + AMD JS under `assets/`), wired via the
   `configbox/custom` namespace, or attached to a property (see the properties guide §8).
10. **Work an operator or cron initiates (exports, imports, one-off fixes)?** → a **custom CLI command**
    (`cli/commands.php`, surfaced as `wp configbox custom <command>`). Additive and isolated: a broken
    commands file cannot affect the stock CLI suite.
11. **None of the above — you must change a framework/helper class itself?** → `system_overrides/`,
    accepting the sync cost. Consider asking the vendor for a hook instead.

A useful tie-breaker: **prefer "combine/add" mechanisms (observers, property injection, language
overrides) over "replace" mechanisms (class shadows, system overrides)** — additive changes don't drift
out of sync when the core file they would have replaced gets updated.

---

## 5. Layer layout (full)

```
data/customization/                       ← getDirCustomization(); gitignored, upgrade-safe
  controllers/<name>.php                  ← a NEW controller (ConfigboxController<Name>); does NOT shadow a core controller
  models/<name>.php                       ← a NEW model (ConfigboxModel<Name>); does NOT shadow a core model
  views/<view>/…                          ← a NEW view class; does NOT shadow a core view
  templates/<view>/<tmpl>.php             ← override a view template (markup only)
  properties/<type>.php + tmpl/<type>.php ← custom Kenedo property type
  model_property_customization/<model>.php← inject property defs into a stock model
  rule_condition_types/<name>.php         ← custom rule condition type
  custom_observers/<File>.php             ← event observers (registered via DB connectors)
  psp_connectors/<name>/                  ← payment connectors
  system_overrides/<Class>.php            ← eager class/function pre-emption at boot
  system_overrides/getInitQueries.php     ← SQL run right after each DB connect
  language_overrides/<tag>/overrides.ini  ← text overrides per language tag
  assets/css/…  assets/javascript/…       ← custom CSS + AMD JS modules (configbox/custom)
  cli/commands.php                        ← custom CLI commands (`wp configbox custom <command>`)
  updates/<version>.php                   ← customization-track DB migrations
```

Two paths people confuse with the customization layer but which are **separate**:

- **`data/store/private/settings/`** — `getDirCustomizationSettings()`
  (`general.php:1015`). Boot-time settings/override PHP files loaded by `loadOverrideFiles()` *before* the
  `system_overrides/` files (`helpers/overrides.php:9-22`). Covered in the boot-hooks guide.
- **Joomla template overrides** — `getTemplateOverridePath(...)` under the active site template, tried
  *before* `data/customization/templates/` in the template chain (`KenedoView.php:482`). This is the
  Joomla-native override slot; the customization slot is the platform-neutral one.

---

## 6. Cross-cutting rules (apply to every guide)

- **Override the minimum.** Subclass and override one method; copy one template and change a few lines.
  Never copy a whole core file to change one part of it — you inherit its future bug fixes as drift.
- **Escape on output, sanitize on input.** SQL is traditionally hand-built mysqli: `getQuoted()`
  backtick-quotes **identifiers**, `getEscaped()` escapes **values** (add the surrounding quotes
  yourself) — or bind values with `setPreparedQuery($sql, $params)` and skip manual escaping
  entirely. In templates use `hsc()`;
  validate/normalize request data before it touches a query. (See the project's `CLAUDE.md`.)
- **Schema changes go through migrations.** A property/model maps existing columns; it never creates them.
  Deliver any new column/table via a customization migration (`updates/<version>.php`), guarded with
  `ConfigboxUpdateHelper::tableFieldExists()`/`tableExists()`. See `technical/com_configbox_migrations.md`.
- **Match the surrounding idioms.** No namespaces, no PSR-4, static singletons, PHP templates, AMD modules
  served without a build step. Follow Kenedo conventions, not modern Joomla/PSR ones.
- **Verify manually.** CBX has **zero automated tests**. Exercise the real admin form / frontend
  flow after every change, including XHR-injected views (many admin forms load via XHR, not full reloads).
- **Customization wins — so don't shadow by accident.** A file at `data/customization/properties/string.php`
  replaces the built-in `string` type install-wide. Use a fresh name unless replacement is the goal.

---

## 7. Prerequisites & reading order

Read `technical/com_configbox_kenedo_mvc.md` first if the Kenedo MVC model (controllers → models →
properties → views/templates) is unfamiliar; this overview assumes that vocabulary.

Suggested path through this folder:

1. **This overview** (the map and the decision guide).
2. `com_configbox_overriding_views_and_templates.md` — the most common task (change a screen).
3. `com_configbox_extending_stock_models.md` + `com_configbox_custom_properties.md` — add data/fields.
4. `com_configbox_overriding_controllers_and_models.md` — change behavior.
5. `com_configbox_events_and_observers.md` — react to events (the safest injection point).
6. The domain/integration guides — `com_configbox_custom_rule_conditions.md`,
   `com_configbox_payment_connectors.md`.
7. `com_configbox_assets_and_amd.md`, `com_configbox_language_overrides.md` — presentation polish.
8. `com_configbox_system_overrides_and_boot_hooks.md` — last-resort framework overrides.

---

## See also

- `customization/com_configbox_custom_properties.md` — the first and most complete guide (the style/depth
  bar for this track).
- `technical/com_configbox_kenedo_mvc.md` — the framework these mechanisms hook into.
- `technical/com_configbox_migrations.md` — delivering the schema a customization needs.
- `external/kenedo/classes/KenedoController.php`, `…/KenedoModel.php`, `…/KenedoView.php` — the loaders
  that implement the resolution rule.
- `helpers/overrides.php`, `external/kenedo/classes/KenedoObserver.php` — the boot-override and observer
  mechanisms.
