Skip to main content
This guide takes you from an empty folder to a running Overlayed app with a window on screen. Along the way it explains what each piece is for - the two config files, the CLI, the .overlayed directory - and gives you a checkpoint at each step so you know it worked before moving on.
Already comfortable with Electron and just want the commands? The Quickstart is the condensed version of this flow. For the architecture behind it, see App Structure.

What an Overlayed App Is

Four facts frame everything below:
  1. It’s your Electron app with Overlayed inside. There is no scaffolding command and no framework to adopt - you start from a plain Electron project, install @overlayed/app, and call overlayed() once at startup. That call returns the overlay object the rest of your code uses.
  2. Your UI is a website, not local files. The Electron side creates windows and loads your frontend from a URL - a local dev server while developing, https://your-site.overlayedapps.com in production. Never file://.
  3. It runs on a custom Electron build. @overlayed/electron provides the ogg-electron command - use it anywhere you would normally run electron. Your imports (app, BrowserWindow, etc.) still come from electron as usual.
  4. Local development is tied to a registered application. overlayed() requires an applicationId from the Overlayed Dashboard, and the CLI caches your app’s metadata locally in a .overlayed directory before the app can start.
If you’d rather follow along from working code, the example apps repo is a good starting place for this guide.

From Empty Folder to Overlay Window

1

Create a bare Electron project

Start with a minimal TypeScript Electron project. Two files matter: a package.json that runs the app through ogg-electron, and a tsconfig.json that compiles src/ to dist/.
{
	"name": "my-overlay",
	"version": "0.0.1",
	"type": "module",
	"main": "dist/main.js",
	"scripts": {
		"start": "tsc && ogg-electron ."
	}
}
{
	"compilerOptions": {
		"module": "NodeNext",
		"moduleResolution": "NodeNext",
		"strict": true,
		"skipLibCheck": true,
		"outDir": "dist"
	},
	"include": ["src"]
}
Keep "type": "module". @overlayed/app is ESM-only and cannot be loaded as CommonJS - a CJS build fails with Cannot use import statement outside a module. See Understanding Bundles for the build-format rules.
Prefer to start from something working? Clone one of the completed example apps instead and follow along from step 3.
2

Install the Overlayed packages

Install the two runtime packages into your project, pinned to exact versions, and the CLI globally:
pnpm i @overlayed/app @overlayed/electron --save-exact
pnpm i -g @overlayed/cli
npm i @overlayed/app @overlayed/electron --save-exact
npm i -g @overlayed/cli
  • @overlayed/app - the runtime: the overlayed() function and everything on the overlay object.
  • @overlayed/electron - the custom Electron build and the ogg-electron command your start script uses.
  • @overlayed/cli - the overlayed command used for authentication, initialization, and later bundling.
3

Authenticate with the platform

The CLI (and your app, in development) talk to the Overlayed API, so authenticate once per machine:
overlayed login
This opens the dashboard page for creating an API key in your browser; paste the key back into the prompt, and it’s stored locally. Checkpoint: overlayed whoami prints the email you’re logged in as.
You’ll need access to the Overlayed Dashboard to create an API key and an application. You can apply for access here.
4

Add the two config files

An Overlayed project has two configuration touchpoints. src/overlayed.ts is your app’s runtime setup - it calls overlayed() and exports the overlay object everything else imports. overlayed.config.ts at the project root is read by the CLI for overlayed init and, later, bundling.
import { overlayed } from "@overlayed/app";
import * as Siege from "@overlayed/app/siege";
import electron from "electron";

export const overlay = overlayed({
	electron,
	applicationId: "YOUR_APPLICATION_ID", // From the Overlayed Dashboard
	modules: [Siege.module()],
	keybinds: {
		toggleMainWindow: {
			keys: ["AltLeft", "KeyX"],
			mode: "toggle",
		},
	},
});
import { defineConfig } from "@overlayed/app";

export default defineConfig({
	applicationId: "YOUR_APPLICATION_ID", // Must match src/overlayed.ts
	app: {
		include: [], // Only needed for bundling - configure later
	},
	site: {
		include: [],
	},
});
  • applicationId comes from the Overlayed Dashboard and must be the same in both files.
  • modules subscribes you to specific game events (in this example, Rainbow Six Siege). Alternatively pass universal: true to react to every supported game. See OverlayedOptions for every option.
  • keybinds is required; this one is a placeholder you can wire to a window later - see Show/Hide Window Keybinds.
