Upgrading Chart Engine

The fixed order an upgrade has to run in — stop, unpack, rebuild the bundle, rebuild the frontend, start — and how to prove the site is actually serving the new build.

11 min readUpdated 6 August 2026upgrade, build, dist, licence, extension-manager

Chart Engine has no tables, no routes, no seeders and no cron jobs, so an upgrade touches nothing you have to migrate. What it has instead is the sharpest ordering constraint in the product: the app loads a built bundle, and the frontend build compiles that bundle into .next at build time. Do the same four steps in the wrong order and you get an install that is fully updated on disk, reports the new version in the admin panel, and serves the previous chart to every trader.

That is the failure this page exists to prevent. Everything else about a Chart Engine upgrade is uneventful.

The Install v… button on the product page extracts the ZIP over the project root and writes the new number into the extension row for product 61364182. It does not rebuild anything and it does not restart anything. The running Next.js server keeps serving the build it loaded at boot.

So the version badge in Extension Manager is evidence of what was unpacked, not of what the browser is running. Until the frontend is rebuilt and the platform restarted, the two disagree.

The order, and why each step is where it is

# Step What breaks if it runs later
1 pnpm stop Files are replaced under a running server, which has to be restarted afterwards anyway — and pnpm stop proves the ports are actually free before anything is written
2 Unpack the release The frontend build reads the addon off the filesystem while it loads its config — files that land afterwards were not in the build
3 pnpm build:chart-engine The bundle is rebuilt into dist/, which .next has already been compiled against — the new code sits on disk and in nothing the browser loads
4 pnpm build:frontend Nothing about the new addon reaches .next at all
5 pnpm start Started earlier, the site serves the pre-upgrade build until it is restarted again

frontend/next.config.js resolves Chart Engine while it is loading. It checks for components/(ext)/chart-engine/index.tsx or dist/index.js, bakes NEXT_PUBLIC_HAS_CHART_ENGINE into the client bundle as a literal "true" or "false", and aliases every import of @/lib/stubs/chart-engine-stub onto the package. The package's package.json points main, module, types and its exports map at ./dist/index.js.

Everything in that sentence is decided once, during pnpm build:frontend. Rebuilding the addon afterwards produces a correct dist/index.js that no page loads until the frontend is built again.

The upgrade

  1. Back up first. Nothing in a Chart Engine release touches the database, but the extractor writes over the project root and the release also carries core files. Take the usual set — the MySQL dump from /admin/system/database/backup, plus your own copies of .env, frontend/public/uploads/ and lic/. See Updating.

  2. Download the release. Open Admin → Extension Manager (/admin/system/extension, permission access.extension), pick Chart Engine, and press Install v… on the product page at /admin/system/extension/61364182. Four keys are involved and they are not interchangeable. access.extension opens the route. view.extension gates the product listing both screens are built from, and without it the product page does not render a permission error — it renders Extension not found, because the list it looks Chart Engine up in came back empty. Check for updates and Install v… both call routes gated on create.license, and the enable/disable switch on edit.extension. Grant all four, or none.

    From a shell instead, pnpm update-all downloads every pending release across the whole install and finalises once.

  3. Stop the platform.

    pnpm stop

    This removes the backend, frontend and cron PM2 apps and puts the maintenance server on their ports.

  4. Rebuild the addon bundle. A release ships dist/ pre-built, so this is required only when you have customised the addon's own source — but running it is the cheapest way to guarantee the bundle matches the source that was just unpacked.

    pnpm build:chart-engine
  5. Rebuild the frontend.

    pnpm build:frontend
  6. Bring it back up.

    pnpm start

Or let the update chain do it

pnpm updator is pnpm stop, then dependencies, then the schema migration boot, then the seeders, then pnpm build:frontend, then pnpm start — in that order, with each link stopping the chain if it fails. Because the release has already put the addon's dist/ on disk by then, the frontend build sees the new bundle and the ordering above is satisfied without you thinking about it.

