Skip to main content

Shipping Complex Qt Applications in the Browser with emscripten-forge

· 5 min read
Matthias Meschede
Scientific Software Engineer at QuantStack

Shipping software efficiently means not rebuilding everything from scratch every time. You want to ship your app as a package, rely on a distribution system to handle dependencies, and share the burden of maintaining your stack with other projects that depend on it. That is the vision behind emscripten-forge, a general-purpose software distribution for WebAssembly.

Whether you're building a scientific library, a compiler, or a desktop application, emscripten-forge can be used to:

  • ship your software as a package for a general-purpose package manager (like apt or conda),
  • leverage shared dependencies: no need to rebuild what's already available,
  • benefit from browser caching: shared objects stay cached, reducing load times and bandwidth.

Qt Applications in the Browser​

Emscripten-forge has been used extensively to ship the open-source scientific computing stack (Python, R, etc.) alongside JupyterLite, and is progressively expanding to compiler stacks and console applications.

Emscripten-forge can now build & host Qt applications that run entirely in the browser. A compatible Qt6 desktop app can be built once for WASM, published as an ordinary conda package, and launched directly from the package repository, client-side, with no additional server behind it.

This also unlocks libraries that link against Qt, a common pattern for libraries that are attached to GUIs but can also be used independently. Once compiled, these libraries can now run interactively in the browser, for example with notebook.link. See pygplates on notebook.link for a live example using pygplates, a Python wrapper of the C++ library underlying the popular plate tectonics desktop application GPlates, which uses Qt extensively. Such GUI-free use is stable, fast, and unlocks many interesting use cases.

Running full Qt applications in the browser, including GUIs, may require custom patches, as well as case-by-case examination for compatibility (e.g., not all Qt subpackages are available), stability, and performance. But it's now a conceivable path for anyone who wants to take proven desktop software and make it dramatically more accessible without rewriting it.

You can try the following examples by clicking the links below:

(These links require a recent browser with JSPI support: Safari 27+, Chrome 137+, Firefox 153+.)

Just a Package​

Pointing Emscripten at a single Qt C++ app and getting an executable .wasm file has been possible for years. Plenty of Qt-on-WASM demos already exist. What's new here is the distribution system: versioned artifacts, declared dependencies, and automatic rebuilds when dependencies change—all built and managed with the same CI machinery used for any other package.

On a native desktop machine, you can run apt install sqlitebrowser to obtain a package containing a binary executable. A Qt app shipped on emscripten-forge follows the same principle: it's just a package, shipping files, including a .wasm file, that you run with a small HTML wrapper page, such as the one hosted on emscripten-forge.org/qtapp.

The Qt app can use other packages from emscripten-forge at build and run time (and thousands of builds from conda-forge, which are platform-independent). This is crucial because many Qt-based apps rely on third-party libraries, each potentially with deep dependency trees, that all need to be built for WASM, resolved for compatible versions, and appropriately linked. This is exactly what a package manager does on a desktop machine. For example, GPlates pulls in Qt along with a substantial geoscientific C++ stack (GDAL, PROJ, etc.). Consuming it as packages that already exist, are already built, and are automatically rebuilt when their dependencies update is considerably simplified.

What emscripten-forge Offers Over Other Package Managers for the Browser​

The most popular package manager for browser-delivered artifacts is npm. Could you solve this problem with npm? You could resolve dependencies at build time, bundle everything into one blob, and host that blob. But what does a browser-native conda distribution like emscripten-forge give you that a vendored npm bundle doesn't?

1. Native Package Layout​

emscripten-forge uses a package model that understands native packaging. While npm is designed for JavaScript modules, emscripten-forge packages install into a POSIX-shaped prefix—bin/, lib/, include/, share/, etc/—the same convention native libraries expect for build or runtime. Qt plugins, for example, can drop files into lib/qt6/plugins/, and Qt will find them at runtime. A data package can place assets in share/. npm has no equivalent: each package lives in its own node_modules/<name>/ directory, with whatever internal structure the author chose. There is no notion of a standardized prefix layout.

2. Consistent ABI​

To make libraries like Qt, GDAL, Boost, or netCDF work together, they must adhere to the same conventions (compilation flags, emscripten versions) to ensure a compatible ABI.

Two libraries with incompatible ABIs will not link—or worse, they will link and crash in unpredictable ways. The emscripten-forge distribution ensures that packages work together by enforcing consistency across these parameters. npm has no vocabulary for this because it was meant for JavaScript, which does not have these constraints.

3. Build Machinery​

Emscripten-forge provides tools for testing, linting, and reviewing that all aim to keep the distribution consistent across all packages. CI is attached to manage builds and deployments. Bots automatically scan for updates, open and merge PRs. In the future, cascading rebuild campaigns across the dependency tree in the manner of conda-forge will be enabled.

Where This Is Going​

Try the other demos at emscripten-forge.org/qtapp, or open an issue on emscripten-forge/recipes if there is a Qt app you would like to see packaged.