5

Initialize the dev environment

overlayed init
This reads the applicationId from overlayed.config.ts, fetches your application’s metadata from the API, and caches it in .overlayed/meta.json. Your app reads that cache at startup during development.Add .overlayed to your .gitignore - it’s fetched data, not source. Details in Local Development.
6

Create the entry point and a window

All electron windows must be created through overlay.windows, but they return the BrowserWindow that you’re used to.
The fastest way to see something on screen. createWindow returns a regular Electron BrowserWindow, so it appears on your desktop immediately - no game required.
src/main.ts
import { app } from "electron";
import { overlay } from "./overlayed";

app.on("ready", () => {
	const window = overlay.windows.createWindow({
		width: 800,
		height: 600,
	});

	// Any URL works for a first smoke test - remember to
	// point this at your frontend dev server later.
	window.loadURL("https://google.com");
});
7

Run it

pnpm start
npm start
If you skipped overlayed init, the very first run behaves differently: Overlayed detects the missing .overlayed cache, fetches and caches the metadata itself, then exits on purpose with a message explaining why. Just run the start script again - this only ever happens once, and never in a packaged app.Checkpoint: the app stays running and your window appears (on the desktop, or in-game once your subscribed game is ready). That’s your first Overlayed app.

Loading Your UI the Right Way

The URL you pass to loadURL should differ between development and production. In development, load your local frontend dev server; in production, load the site URL Overlayed deployed for you:
const siteUrl = import.meta.env.DEV
	? new URL("http://localhost:5173") // Your frontend dev server
	: overlay.windows.getProductionSiteUrl();

window.loadURL(siteUrl.toString());
Avoid loading the production URL (https://your-site.overlayedapps.com) during local development - it requires release headers that only exist in deployed builds, and you’ll get a “Configuration Required” page instead of your UI. See Local Development.
This split is what makes the website-based architecture pay off: once deployed, you can ship UI fixes without your users reinstalling anything.

FAQ

No. The CLI’s commands are login, logout, whoami, init, and bundle - none generate a project. Start from any Electron project (the Electron quick start works) or clone a completed example app.
Not for most work. Out-of-game windows (createWindow), keybind config, logging, and your frontend all work without a game. You need a subscribed, running game only for the in-game pieces: gameReady, createInGameWindow, and game events. See the supported games list.
That’s the automatic first-run initialization. Without .overlayed/meta.json, the app fetches your application metadata, caches it, and exits so the next run can load the cache - run it again and it starts normally. If it exits with “Failed to fetch application metadata” instead, run overlayed login first, then overlayed init.
They serve different consumers. overlayed() in src/overlayed.ts configures your app at runtime (modules, keybinds, flags). overlayed.config.ts is read by the CLI - overlayed init takes the applicationId from it, and overlayed bundle uses its include globs when you deploy. Keep the applicationId identical in both.
Overlayed initializes once - after the first call, subsequent calls return the same instance. The convention is a dedicated src/overlayed.ts that exports overlay, and every other file imports from there.
Yes, with no extra changes - though Overlayed is built TypeScript-first, and typed access to options, events, and keybind names is a big part of the experience.
In order:
  1. Subscription: gameReady only fires for games passed via modules (or with universal: true).
  2. Timing: for freshly launched games there’s a deliberate delay before injection - gameReady does not fire the instant the game starts.
  3. inGameRenderingSupported: when it’s false for the session, in-game rendering isn’t available and you should fall back to a desktop window.
Game Launch & Close Events walks through all three.
The preload script didn’t expose the Overlayed globals to your frontend. Make sure your window’s preload script imports @overlayed/app/preload. See the troubleshooting section in Local Development.
Fill in the include globs in overlayed.config.ts, run overlayed bundle to upload an app bundle and a site bundle, and create a release for each in the dashboard. Understanding Bundles covers the whole pipeline.

Next Steps

Show/Hide Window Keybinds

Wire the keybind you declared to your window’s visibility.

Game Launch & Close Events

Create and tear down windows around the game’s lifecycle.

Creating Windows

In-game vs. out-of-game windows and the windows API.

Understanding Bundles

Bundle and release your app when it’s ready to ship.