Skip to content

Creating upgrade compatible themes

Dotan Mazor - XMPie edited this page Sep 23, 2026 · 1 revision

Building a custom uStore NG theme: what stays stable, and what breaks on upgrade

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.


1. The core principle: extend, do not overwrite

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.


2. What we keep stable

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

2.1 CSS variables

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.

2.2 Custom CSS and Variables CSS

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.

2.3 Widget slots

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.

2.4 The config.json schema

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.

2.5 The uStore REST API

The public REST API of the uStore server. The current documentation always lives on your own server at http://<uStoreServer>/ustorerestapi.

2.6 The uStore library (@ustore/core)

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.

2.7 Theme extension points

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.

2.8 Page and entity DOM hooks

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; }

3. What breaks on upgrade

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.


4. If you are building the theme with an AI assistant

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.


5. After a uStore upgrade: re-validating your theme

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

  • uStoreVersion in config.json matches the new base theme. While the two differ, uStore reports the theme as out of date.
  • Changes from the new base theme's package.json are carried over, and npm install has 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.api calls 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.


6. No extension point for what you need?

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.


Where to read next

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

Clone this wiki locally