A storefront typically pulls in dozens of small .js and
.css files: styling libraries, stylesheets, scripts for
individual modules. Each file is a separate request to the server. A
bundle merges them into a single file, and this happens right when you
save in the development environment — no external bundlers, config
files, or separate build commands needed.
Open a .js or .css file in the development
environment. Above the editor you’ll see two lines: a blue one — the
file description, and a grey one — the parameters. The parameters
specify which bundles this file will be included in:
base.js: 4
To the left of the colon is the bundle name, to the right is the priority. A single file can belong to multiple bundles, listed separated by commas:
base.js: 4, print.css: 2
Priority is optional: without it, the file gets 0. The
bundle name becomes the name of the output file, so the extension is
specified directly in it — base.js,
melbis.css, melbis.auth.js. The name may
contain Latin letters, digits, dots, and underscores; all other
characters are stripped — in particular the hyphen, so
melbis-auth.js will become melbisauth.js.
Any .js or .css file inside a template
group can be included in a bundle — both from the shared
statics/ directory and from individual module
directories:
templates/default/
statics/
base/bootstrap.js base.js: 2
base/bootbox.js base.js: 4
melbis/main.js melbis.js: 1
units/
melbis_cataloge/scripts.js melbis.js: 10
melbis_base_page/scripts.js melbis.js: 15
That’s the whole point: a module keeps its script alongside its own templates, where it’s easy to edit and find, while the code arrives at the storefront in a shared file, adding no extra requests.
The assembled file is placed in the statics/ directory
of the same template group, with the prefix bundle.:
templates/default/statics/bundle.melbis.js
A rebuild is triggered on every save of a file that has the bundle line filled in, and it rebuilds all bundles in the group from scratch.
A composition report is prepended to the file:
/* Melbis Shop auto bundle report */
/* Create: 2026-07-17 15:39:56 */
/* #1 main.js 32 ln 1 kb /templates/default/statics/melbis/main.js */
/* #10 scripts.js 57 ln 1 kb /templates/default/units/melbis_cataloge/scripts.js */
/* #15 scripts.js 77 ln 3 kb /templates/default/units/melbis_base_page/scripts.js */
It shows what was included and in what order — this is the first place to look when it’s unclear whose code ran first.
Bundling is concatenation only. No minification, transpilation, or path rewriting inside CSS takes place: files are joined in priority order as-is. If you need minified vendor code, put the already-minified version of the file into the bundle.
Files are sorted by priority in ascending order. The numbers themselves are arbitrary — only their relative values matter, so it’s convenient to leave gaps so you can insert files between existing ones later.
Priorities are a handy way to separate logical groups. For example,
in the demo store, files with priority below 10 are base files from
statics/, and above 10 are module scripts: this ensures
libraries are guaranteed to load before the code that depends on them.
This is not a platform rule, just a way to keep things organized.
Do not assign the same priority to files whose order matters. Sorting is done by number only, and files with equal values are placed in the order the engine traversed the metadata directory — which cannot be predicted.
<link rel="stylesheet" type="text/css" href="{PATH}/statics/bundle.base.css">
<link rel="stylesheet" type="text/css" href="{PATH}/statics/bundle.melbis.css?{BUILD}">
<script defer src="{PATH}/statics/bundle.base.js"></script>
<script defer src="{PATH}/statics/bundle.melbis.js?{BUILD}"></script>{BUILD} is the project build number, a system tag
available in any template. It acts as a version marker: as long as the
number hasn’t changed, the browser serves the file from its cache, but
as soon as the number increases, it downloads the file again.
It changes in two ways: manually — via the MELBIS_BUILD
parameter in the “Design → Installation” dialog (see
“Configuration”), or automatically — via a button in the development
environment that enables incrementing MELBIS_BUILD on every
file save.
You can add ?{BUILD} to any bundle. Vendor libraries
change infrequently, so they are often included without the marker — but
that’s a matter of preference, not a requirement.
To remove a file from a bundle, clear the bundle name from the parameters line and save the file: it will be excluded from the bundle on the next build.
If the last file is removed from a bundle, the bundle itself remains on disk. There is nothing left to build it from, so the engine simply leaves it untouched — the previous
statics/bundle.namefile stays in place and continues to be served to the storefront. The same applies when renaming a bundle: a file with the new name will appear, but the old one will remain. Such files must be deleted manually.
It is best to delete and rename static files using the development
environment’s file tree: that way, bundle metadata is moved or removed
along with the file. If you delete a file outside the IDE — for example,
via FTP — its metadata will remain, and the next build will report a
Can't include file to bundle error.
A bundle only concatenates. If code also needs to be minified,
obfuscated, or otherwise transformed, there is a separate mechanism for
that — the file transformer, Ctrl+B or the
button on the editing panel.
Here’s how it works: the environment sends the filename and the current contents of the editor to the server — including unsaved edits — the server calls your transformer script via HTTP, and whatever it returns completely replaces the text in the editor. The result is not saved anywhere automatically: review what you got and save the file the usual way.
The script address is set in “Editor Settings → Optimization → File Transformer”:
http://localhost/minify.php by default.Ctrl+B
accidentally.This is a development environment setting, not a project setting: it
is not stored in config.json, and each developer configures
it on their own machine.
The script is called by the server, not the application — just like scheduler tasks. That’s why the address uses
localhost: the server calls itself, and the transformer works immediately, without any domain configuration. If you move the script to a separate host, the address must be reachable from the store’s web server, not from the developer’s workstation.
A standard POST request with two fields:
| Field | Contents |
|---|---|
name |
the filename — the extension makes it easy to decide how to handle it |
content |
the current contents of the editor |
It should return the processed text in the response body — no
wrappers, headers, or JSON. A response with a status code of
400 or higher will be shown as an error by the environment,
and the editor contents will not be changed.
The demo store includes a ready-made transformer — a JS and CSS minifier. It’s a plain PHP file in the project root; the engine plays no part in it.
It starts with a check that the request came from the server itself:
// The transformer is called by the engine, so only local requests are allowed
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
if ( $ip !== '127.0.0.1' && $ip !== '::1' )
{
header('HTTP/1.1 403 Forbidden');
die('Only local allowed');
}Then comes the actual transformation:
switch ( pathinfo($_POST['name'], PATHINFO_EXTENSION) )
{
case 'js':
$data = array('input' => $_POST['content']);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.toptal.com/developers/javascript-minifier/api/raw');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
echo curl_exec($ch);
curl_close($ch);
break;
case 'css':
// Same thing, but calling a CSS minifier
break;
default:
echo $_POST['content'];
}Branching by extension is not a platform requirement, but a
convenient convention: a single address handles all file types. The
default branch returns the content as-is, so pressing
Ctrl+B on an unknown file type won’t cause any harm; it’s
worth keeping this rule in your own transformer.
The internals can be anything: minification, obfuscation, auto-formatting, calling a third-party service or a local utility. The platform doesn’t know or check what exactly happens — it only cares about the text in the response.
Don’t remove the local-call check. The transformer lives in the site root, accepts arbitrary text, and runs on your server — without it, anyone on the internet could invoke it. The same technique is used by cron modules, which have a ready-made
CronLocalOnly()method for this purpose (see “Task Scheduler”).