Skip to main content
When it’s time to deploy, overlayed bundle zips your project into two uploads: an app bundle (your Electron code) and a site bundle (your frontend). What lands in each zip is decided entirely by the app and site globs in your overlayed.config.ts. This guide walks through getting those globs right - pulling in the runtime dependencies your app needs, trimming out the files it doesn’t, and keeping the config working across development and CI.
This is the workflow companion to the OverlayedConfig reference (every field and its type) and Understanding Bundles (how bundles become releases). Read those for the full API surface.

How Bundling Reads Your Config

Three facts shape every decision below:
  1. There is no npm install on the server. The build only sees the files you bundle. If a runtime dependency isn’t matched by an include glob, it won’t exist in production - this is the single most common cause of a working dev build that crashes once released.
  2. app and site are bundled independently. Each has its own include, exclude, and baseDir. You can bundle just one at a time, and they can point at completely different directories.
  3. Globs are resolved relative to the config file - or to baseDir when you set it. package.json and node_modules/@overlayed/app are always added to the app bundle, and overlayed.config.ts is always excluded from both bundles (the app bundle also excludes any installer folder; the site bundle does not).
Why bundling works this way. Overlayed deliberately does not build your project for you - we never run npm install, and we do not build your site. You compile your Electron code and your frontend however you like, with whatever tooling and dependencies you choose, and the bundle ships exactly those files. That keeps your build pipeline fully in your control. The one thing we do build for you is the Electron app: your app bundle is turned into a signed installer for your users.

Configuring the Two Bundles

1

Start from a distribution-ready config

A complete config loads the applicationId from the environment and describes both bundles. This is the shape to work from:
overlayed.config.ts
Using loadEnv keeps the ID out of source control and lets the same file resolve a different application in CI versus locally. applicationId must be a valid ULID - defineConfig throws immediately if it isn’t.
The site bundle is only consumed at deploy time; during development your frontend runs from its own dev server. You can leave the site config in place year-round - it does nothing until you bundle.
2

Include your app's runtime dependencies

The app bundle needs your compiled output and every node_modules package your Electron process loads at runtime. List each one explicitly so unused dependencies don’t bloat the zip:
overlayed.config.ts
You never need to list package.json or node_modules/@overlayed/app - both are always added to the app bundle for you.
Native modules and anything your main process requires or imports at runtime must be included. A missing dependency won’t fail the bundle - it fails at launch in production with a module-not-found error.
3

Point the site bundle at your build output

Set baseDir to your frontend’s build directory so the pattern stays a simple **/* instead of repeating the prefix. Everything under baseDir is resolved relative to it:
overlayed.config.ts
A site bundle must contain at least one .html file - if the glob matches no HTML, overlayed bundle fails with Site bundle must contain at least one HTML file.
4

Bundle and verify

Run the CLI. With no flags it prompts for which bundles to build; the flags below target one directly:
Before uploading anything, inspect the zip locally with the --debug flag. In debug mode the CLI writes .overlayed/tmp/debug-app.zip and .overlayed/tmp/debug-site.zip instead of uploading, and logs each file it adds:
Open the zip and confirm your compiled code, dependencies, and package.json are all present and nothing secret or oversized slipped in.

Excluding Files

When a broad include is easier than enumerating everything, cast a wide net and trim it with exclude. It accepts a single glob or an array, resolved from the same baseDir as include:
overlayed.config.ts
This keeps the whole dependency tree while dropping the parts that never run in production. exclude works identically on the site bundle - for example, dropping **/*.map from a frontend build.
overlayed.config.ts is removed from every bundle automatically, and the app bundle also drops any installer folder for you. The site bundle does not - if a broad site include could match an installer directory, add an explicit exclude for it.

Reserved Files

A few names are reserved and will fail the app bundle if your globs pick them up:
  • An install folder at the bundle root (reserved for the installer).
  • The files meta.json and app-update.yml at the bundle root (reserved for internal use).
If you hit one of these, tighten the offending include pattern or add an exclude entry for it.

FAQ

Almost always a missing runtime dependency. The build server never runs npm install - only bundled files exist in production. Add every node_modules package your main process loads to the app include, then run overlayed bundle --debug and confirm it’s in the zip.
They’re equivalent for matching, but baseDir also sets where relative paths in the zip start. baseDir: "./out/renderer" with include: ["**/*"] bundles the contents of out/renderer at the zip root - ideal for a site whose index.html must sit at the top level.
No. The site bundle is only used when you deploy - your frontend runs from its own dev server locally. You still declare site in the config so overlayed bundle can build it when you ship.
Yes. Version resolution for @overlayed/app and @overlayed/electron defaults to ./node_modules relative to the app baseDir; point it elsewhere with nodeModulesDir, or take full control with resolvePackageVersion. See the reference.
Load it from the environment with loadEnv (shown above) and store the value in a .env file you gitignore, or in your CI secrets. The ID must still resolve to a valid ULID at bundle time.
Yes. The commit hash defaults to git rev-parse HEAD; override it - or omit it entirely by returning undefined - via resolveCommitHash in the config.
Your include matched no .html. Check that baseDir points at the built output (not your source directory) and that the build ran before bundling.

Next Steps

OverlayedConfig Reference

Every config field and its exact type.

Understanding Bundles

How bundles become releases and reach your users.

Overlayed Config

The config file’s role in the overall project.

Site Updates

Ship frontend changes without a new app release.