upgrade-mendix-version · git:20260831.70b0e5a · 2026-08-31 · sha256 38ab5b00f3978855
upgrade-mendix-version git:20260831.70b0e5aA
Immutable. This exact content is served forever at /api/v1/blob/38ab5b00f3978855.
---
name: upgrade-mendix-version
description: "Move a project to a newer Mendix version headlessly, with no Studio Pro. Use when raising a project's Mendix version, when a build complains the project version does not match MxBuild, or before adopting a newer runtime. Covers the one check that decides whether a converter must run, and the two green false successes that upgrade nothing."
---
# Upgrading a Mendix version without Studio Pro
## Why this needs a skill
Both ways of getting it wrong **look like success**:
| what you do | what you see | what is true |
|---|---|---|
| `mxbuild --loose-version-check` | `BUILD SUCCEEDED` | the project is still on the old version |
| `UPDATE _MetaData SET _ProductVersion` | the project reports the new version, and mxcli agrees | every unit is read against the wrong schema |
Neither prints a warning. The first is not hypothetical — it is what happened to
the project that reported this, and it took going back to look at the version
column to notice that a successful build had upgraded nothing.
## The check that decides everything
A `.mpr` is SQLite. The version lives in one table, `_MetaData`, with columns
`_FormatVersion`, `_ProductVersion`, `_BuildVersion` and `_SchemaHash`.
Use Python rather than the `sqlite3` CLI: `python3` ships with a `sqlite3`
module, while the CLI is frequently absent (it is not in the mxcli devcontainer).
```bash
mprhash() { python3 -c "import sqlite3,sys
print(*sqlite3.connect(sys.argv[1]).execute(
'SELECT _ProductVersion,_SchemaHash FROM _MetaData').fetchone(), sep=' ')" "$1"; }
mprhash app.mpr
```
```
11.13.0 {SHA256}5Fk35jOyzj+cWnJe9ZkGWQjMEzsge3nIzS2zxH9jp6M=
```
For the version alone, mxcli says it on connect — no SQL needed:
```
Connected to: /path/App.mpr (Mendix 11.14.0)
```
One `UPDATE` would make the project *claim* the new version. Whether that is an
upgrade or a corruption is decided by the **fourth column**:
1. Create a blank project at the target version (or use a project you already
have there) and read its `_SchemaHash`.
2. Compare it with the project's.
- **Same** → the model schema did not change. The version is a label.
- **Different** → the schema changed, so units stored against the old one
would be read against the new. A converter must run.
`_SchemaHash` is a property of the **Mendix version, not of the project** —
which is what makes this usable. Measured across two unrelated projects and two
machines: a 517-unit application and a blank app, both at 11.13.0, carry
byte-identical hashes, and the same holds at 11.14.0. So a reference value can be
recorded and reused rather than rebuilt each time.
```
11.13.0 {SHA256}5Fk35jOyzj+cWnJe9ZkGWQjMEzsge3nIzS2zxH9jp6M=
11.14.0 {SHA256}o9B9S8lorV9RD5gY9B6j1bJp4ALW87u4newnreIbRAg=
```
Two values are not a rule. Treat the table as a cache to check against, and
**re-derive the target version's hash from a blank project the first time you go
to a version that is not listed** — one command, and it is the only thing that
actually answers the question.
## Doing it
`mx convert` is the converter. It sits beside `mxbuild` in the toolset, and
mxcli does not wrap it.
```bash
# 1. Cache the target toolchain.
mxcli setup mxbuild --version 11.14.0 # ~/.mxcli/mxbuild/11.14.0/modeler/
# 2. Get the reference hash for the target version, if you do not have it.
mx create-project --app-name Ref --output-dir /short/path
mprhash /short/path/Ref.mpr
# 3. Convert a COPY first, never the project.
cp -a MyApp /short/MyApp-probe
mx convert --in-place /short/MyApp-probe
# 4. Verify the copy before touching anything real.
mprhash /short/MyApp-probe/App.mpr # must equal the reference from step 2
mx check /short/MyApp-probe/App.mpr
# 5. Only now, the real project.
mx convert --in-place MyApp
```
Converting a copy first, confirming its hash matches the reference, and only then
touching the real project is what makes this safe rather than lucky. Step 4 is
the whole point: it is the difference between "the conversion ran" and "the
conversion produced what the target version expects".
## Verifying MPR v2 survived
`mx convert` preserves MPR v2 — which is not a given. Its siblings
`mx update-widgets` and `mx rename-design-properties` collapse a v2 project into
a single-file v1 `.mpr` and delete `mprcontents/` as a side effect, one-way. That
is the whole reason `mxcli fix widgets` exists.
**Check the storage format, not the unit count.** Measured on an 11.13 → 11.14
conversion: `.mxunit` files went **391 → 386** while v2 was perfectly intact. A
conversion may legitimately drop or merge units, so an unchanged count is a
coincidence, not the invariant. What actually distinguishes preserved from
collapsed:
```bash
[ -d MyApp/mprcontents ] && echo "v2 intact" # a collapse deletes this
stat -c%s MyApp/App.mpr # v2: tens of KB. v1: tens of MB
```
On the measured run: `mprcontents/` present, `.mpr` 73,728 bytes.
## Traps
**`mx convert` takes the app DIRECTORY, not the `.mpr`.** Pointing it at the file
fails with a message that reads like the project is missing:
```
Conversion failed: The app directory '/path/App.mpr' does not exist.
```
**`--loose-version-check` suppresses the check, it does not run a converter.**
Verified: after invoking mxbuild with the flag, `_ProductVersion` and
`_SchemaHash` are untouched. A green build proves nothing about the version.
**A runtime version and its tooling move together.** The upgrade may break the
tools around it, and the failure need not mention a version at all. On
11.13 → 11.14 it surfaced as:
```
Error: bundling web client: no rollup.config.mjs in .../deployment/web
(run a serve Deploy build first)
```
— because 11.14's MxBuild bundles the web client itself, so the separate rollup
step has nothing left to configure. Deleting `deployment/` and rebuilding does
not help; a newer mxcli is what fixes it. **When something breaks right after an
upgrade and names no version, suspect the toolchain before the model.**
**`mx create-project` needs a short output path.** It fails with
`System.IO.PathTooLongException` under a deeply nested directory, during package
extraction — the message names a path length, not the real constraint.
## What generalises, and what does not
Most version-pair specifics will be wrong next time. The web-client bundling
change is 11.13 → 11.14 and will not recur; the two hashes above are facts about
two releases, not a pattern.
What generalises is the decision procedure — **`_SchemaHash` decides label vs
convert** — plus the three traps: convert takes a directory, `--loose-version-check`
upgrades nothing, and the tooling moves with the runtime. Check the specifics
against your own versions rather than trusting them.
## After the upgrade
Re-run whatever the project relies on that is not covered by `mx check`, because
a clean check is not evidence the app still works. On the reporting project the
thing worth being anxious about was a non-standard database connection type not
in Mendix's own picker; it survived, and a full sync cycle proved it. Pick your
own equivalent — the integration nobody would notice breaking — and exercise it.
## Related
- `run-local` — booting the app after an upgrade; the toolchain mismatch above
surfaces there first.
- `debug-bson` — if the converted model behaves oddly, and for why `mx convert`'s
bare error *count* is not evidence about a model.