CLI tool
nwutils/Web2Executable avatar
nwutils/Web2Executable

Web2Executable: turning a web app into a desktop binary with NW.js

Uses NW.js to generate "native" apps for already existing web apps.

973 stars125 forksPythonMIT

At a glance

What is it?
Web2Executable wraps an existing Nodejs or HTML application in an NW.js runtime and exports it as a standalone executable for Windows, macOS and Linux from a single machine, with a GUI for the interactive case and a command line tool for automated builds. The project is in maintenance mode, which is the first thing to weigh before adopting it.
Who is it for?
Adopt Web2Executable when you have a web codebase that needs to ship as a desktop binary, you are willing to bundle an NW.js runtime with it, and you can accept a tool in maintenance mode that the maintainer asks contributors to help. It is the wrong tool for a project that needs code signing, auto-updates or a small installer, because none of that is in the documented feature set, and the wrong tool for a codebase that already targets Electron.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A web codebase in a desktop shell, exported from one machine

The pitch is in the repository description: uses NW.js to generate native apps for already existing web apps. Web2Executable is a command line and GUI application that transforms a Nodejs app, or any other JavaScript and HTML app, into an executable that runs in a contained desktop-like style, and it can export to macOS, Windows and Linux all from one platform, so there is no need to buy expensive hardware to build for each target.

The underlying engine is NW.js, the Chromium and Node.js hybrid, and the interface is written in PySide. The README calls it open source and easy to use, and the important architectural point is that it does not require you to change your application. The project you already have, with its HTML entry point and its npm dependencies, is what gets packaged.

That is what makes the tool interesting for a particular kind of team. A Phaser game, a small internal dashboard, a tool with a web front end, a piece of courseware: anything that is already a web app and needs to be handed to people as a program they double click, without them installing a browser plugin, a runtime or a Python stack.

The feature list reads like a packaging wishlist assembled over years. Cross platform export to Mac, Windows and Linux. Working media out of the box, sound and video. A streamlined workflow from project to working standalone executable. Compatibility with Phaser, and the expectation that other HTML5 game libraries work too. The ability to specify which node-webkit version to download, which is how the tool pins the runtime it bundles. Automatic insertion of icon files into Windows executables and Mac apps by filling out the icon fields as needed. A command line utility with functionality equivalent to the GUI, which is the part that matters for automated builds. And compression of the executables with upx.

The performance claim in that list, that it runs with the same performance as Google Chrome, follows from the architecture rather than from any optimization: the application runs inside Chromium.

Maintenance mode, and what it means for a packaging tool

The first thing in the README is a note that deserves to be the first thing in any evaluation. The repository is in maintenance mode, bug fixes will be provided on a best effort basis, and users are asked to consider contributing back.

That is a meaningful statement for a build tool, more than for a library, because a packaging tool sits on the critical path of a release. When something breaks, the answer is not to open an issue and wait, it is to fix it yourself or to pin a version that works.

The release record matches the notice. The tags are v0.7.2b from 2022-06-12, v0.7.1b from 2019-09-14 and v0.7.0b from 2019-09-13, and the b suffix has been part of the naming for years. Between the 2019 and 2022 tags there is a gap of nearly three years, which tells you the project has always moved in bursts rather than on a schedule.

The code, however, has not been left alone. The last push was on 2026-10-02, and the dependency list in requirements.txt is current rather than archaeological: pyside6 at 6.11.2, pyinstaller at 6.22.3, pillow at 12.3.0, requests at 2.34.2, pytest at 9.1.1, ruff at 0.16.9 and black at 26.5.1, all pinned exactly. Someone is keeping the tool buildable against current libraries even while declaring that new features are not the goal. That is the profile of a maintained-in-place project rather than a neglected one, and it is a better shape than the release tags alone would suggest.

What a prospective user should take from this is that the tool works, the dependency base is current, and the bug response time is on the maintainer's schedule.

The command line tool and the flags that matter

The command line path is the one to automate, and the README describes it as functional equivalent to the GUI, useful for automated builds.

The entry point is command_line.py, and the dependencies for that path are configobj, installed with pip, and Pillow if you want icon replacement, which is optional. Running the script with the --help option lists the export options, and there are prebuilt binaries for Mac and Windows in a command_line_builds folder of the repository for anyone who would rather not install Python.

The example invocation shows the shape of the whole interface:

shell
python3.4 command_line.py /var/www/html/CargoBlaster/ --main html/index.html --export-to linux-x64 windows mac --width 900 --height 700 --nw-version 0.10.5

The arguments map one to one onto the decisions a user makes in the GUI. The first positional argument is the project folder, the one containing at least one HTML or PHP file. The main flag points at the entry point inside it, and the export-to flag takes one or more target platforms, here linux-x64, windows and mac from a single run. The width and height set the window size, and the nw-version flag pins which node-webkit build to download and bundle, which is the setting that decides what runtime your users will be running.

The version pin deserves attention in a build tool. Because the runtime is downloaded rather than built, a version that is no longer published upstream cannot be fetched, and the tool's behaviour then depends on a mirror you do not control. Pinning a version you have tested is the right habit, and reading the export log for what it actually downloaded is the check worth doing on the first build of a release.

The project folder also determines the output name, which the GUI explains: the name of the export application is autogenerated from the folder you choose, so you change it if you want something else. A build script that wants a stable artifact name therefore has to rename the folder.

The GUI, its submodules and the export checklist

The graphical version is the interactive path, and the setup instructions are three commands. The dependencies first:

shell
pip install -r requirements.txt

Then the submodules, which the repository needs because it ships them as a .gitmodules relationship rather than vendoring them:

shell
git submodule update --init --recursive

And then the application itself:

shell
python3.4 main.py

The export procedure inside the GUI is described as a four-step checklist, and the steps are worth reading because they encode the validation rules. You choose a project folder with at least one HTML or PHP file, and the export application name is generated from that folder. You modify the options as needed. You choose at least one export platform, and only then does the Export button become enabled, which happens as long as the fields marked with a star are filled in and all the files named in the fields actually exist. Finally you click Export, and then the Open Export Folder button takes you to the output.

That validation rule, the starred fields and the existence check, is the GUI's version of what the command line does with its flags. It is a small thing, but it tells you the tool checks its inputs before it starts downloading runtimes, which saves a long wait followed by a failure.

The prebuilt binaries are the other option, and the README is specific about them: for Windows, Mac or Ubuntu there are no dependencies or extra applications needed, you download, extract and double click the app, exe or binary, and this applies to both the command line version and the GUI version. There is a caveat about Windows, where some people report needing the Microsoft Visual C++ 2008 or 2010 SP1 redistributable, with the error message being a side by side configuration complaint. The maintainer is careful to say they have no machine to test it, which is the honest way to report an unverifiable claim.

Icons, upx and the other Electron-shaped alternative

Two small features have real value in distribution, and one alternative deserves a note.

Icons are inserted automatically into the Windows executables and the Mac apps by filling out the icon fields, and the icon replacement requires PIL or Pillow, which the building-from-source section mentions alongside the PySide requirement. That matters because an unsigned, iconless binary on Windows is a poor first impression, and a tool that handles the resource section for you saves a step that is fiddly to do by hand.

Compression with upx shrinks the executables, which for a bundled Chromium runtime is a large saving on download size.

The alternative is named in the README. If you want to export using Electron instead of NW.js, the recommendation is Electrify, described as the author's other app based on Web2Executable. That is the honest comparison to make: the two tools share a lineage and an author, differ in the runtime they bundle, and the choice between them follows from what your application already uses. An Electron project packaged with an NW.js runtime gains nothing, and an NW.js project packaged with Electron loses the node-webkit version pinning that the --nw-version flag provides.

There is also a section of planned features that are not implemented: the ability to add external files to the project, and minifying JavaScript and HTML. The second of those matters for a production build, because an unminified bundle shipped inside a desktop app is larger than it needs to be, and the workaround is to minify before handing the project over.

Where a wrapper like this stops being enough

The first limitation is what a packaged web app is: a Chromium process with your content in it. That gives you the performance of a browser and the memory footprint of a browser, and a user who opens five of your app instances opens five browser engines. The README's claim of Chrome-equivalent performance is a fair description and also a warning, because it means the performance ceiling is the one you already know from the browser.

The second limitation is the distribution work that the tool does not do. There is no documented code signing, no notarisation, no auto-update mechanism and no installer builder. The exports are executables and app bundles, which is enough to hand someone a file, and not enough to publish through a store or to deliver updates without re-releasing the whole thing. For an internal tool that is fine. For a consumer application with an update channel, it is the bulk of the work.