Two things it does not do:

  • It does not run pnpm build:chart-engine. It does not need to for a stock release, which ships the bundle built. If you have customised the source, run that command yourself between pnpm stop and the chain, or run it and then pnpm build:frontend afterwards.
  • It does nothing for files you extracted by hand after the fact. A manual unpack done after a frontend build has none of the ordering guarantees the chain provides.

What pnpm build:chart-engine actually runs

cd frontend/components/(ext)/chart-engine && npm install && npm run build

Note the package manager. The addon is not a pnpm workspace project — pnpm-workspace.yaml lists frontend, backend and e2e, and nothing else. It keeps its own package-lock.json and its own node_modules directory inside the frontend tree, holding its build toolchain (tsup, typescript, rimraf). A root pnpm install never installs any of it; only that npm install does.

Two consequences worth knowing before you need them:

  • The first run on a fresh box needs the npm registry. On an air-gapped or firewalled server with no node_modules inside the addon directory, the command fails at npm install and you are dependent on the dist/ the release shipped. That is a working state, not a broken one — it is what most installs run.
  • npm run build clears dist/ before it writes. The tsup config sets clean: true, so a build that fails part-way can leave the package with no entry point at all. Do not run the frontend build until dist/index.js is back.

A successful run produces three files:

File What it is
dist/index.js The bundle. This is the package entry point and the only thing the app loads
dist/index.d.ts The type definitions, which is what turns a misspelled prop in a custom integration into a build error
dist/.build-manifest.json A record of the build — builtAt, sourceCount, and the sha256 of every source file it was built from

Proving you are on the new build

  1. Check the bundle on disk, with its timestamp:

    ls -l frontend/components/\(ext\)/chart-engine/dist/index.js

    The parentheses are part of the path and have to be escaped in a POSIX shell.

  2. Read the build manifest, if the release shipped one. builtAt is an ISO timestamp of when that bundle was produced, which is a better answer than a file mtime — an extraction or a fresh checkout restamps every file at once.

    head -3 frontend/components/\(ext\)/chart-engine/dist/.build-manifest.json
  3. Hard reload a trading page you have switched over — ::kbd[Ctrl+Shift+R] — and confirm the chart's own toolbar is there: timeframes, chart-type picker, and buttons for indicators, alerts, replay, heatmap and fullscreen.

  4. Check the browser console. This line means the build did not see the addon and the page fell back:

    [ChartSwitcher] Chart Engine addon not installed. Falling back to TradingView.

    The fix is a frontend rebuild, not a setting.

  5. Confirm both settings still read Chart Engine. binarySettings.display.chartType on /admin/finance/binary/settings and spotChartEngine on /admin/trading/settings. They live in the database and an upgrade does not touch them — but a licence that lapsed during the window does. See below.

Where the version numbers come from

The product page at /admin/system/extension/61364182 shows two figures that can legitimately disagree, plus a third list from somewhere else again.

Shown as Source Caveat
Current version The version column of the extension row for 61364182. Written by the download route when it finished extracting Says what was unpacked, not what was built or restarted
Latest version The licence service's answer to the update check The batch check behind the product list is cached for 10 minutes; the detail page's Check for updates button asks again
Recent releases The published release catalogue, fetched from the documentation site A separate service. The installed build is often not in this list at all

Updates are offered one version at a time: after a successful download the panel re-checks and offers the next one.

If the licence service is unreachable, the check is answered with the same "latest version" wording as a genuine up-to-date result, and a missing licence file answers No purchase code found under the same heading. Read the message line, not the heading.

Clear out what the release deleted

An update is extracted over the install. Files a release changed are overwritten and files it added appear, but files it removed are still sitting there — nothing in the pipeline deletes anything.

pnpm build:frontend runs scripts/clean-stale-files.mjs as its first step, so in the normal chain this happens for you. To look before it acts:

pnpm clean:stale --check   # preview only, changes nothing
pnpm clean:stale           # delete

