Visual theme customization tool for Magento 2 Breeze themes with live preview, draft/publish workflow, and 16+ field types.
Full documentation: https://docs.swissuplabs.com/m2/extensions/breeze-theme-editor/
- Installation Guide
- Theme Developer Guide - How to add Theme Editor support to your theme
- Configuration
- User Guide
- GraphQL API
- Live Preview - See changes instantly without page reload
- Draft/Publish Workflow - Test before going live
- 16+ Field Types - Colors, fonts, toggles, images, spacing, repeaters, and more
- Dual Color Format Support - RGB (Breeze 2.0) and HEX (Breeze 3.0) with auto-detection
- Theme Inheritance - Extend parent theme configurations
- Multi-Store Support - Different settings per store view
- Version Control - Publication history with rollback
- Secure Access - Token-based authentication (3-hour sessions)
Add etc/theme_editor/settings.json to your theme:
{
"version": "1.0",
"sections": [
{
"id": "colors",
"name": "Colors",
"icon": "palette",
"settings": [
{
"id": "primary_color",
"label": "Primary Color",
"type": "color",
"default": "#1979c3",
"css_var": "--primary-color",
"description": "Main brand color"
},
{
"id": "text_color",
"label": "Text Color",
"type": "color",
"default": "#333333",
"css_var": "--text-color",
"description": "Body text color"
}
]
},
{
"id": "typography",
"name": "Typography",
"icon": "text_fields",
"settings": [
{
"id": "body_font",
"label": "Body Font",
"type": "font_picker",
"default": "Open Sans",
"css_var": "--font-family-base",
"options": [
{"value": "Open Sans", "label": "Open Sans"},
{"value": "Roboto", "label": "Roboto"},
{"value": "Georgia", "label": "Georgia"}
]
},
{
"id": "font_size",
"label": "Base Font Size",
"type": "number",
"default": "16",
"min": "12",
"max": "24",
"step": "1",
"css_var": "--font-size-base",
"description": "Base text size in pixels"
}
]
},
{
"id": "layout",
"name": "Layout",
"icon": "view_quilt",
"settings": [
{
"id": "container_width",
"label": "Container Width",
"type": "text",
"default": "1280px",
"css_var": "--container-width",
"placeholder": "e.g. 1280px"
},
{
"id": "sticky_header",
"label": "Sticky Header",
"type": "toggle",
"default": true,
"css_var": "--header-sticky",
"description": "Fix header on scroll"
}
]
}
]
}The Theme Editor generates CSS variables that you can use in your stylesheets:
/* web/css/source/_theme.less or styles.css */
:root {
/* These will be overridden by Theme Editor */
--primary-color: 25, 121, 195; /* RGB format for Breeze */
--text-color: 51, 51, 51;
--font-family-base: "Open Sans", sans-serif;
--font-size-base: 16;
--container-width: 1280px;
--header-sticky: 1;
}
/* Use variables in your styles */
.btn-primary {
background: rgb(var(--primary-color));
}
body {
color: rgb(var(--text-color));
font-family: var(--font-family-base);
font-size: calc(var(--font-size-base) * 1px);
}
.page-wrapper {
max-width: var(--container-width);
margin: 0 auto;
}
.page-header {
position: var(--header-sticky) == 1 ? sticky : relative;
}| Type | Description | CSS Output Example |
|---|---|---|
| color | Color picker with RGB/HEX support | --color: 255, 0, 0 (RGB) or --color: #ff0000 (HEX) |
| text | Single line input | --width: 1280px |
| textarea | Multi-line text input | --content: "text..." |
| number | Numeric input | --columns: 4 |
| range | Slider control | --opacity: 0.8 |
| select | Dropdown selector | --variant: primary |
| checkbox | Multiple selection | JSON data |
| toggle | On/Off switch | --enabled: 1 or 0 |
| font_picker | Font selector | --font: "Georgia", serif |
| color_scheme | Predefined color schemes | Multiple CSS vars |
| icon_set_picker | Icon set selector | --icon-set: fontawesome |
| spacing | 4-sided control | --padding: 20px or 10px 20px |
| image_upload | Image upload | --logo: url(...) |
| code | Code editor | CSS/JS code |
| social_links | Social media links | JSON data |
| repeater | Dynamic lists | JSON data for JS |
Color Format Support:
- Breeze 2.0 themes use RGB format:
--color: 255, 0, 0 - Breeze 3.0 themes use HEX format:
--color: #ff0000 - Format is auto-detected from default value or set explicitly via
formatfield
See full documentation: https://docs.swissuplabs.com/m2/extensions/breeze-theme-editor/theme-developer-guide/
The module provides a comprehensive GraphQL API for programmatic access:
query {
breezeThemeEditorConfig(storeId: 1, status: DRAFT) {
version
sections {
code
label
fields {
code
label
type
value
default
format
cssVar
}
}
palettes {
id
label
groups {
colors {
id
label
value
cssVar
format
}
}
}
}
}API Features:
- 10 Query operations (config, values, publications, presets, etc.)
- 9 Mutation operations (save, publish, rollback, import/export, etc.)
- Full support for draft/publish workflow
- Publication history with changelog tracking
See full API documentation: https://docs.swissuplabs.com/m2/extensions/breeze-theme-editor/graphql-api/
The Theme Editor supports both RGB and HEX color formats to ensure compatibility with different Breeze versions:
{
"id": "primary_color",
"type": "color",
"default": "25, 121, 195",
"format": "rgb",
"css_var": "--primary-color"
}CSS output: --primary-color: 25, 121, 195
Usage: background: rgb(var(--primary-color))
{
"id": "primary_color",
"type": "color",
"default": "#1979c3",
"format": "hex",
"css_var": "--primary-color"
}CSS output: --primary-color: #1979c3
Usage: background: var(--primary-color)
If format is not specified, the Theme Editor automatically detects the format from the default value:
- Contains comma β RGB format
- Starts with
#β HEX format - Contains
rgb()wrapper β RGB format (wrapper is removed)
Note: The GraphQL API includes the format field for all color values.
-
Enable in Admin:
- Go to: Stores > Configuration > Swissup > Breeze Theme Editor
- Select Yes and save
-
Access Theme Editor:
- Click "Open Frontend in Theme Editor Mode" button in admin config
- Or use URL:
https://your-store.com/?breeze_theme_editor_access_token=YOUR_TOKEN - Access token is valid for 3 hours
-
Make Changes:
- Panel appears on the right side
- Edit values with live preview
- Click Save Draft or Publish
On staging sites protected by HTTP Basic Auth (.htaccess), the browser keeps
asking for the password over and over as soon as the Theme Editor opens.
Why: the admin UI authenticates its GraphQL calls with
Authorization: Bearer <token>. Apache/nginx consumes that header first, tries
to read it as Basic credentials, fails, and answers 401 WWW-Authenticate: Basic
before Magento is ever reached. Every XHR then triggers a native password
prompt, and no correct password can clear it.
Fix (no server access needed): move the token to another header.
Go to Stores > Configuration > Swissup > Breeze Theme Editor > General Settings
and set GraphQL Authorization Header to X-Bte-Authorization.
The admin JS then sends the token there, Basic Auth ignores the unknown header,
and the module copies the value into Authorization server-side, right before
Magento validates the token. Authentication itself is unchanged β the same JWT,
the same core validator, the same ACL checks.
Same thing from the CLI:
php bin/magento config:set breeze_theme_editor/general/auth_header X-Bte-Authorization
php bin/magento cache:flushAlternative fix (requires server access): let Bearer requests through Basic Auth β for the GraphQL endpoint only.
β οΈ Do not apply these rules site-wide. The web server can only check that the header starts withBearer, not that the token is valid, so an unscoped rule lets anyone bypass Basic Auth on every route by sending an arbitraryBearervalue. Scoped to the GraphQL endpoint, the exception is limited to a path Magento authenticates itself β but note that/graphqlis a public API in Magento, so its unauthenticated queries (catalog data and the like) become reachable on the staging site.
π Substitute your actual endpoint path. The module builds the endpoint from the store base URL, so a subdirectory install answers on
/shop/graphql, not/graphql. Every/graphqlbelow must be replaced with your real path β the 401 message the Theme Editor shows prints the path it is actually calling. Keep the patterns anchored, and escape regex metacharacters if your base path contains any (/shop.v2/graphqlβ^/shop\.v2/graphql$).
Apache β in the vhost, where <LocationMatch> matches the URL as it arrived,
before any rewriting. Use <LocationMatch> with an anchored pattern rather than
<Location>: <Location> matches by prefix, so /graphql would also cover
unrelated routes such as /graphql-admin.
<LocationMatch "^/graphql$">
SetEnvIf Authorization "^Bearer " BTE_BEARER
<RequireAny>
Require env BTE_BEARER
Require valid-user
</RequireAny>
</LocationMatch><LocationMatch> is not allowed in .htaccess. There, use <If> β but verify it
with the curl check below, because per-directory configuration is merged after
Magento's rewrite to index.php on some setups:
<If "%{REQUEST_URI} =~ m#^/graphql$#">
SetEnvIf Authorization "^Bearer " BTE_BEARER
<RequireAny>
Require env BTE_BEARER
Require valid-user
</RequireAny>
</If>nginx β the exception has to be decided at server level, not inside a
location. Magento's routing internally redirects /graphql to index.php,
and the redirected request re-enters the PHP location, which inherits the
server-level auth_basic and challenges again. $request_uri keeps the
original URI across that redirect, so key the realm on it:
# http { } block
# $request_uri carries the query string, so match the bare path or the path
# followed by "?" β and anchor it, or /graphql-admin would opt out too.
map $request_uri $bte_uri_ok {
default 0;
"~^/graphql$" 1;
"~^/graphql\?" 1;
}
map $http_authorization $bte_bearer_ok {
default 0;
"~*^Bearer " 1;
}
map "$bte_uri_ok$bte_bearer_ok" $bte_realm {
default "restricted";
"11" off;
}
# server { } block β applies to every location, PHP included
auth_basic $bte_realm;
auth_basic_user_file /path/to/.htpasswd;Verify with:
curl -s -o /dev/null -D - -X POST https://your-store.com/graphql \
-H 'Authorization: Bearer test' \
-H 'Content-Type: application/json' \
-d '{"query":"{__typename}"}'A 401 with www-authenticate: Basic means Basic Auth is still intercepting;
anything else means the request reaches Magento.
Then check that a normal page still asks for the password:
curl -s -o /dev/null -D - https://your-store.com/ -H 'Authorization: Bearer test'This must still return 401. If it returns 200, the rule was applied too
broadly and the whole site is now reachable with an arbitrary Bearer header.
The nginx recipe above was verified against nginx 1.24 with Basic Auth enabled and Magento at the web root:
| request | result |
|---|---|
/graphql with a valid Bearer token |
reaches Magento, authenticated |
/graphql?x=1 with a valid Bearer token |
reaches Magento, authenticated |
/graphql-admin with a Bearer header |
Basic challenge β not bypassed |
/GraphQL with a Bearer header |
Basic challenge β not bypassed |
/ with a Bearer header |
Basic challenge β not bypassed |
/graphql with no auth header |
Basic challenge β not bypassed |
The Apache variants follow the same idea but were not tested here β run both curl checks after applying them.
composer require swissup/module-breeze-theme-editor
php bin/magento module:enable Swissup_BreezeThemeEditor
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy
php bin/magento cache:flush- Download the module
- Extract files to
app/code/Swissup/BreezeThemeEditor - Run the following commands:
php bin/magento module:enable Swissup_BreezeThemeEditor
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy
php bin/magento cache:flushThis module includes comprehensive test coverage:
- Backend Tests: 25+ PHPUnit tests for PHP code
- Frontend Tests: 24 JavaScript tests for UI components
Using Docker (Recommended):
cd /path/to/magento/root/src
bin/clinotty bash -c "cd vendor/swissup/module-breeze-theme-editor && ../../bin/phpunit"Using PHPUnit directly:
cd vendor/swissup/module-breeze-theme-editor
../../bin/phpunitRun specific test file:
../../bin/phpunit Test/Unit/Model/Service/CssGeneratorTest.phpAccess the browser-based test runner:
http://your-store.com/?jstest=1&autorun=1
URL Parameters:
jstest=1- Enable test modeautorun=1- Automatically run all tests on page loadsuite=<name>- Run specific test suite only
For complete testing documentation, see README-TESTS.md
- Magento 2.4.x or higher
- PHP 7.4, 8.0, 8.1, or 8.2
- Breeze Frontend (v2.0+ for RGB format, v3.0+ for HEX format)
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new features
- Submit a pull request
OSL-3.0
- Documentation: https://docs.swissuplabs.com/m2/extensions/breeze-theme-editor/
- Issues: GitHub Issues
- Source Code: GitHub Repository
- Breeze Frontend - Lightning-fast Magento 2 theme
- Swissup Extensions - Premium Magento extensions