The third is the runtime download. The tool fetches a node-webkit build, which means a build depends on an upstream artifact being available, and a pinned old version is the least likely to be available. The NW.js version you ship is also the version of Chromium your users get, with its own security posture, and the tool offers no mechanism to patch it other than exporting again.

The fourth is the platform honesty of the export. Producing a macOS application from Linux means a bundle that has not been through Apple's signing and notarisation, and the README does not claim otherwise, but the promise of export to all three platforms from one machine can be read as more than it is.

The fifth is the mode the project is in. Maintenance mode with best effort fixes, a maintainer asking for contributions, and no feature work means that a team that needs a specific capability will implement it themselves. For most users that is the correct trade, because the packaging itself is the hard part and it is already solved.

Licence, the Python version drift and the shape of the source

The licence is MIT, stated in the README and carried in a LICENSE.md file at the repository root. For a packaging tool that you may well fork, that is about the most permissive arrangement available. This describes the published terms rather than legal advice.

There is a documentation drift worth noticing. The building-from-source section says the requirements are the PySide library and Python 3.4.3 or higher, and the command examples use python3.4, but the pinned dependency list names pyside6, which is the Qt 6 binding and a different package from the PySide the text refers to. The examples in the README are therefore older than the code, and a reader who follows them literally will be installing the wrong package. The dependency list is the reliable source, since it is pinned to exact versions and clearly current.

The source layout is a small Python application rather than a package with a modern entry point. There is main.py for the GUI and command_line.py for the CLI, a config.py and a utils.py for support code, util_classes.py for the data structures, a pe.py module whose purpose the README does not explain, an image_utils directory for the icon work, a files directory, a scripts directory, a tests directory, and a requirements.txt with everything pinned exactly. There is no pyproject.toml and no src layout, so the tool is run as scripts from a checkout rather than installed as a library, which is consistent with the PyInstaller usage in the dependency list for producing the prebuilt binaries.

The project also asks for structured bug reports, with feature requests titled in a Feature form and complaints titled with Annoyance as the first word, and it points at a FAQ wiki and a screenshots wiki for support. A wiki-based support model and an issue template convention are small signs of a project that has been running for a decade, and both are the kind of thing you notice when a tool has real users who came for the answers rather than for the code.

Editorial conclusion

Adopt Web2Executable when you have a web codebase that needs to ship as a desktop binary, you are willing to bundle an NW.js runtime with it, and you can accept a tool in maintenance mode that the maintainer asks contributors to help. It is the wrong tool for a project that needs code signing, auto-updates or a small installer, because none of that is in the documented feature set, and the wrong tool for a codebase that already targets Electron. Before starting, check whether the NW.js version you pin still downloads, confirm that the exported binary runs on a clean machine without the Visual C++ redistributable, and plan for the source build rather than the 2022 binary.

Frequently asked questions

What does Web2Executable do?

It transforms a Nodejs app, or any other JS and HTML app, into an executable that runs in a desktop-like style, powered by NW.js, and it can export to macOS, Windows and Linux from a single platform. The interface is written in PySide and the project is open source under the MIT licence.

How do I export a web app with Web2Executable from the command line?

Run command_line.py with the project folder, the main entry point and the export targets, for example python3.4 command_line.py /var/www/html/CargoBlaster/ --main html/index.html --export-to linux-x64 windows mac --width 900 --height 700 --nw-version 0.10.5. The --help option lists all export options.

Is Web2Executable still maintained?

The README states the repository is in maintenance mode, with bug fixes on a best effort basis and contributors invited. The last release tag is v0.7.2b from 2022-06-12, but the last push was on 2026-10-02 and the pinned dependencies are current, so the code is kept buildable.

What does Web2Executable need to run from source?

The dependencies are installed with pip install -r requirements.txt, the submodules are initialised with git submodule update --init --recursive, and the GUI is started with python3.4 main.py. The dependency list names pyside6 for the interface, and Pillow is needed only if you want icon replacement.

Can I use Electron instead of NW.js with Web2Executable?

Not with this tool. The README points to Electrify, the author's other application, for Electron exports. The choice matters because the nw-version flag pins which node-webkit runtime is downloaded and bundled, and that option does not exist in an Electron based flow.

Official sources

  1. Issues
  2. License: MIT
  3. nwutils/Web2Executable on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nwutils-web2executable.svg)](https://hysenlabs.com/projects/nwutils-web2executable)

Was this useful?