Inside the addon directory the case that matters is a leftover module that shadows a directory beside it — a foo.ts left over next to the foo/ folder that replaced it. Module resolution prefers the file, so tsup would bundle last version's code and the resulting dist/index.js would be a hybrid of two releases. The script's shadow pass walks the whole of frontend/ (skipping node_modules, .next, .git, .turbo, coverage, public, messages and uploads), so it reaches inside the addon and removes exactly that.

What it does not remove is an orphan leftover that shadows nothing: the script's explicit retired-paths list names no Chart Engine files today. Those are inert for the running site — tsup only bundles what index.tsx reaches — but they do mean the package directory accumulates files from past releases.

When the script reports Kept N stale path(s): still imported on this install, it is telling you that something on disk still imports a module this release removed — which means that importer is an old copy the extraction did not replace. Re-extract the release over the install and run it again rather than deleting by hand.

The licence, after a version bump

Licence files in lic/ are not shipped in a release package, so an upgrade leaves them untouched and there is nothing to re-enter. Opening /admin/system/extension/61364182 calls the verify route on load (POST /api/admin/system/license/verify, permission create.license) — if the page renders the licensed product view with the version card, the licence is good.

Reactivation is only needed when the machine fingerprint changed, which is a server move rather than an upgrade — see Moving an install.

The licenseHeartbeat scheduled job runs every six hours. A revoked or expired result for product 61364182 is handled by writing configuration, not by raising a banner: binarySettings.display.chartType goes back to TRADINGVIEW, spotChartEngine goes back to TRADINGVIEW, and the settings cache is cleared so every process picks it up at once.

There is no notification and no audit entry naming the licence. If your charts are TradingView again after an upgrade and the build checks above all pass, the cause is the licence — and reactivating it does not restore your choice. Set both settings again. Administering Chart Engine has the detail.

What survives on the user side

All of it. Chart Engine stores nothing on the server, so there is nothing to migrate and nothing to back up. Every piece of a trader's arrangement lives in that browser's local storage, keyed by name rather than by build:

Key Holds Lifetime
binary-chart-state Per-symbol timeframe, zoom, chart type and expiry minutes Discarded 24 hours after it was last saved
binary-chart-indicators The indicator list — global to the browser, not per market Discarded 30 days after it was last saved
binary-chart-drawings-<SYMBOL> Drawings, one key per market No expiry
binary-chart-favorite-indicators Favourited indicators No expiry
chart-templates Saved templates No expiry

One upgrade-visible exception, and it is deliberate. Stored indicators carry a paletteVersion; when it is older than the current one the chart drops the colour keys as it loads them — the color on every style sub-object, and every parameter whose name ends in color, colour or colors — so an indicator picks up the themed palette instead of a hex value frozen at the moment it was added. Periods, sources, thresholds and line widths are kept.

Colours are not, and that includes the ones a trader picked by hand. The saved shape is id, type, params, style, isVisible and placement — nothing in it records where a value came from, so a custom colour is indistinguishable from a frozen palette one and goes with it. A trader reporting "my indicators changed colour after the update" is seeing that, once, and it does not recur — but any colour they had set deliberately has to be set again.

When it goes wrong

The download bumped the extension row and rebuilt nothing. Run pnpm build:frontend and pnpm restart, then hard reload. This is by far the most common report on this page.

The frontend was built before the files landed — or built while dist/index.js was missing, which a failed pnpm build:chart-engine will leave you with, since tsup clears the directory before it writes. Confirm dist/index.js exists, then rebuild the frontend.

The addon installs its own toolchain from the npm registry and is not covered by the workspace install. Check outbound access to the registry. If you cannot get one, skip the step: a stock release ships dist/ already built, and only a customised source tree needs rebuilding.

A leftover from a previous version. pnpm clean:stale --check shows it, pnpm clean:stale removes it, then rebuild. See the stale-files section above, and the same entry in Updating.

Then it is not the upgrade. An empty chart with a Retry button is a data problem — wrong market type, or a market with no candles — and belongs in Troubleshooting.