-
Notifications
You must be signed in to change notification settings - Fork 3
Creating upgrade compatible themes
uStore NG lets you build your own theme on top of the theme shipped with the product (AquaBlue). The theme is your code, and you own its compatibility with future uStore versions.
This article is the compatibility contract: the surfaces XMPie keeps stable between versions, and the practices that will break your theme when uStore is upgraded.
Read it before you start. Most compatibility problems are not coding mistakes — they are architectural decisions made on day one.
A uStore upgrade ships a new version of the base theme. Your theme is upgraded by carrying your changes into that new base theme — see Upgrading a Custom Theme.
Which gives one rule: the fewer base theme files you changed, the cheaper every upgrade is.
- New functionality goes into new files and new components.
- Styling goes through CSS variables and the Custom CSS panel, not by editing the base theme's SCSS.
- Changing an existing component is a minimal, targeted edit, not a rewrite of the file.
A theme with 10 modified files upgrades in an hour. A theme with 200 rewritten files costs weeks at every upgrade.
Everything below is public contract. We do not break it between versions without announcing it first.
| Surface | What is stable | Reference |
|---|---|---|
| CSS variables | The documented variable names and their meaning | Editing CSS variables |
| Custom CSS / Variables CSS | The panels themselves, and the order in which they are applied | Theme customization overview |
| Widget slots | Slot names declared in config.json
|
Widgets |
| Page and entity DOM hooks |
data-page and the entity GUID attributes on <body>
|
see 2.8 below |
config.json |
The schema and the meaning of its fields | Theme file structure |
| REST API | The public uStore API | http://<uStoreServer>/ustorerestapi |
| uStore library | The documented UStoreProvider methods |
uStoreProvider Reference |
| Theme extension points |
getInitialProps, localizations, General services, folder aliases |
see 2.7 below |
A documented set of CSS variables controls colours, fonts, sizes and images across the storefront. Some are exposed as controls in the WYSIWYG theme editor; the rest can be set in the Variables CSS panel.
CSS precedence, highest first:
| # | Level |
|---|---|
| 1 | Custom CSS |
| 2 | Variables CSS |
| 3 | Theme editor controls |
| 4 | The theme's default CSS |
Only documented variables are stable. The theme declares further CSS variables that are not part of the documented set; those are internal and may be renamed or removed.
The Custom CSS and Variables CSS panels of the theme editor are the supported way to customize appearance. The order in which they are applied — variables first, then Custom CSS — is guaranteed.
Store-level customization is done through the theme editor only, not by editing CSS files in the uStore file system.
Slot names declared in the theme's config.json under customization.widgets.locations[].name are stable. A widget placed in a slot by name keeps working after an upgrade.
If you need an extension point and no slot exists for it, ask XMPie for a new slot. That is cheaper and safer than injecting markup into a component you do not own.
| Field | Purpose |
|---|---|
engineVersion |
Theme engine version |
name |
Internal theme name |
displayName |
Name shown in the Back Office |
uStoreVersion |
The uStore version this theme was built for |
customConfigurations |
Optional, and not present in the shipped config.json. Theme-level configuration. Today it holds one setting: nonSecuredPages, the theme's own pages that open without login |
customization |
Theme editor controls and widget slot declarations |
features |
Internal. Build-time flags for XMPie's own components. Leave the block exactly as shipped |
uStore compares uStoreVersion against the version of the base theme to tell you whether your theme is up to date.
The base theme ships without a customConfigurations block. Add it only if you need it.
Every url must start with /pages/, otherwise the theme is rejected on upload, and the nonSecuredPages array must be present whenever the block is. Anonymous access applies to B2C stores; a B2B store always asks for login.
The public REST API of the uStore server. The current documentation always lives on your own server at http://<uStoreServer>/ustorerestapi.
| Service | Purpose | Reference |
|---|---|---|
UStoreProvider.api |
Calls to the uStore REST API | Working with REST API |
UStoreProvider.state |
Access to the uStore data model | Accessing uStore data |
UStoreProvider.state.customState |
Your own state, shared across the theme | Managing custom state |
The stable surface is the set of methods listed in the uStoreProvider Reference. Anything reachable on the object but absent from the reference is internal.
| Extension point | What it is |
|---|---|
getInitialProps |
An async function assigned to a page component, called before the page renders: MyPage.getInitialProps = async ({ query }) => ({ ... }). The component must be exported from routes/index.js — a file that merely sits in routes is never called. On the first load the return value is written into the custom state; on later navigations it feeds the customState prop, where live custom state takes precedence. See Working with REST API
|
| Localizations | Locale files under src/localizations and the t helper, imported as a named export: import { t } from '$themelocalization'. See Working with localizations
|
| General services |
themeContext, urlGenerator and utils, shipped inside the theme in src/ustore-internal/services. You use them; you do not modify them. See General services
|
LinkAria |
The link component for internal navigation, imported from $core-components. Pass the target URL through to, normally produced by urlGenerator. reloadDocument forces a full page load, disabled disables the link, and the remaining props reach the underlying react-aria-components link |
| Folder aliases |
$assets, $core-components, $themepages (the routes folder), $themelocalization, $themeservices, $styles, $ustoreinternal. The theme declares further aliases; those are internal. See Theme file structure
|
| External packages | Adding third-party npm dependencies. See Adding an external package |
src/ustore-internal is framework code and is not part of the contract.
Every NG page marks itself in the DOM, so Custom CSS and widgets can target one page or one item without depending on component markup.
| Attribute | Element | Value |
|---|---|---|
data-page |
<body> |
The page name in kebab case: home, category, products, customization, uedit-customization, and the id of your own pages |
data-item-guid |
<body> |
The GUID of the entity the page shows, on the pages that show one |
data-category-guid |
The layout element | The GUID of the current category, on the category page |
data-product-guid |
The layout element | The GUID of the current product, on the product and customization pages |
The <body> attributes are written by the theme shell and are the ones to rely on. The attributes on the layout element come from the base theme's layout component and disappear if you replace that component with your own.
The GUID attributes appear only once the entity has loaded, and all of them are removed when you navigate away.
body[data-page="category"] .my-widget { display: none; }
body[data-item-guid="0F5A...C1"] .price { display: none; }Nothing below is stable. It works today and may stop working in any next version, without an announcement.
| Practice | Why it breaks |
|---|---|
| Overwriting base theme files instead of extending them | Every overwritten file is a manual merge conflict at the next upgrade |
| CSS selectors built on the internal DOM structure or on generated class names | Component markup is an implementation detail; it changes with redesigns, refactoring and accessibility fixes. Target the page and entity hooks of 2.8 instead |
| Calling undocumented internals of the uStore library | Methods absent from the reference, the shape of the internal Redux state, and internal helpers all change freely |
Modifying src/ustore-internal
|
Framework code. It is replaced wholesale on upgrade |
Editing or copying the features block of config.json
|
Internal build-time flags for the base theme's own components. Their names and their effects change between versions |
Depending on the internals of core-components
|
Their structure, props and styles move with the base theme |
| Editing build and configuration files | Build configuration, webpack configs, node_modules, out, dist are not carried across versions |
| Depending on legacy-iframe internals | The iframe that hosts the legacy ASP.NET pages, its selectors and its messaging are internal. Style legacy pages through the skin |
| Relying on undocumented CSS variables | See 2.1 |
If your Custom CSS stopped taking effect after an upgrade, the cause is almost always the second row.
Prefer, in this order: CSS variables → Custom CSS scoped through the page and entity hooks of 2.8, with the loosest possible selector → a widget slot → ask XMPie for an extension point.
An AI coding assistant writes against whatever it can see in the downloaded theme. It sees internal markup, internal calls and framework folders, and it cannot tell them apart from a supported API. Without explicit constraints it will almost always produce code bound to internals.
The theme package you download contains an AGENTS.md file in the src folder, next to package.json — the folder you open as the project. It holds the rules of this article in the form an assistant consumes directly: the layout of the package, what is off limits, and an index of the documentation pages as raw-markdown links the assistant can fetch on its own.
Most AI coding tools read AGENTS.md from the project root by themselves — open that src folder with your assistant and the rules apply. If your tool expects a different file name, point it at AGENTS.md explicitly or copy the file to the name it expects. You own the file: extend it with your own project's conventions.
Keep it in the package when you repackage the theme for upload. It travels with the theme and is never served to shoppers.
If you cannot use the file at all, give your assistant these constraints instead:
uStore NG theme development rules:
1. Do not modify base theme files unless necessary. Add new functionality in new files.
2. Do not modify or delete src/ustore-internal.
3. Style through documented CSS variables and the Custom CSS panel.
Do not depend on internal DOM structure or generated class names.
To target a single page or item, use the data-page and data-<entity>-guid hooks.
4. From @ustore/core use only methods listed in the uStoreProvider Reference.
Do not call undocumented methods and do not read the internal Redux state directly.
5. Use the widget slots declared in config.json as extension points.
Do not inject markup into components you do not own.
6. Fetch server data only through UStoreProvider.api or the public uStore REST API.
Review the result against sections 2 and 3 before the theme goes to production.
New uStore versions are backward compatible, so an existing custom theme keeps working. To pick up new features and fixes, upgrade the theme to the server version. The procedure is in Upgrading a Custom Theme. Then work through this list.
Version
-
uStoreVersioninconfig.jsonmatches the new base theme. While the two differ, uStore reports the theme as out of date. - Changes from the new base theme's
package.jsonare carried over, andnpm installhas been run. - The installed Node.js version matches what the new uStore version requires.
Appearance
- Custom CSS still applies. Rules that stopped working indicate a dependency on internal markup (section 3) — rewrite them using variables, or re-scope them through the page and entity hooks of 2.8.
- Every CSS variable you rely on is still in the documented set.
- Logo, banner, fonts and button colours render correctly.
Functionality
- Header, footer, home, category and NG product pages.
- Cart and checkout (legacy iframe) open, and the skin is applied.
- Widgets appear in every slot where they were placed.
- Your own pages and components load, and their data arrives.
- Localizations: every culture enabled in the store, including RTL if used.
- Mobile breakpoints: menu, search, product boxes.
Data
-
UStoreProvider.apicalls return data and the browser console is clean. - No calls remain to methods that are absent from the reference.
Validate the upgraded theme on a test store before publishing it to a live one.
Talk to XMPie before you reach into internals. A new widget slot, a new CSS variable or a new API method is a normal request. An extension point we build stays stable; a workaround through internals does not.
| Topic | Page |
|---|---|
| Setting up a development environment | Getting started |
| What is in the downloaded package | Theme file structure |
| The uStore library | uStore library |
| Building and uploading a theme | Publishing the theme |
| Moving to a new uStore version | Upgrading a Custom Theme |
uStore NG Themes
uStore NG Extensibility
Theme customization overview
Editing CSS variables
Editing the CSS
Migration from Legacy to NG
Theme development overview
Creating upgrade compatible themes
Getting started
Theme file structure
Publishing the theme
Upgrading a Custom Theme
Editing HTML content
Adding a new page
Adding assets
Adding a component
Customizing a skin
Modifying CSS variables
Editing fonts
Adding JavaScript
uStore library
Working with REST API
Accessing uStore data
Managing custom state
Working with localizations
General services
Adding an external package
Customizing the Theme Editor
Theme tips and tricks
uStoreProvider Reference
Tax Webhook
Order Approval Webhook
Manufacturer Webhook
Widgets
Cart Export Webhook
Input Control Development Guide
Using uStore as a web component