> ## Documentation Index
> Fetch the complete documentation index at: https://docs.overlayed.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Delaying Overlay Initialization

> Wait to initialize your overlay until the game is ready.

By default, `overlayed()` initializes everything the moment you call it - which is usually at import time, since the
standard pattern is `export const overlay = overlayed({ ... })` in a shared module. Sometimes that's too early: you
want the overlay to stay dormant until Electron is ready, until the user has logged in, or until they've opted into
the overlay in your settings. This guide walks through splitting setup from initialization with `init: false`.

<Info>
  This guide covers the delayed-init workflow. For the option itself, see [Delayed
  Initialization](/advanced/delayed-init) and the [`init` option reference](/packages/overlayed-options#init).
</Info>

## How Delayed Initialization Works

Four things to know before writing any code:

1. **`overlayed()` configures; `init()` activates.** Normally `overlayed()` calls `init()` for you. With
   `init: false`, it validates your config and returns the overlay object without starting anything - no game process
   monitoring, no keybind listening, no windows, no events - until your code calls `overlay.init()`.
2. **Before `init()`, the overlay object is locked.** The only member you can touch is `overlay.init`. Accessing
   anything else (`overlay.windows`, `overlay.on`, even `overlay.initialized`) throws
   `overlayed was called before initialized: <property>`.
3. **`init()` is synchronous and one-shot.** It takes no arguments and returns nothing - when the call returns, the
   overlay is fully initialized and `overlay.initialized` is `true`. Calling it a second time logs a warning and does
   nothing.
4. **Delaying doesn't make you miss the game.** Game detection starts at `init()` and also picks up games that are
   *already running* at that point - see the
   [launch events guide](/guides/game-launch-close-events#faq). And `gameReady` supports late subscription: a
   listener attached after a game is ready is invoked immediately with that game's data.

## Splitting Setup from Initialization

<Steps>
  <Step title="Pass init: false">
    Keep your normal `overlayed()` setup - same modules, keybinds, and options - and add `init: false`. This stays
    safe to run at import time.

    ```typescript overlayed.ts theme={null}
    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",
    	modules: [Siege.module()],
    	keybinds: {
    		toggleMainWindow: {
    			keys: ["AltLeft", "KeyX"],
    			mode: "toggle",
    		},
    	},
    	init: false,
    });
    ```
  </Step>

  <Step title="Call init() when your app is ready">
    Call `overlay.init()` at the moment of your choosing. `init` is safe to pass around as a reference - it's the
    one member that works before initialization:

    ```typescript main.ts theme={null}
    import { app } from "electron";
    import { overlay } from "./overlayed.ts";

    app.whenReady().then(overlay.init);
    ```
  </Step>

  <Step title="Do all overlay work after init()">
    Register listeners and create windows only after `init()` has run. Since `init()` is synchronous, "after" just
    means later in the same function:

    ```typescript main.ts theme={null}
    import { app } from "electron";
    import { overlay } from "./overlayed.ts";
    import { AppWindow } from "./appWindow.ts";

    app.whenReady().then(() => {
    	overlay.init();

    	const appWindow = new AppWindow();
    	overlay.on("gameReady", () => appWindow.create());
    	overlay.on("gameClose", () => appWindow.destroy());
    });
    ```

    Even if the user launched the game before this code ran, you're covered: already-running games are detected
    once monitoring starts, and late `gameReady` subscribers are called immediately for games that are already
    ready.
  </Step>
</Steps>

## Choosing the Init Moment

Where you put the `overlay.init()` call is the whole design decision. Two common patterns:

<Tabs>
  <Tab title="On app ready">
    Initialize as soon as Electron is up. This keeps the overlay's startup out of module-evaluation time without
    changing when it's effectively available.

    ```typescript main.ts theme={null}
    import { app } from "electron";
    import { overlay } from "./overlayed.ts";

    app.whenReady().then(() => {
    	overlay.init();
    	startOverlayFeatures();
    });
    ```
  </Tab>

  <Tab title="Behind a user gate">
    Initialize only once your app decides the overlay should exist at all - after login, or based on an opt-in
    setting. If the user never crosses the gate, the overlay never starts.

    ```typescript main.ts theme={null}
    import { overlay } from "./overlayed.ts";
    import { userSettings } from "./settings.ts";

    async function onUserLoggedIn() {
    	if (!userSettings.overlayEnabled) {
    		return; // overlay stays dormant; enable later by calling initOverlay() then
    	}

    	initOverlay();
    }

    function initOverlay() {
    	overlay.init();
    	startOverlayFeatures();
    }
    ```

    <Tip>
      There is no "de-init". If the user can toggle the overlay off at runtime, keep the gate one-way - once
      initialized, hide your windows and ignore events instead of trying to shut Overlayed down.
    </Tip>
  </Tab>
</Tabs>

## Keeping Pre-Init Code Safe

The locked-object rule bites in two specific places:

<Warning>
  Any module-level code that touches the overlay runs at import time - before your deferred `init()` call - and
  throws. The two common offenders:

  ```typescript theme={null}
  // Throws at import time: property access on a pre-init overlay
  const { windows, keybinds } = overlay;

  // Throws at import time: registering listeners at the top level of a module
  overlay.on("gameReady", () => { ... });
  ```

  Keep module scope down to importing `overlay` itself, and move every property access and listener registration
  into functions that run after `init()`.
</Warning>

A layout that makes this hard to get wrong: `overlayed.ts` exports the un-initialized overlay, and a single
`startOverlayFeatures()` function owns all listener registration and window creation. Your entrypoint calls
`overlay.init()` and then `startOverlayFeatures()`, and nothing else ever touches `overlay` at module scope.

## Waiting for the Game Instead

The description of this guide says "until the game is ready" - but note that you cannot literally wait for the game
to launch before calling `init()`, because game detection itself doesn't start until `init()` runs. No `gameLaunch`
or `gameReady` will ever fire on an uninitialized overlay.

If what you actually want is "do nothing user-facing until the game is ready", you usually don't need `init: false`
at all. Initialize normally and lean on the built-in deferrals:

* Create windows in a `gameReady` listener - see
  [Game Launch & Close Events](/guides/game-launch-close-events).
* Hold off on consuming game events until you're ready: events queue per game module until you call
  `readyForGameEvents()` - see [Event Queuing](/essentials/game-events#event-queuing).

Reserve `init: false` for gating the overlay on *your app's* state (startup order, login, opt-in) rather than on the
game's state.

## FAQ

<AccordionGroup>
  <Accordion title="What happens if I never call init()?">
    Nothing starts: no process monitoring, no keybinds, no windows - none of the overlay's subsystems come up.
    The first time any code accesses an overlay member other than `init`, it throws
    `overlayed was called before initialized`. There's no timeout - the overlay waits indefinitely.
  </Accordion>

  <Accordion title="What happens if init() is called twice?">
    The second call logs the warning `Overlayed already initialized` and returns without doing anything. It won't
    throw and won't re-run initialization, so calling `init()` from two competing code paths is safe - just
    unnecessary.
  </Accordion>

  <Accordion title="Is init() async? How do I know it finished?">
    No - `init()` is synchronous and returns `void`. There is nothing to `await` and no "initialized" event; when
    the call returns, the overlay is ready and `overlay.initialized` reads `true`. Code on the next line can
    safely create windows and register listeners.
  </Accordion>

  <Accordion title="Can I pass different options to init()?">
    No. `init()` takes no arguments - all configuration (modules, keybinds, universal mode, feature flags) is
    fixed by the options you passed to `overlayed()`. If an option needs to depend on runtime state, resolve that
    state before calling `overlayed()` itself.
  </Accordion>

  <Accordion title="Do keybinds work before init()?">
    No. Keybind listening is part of initialization, like everything else - there's no partial mode where some
    subsystems run early. Windows, keybinds, game events, notices, and feature flags all become available together
    when `init()` runs.
  </Accordion>

  <Accordion title="Will I miss game events that happen while init is delayed?">
    You won't receive events from before `init()` - detection wasn't running, so none were produced for you. What
    you don't lose is the *session*: a game already running at `init()` time is still detected and goes through
    the normal launch flow. After that, in-game events queue per module until you call `readyForGameEvents()`, so
    your own startup code has slack too. See [Event Queuing](/essentials/game-events#event-queuing).
  </Accordion>

  <Accordion title="I got &#x22;overlayed was called before initialized: X&#x22; - what does it mean?">
    Some code accessed `overlay.X` before `overlay.init()` ran. The property name in the message tells you what
    was touched - search for that access. It's almost always module-level code: a top-level `overlay.on(...)`, or
    destructuring the overlay object at import time. Move the access into code that runs after `init()`.
  </Accordion>

  <Accordion title="Is &#x22;Overlayed metadata has not been initialized yet&#x22; the same thing?">
    No - despite the similar wording, that error is about your local development environment, not delayed init. It
    means the `.overlayed` metadata cache hasn't been created yet; run `overlayed init` (the CLI command) to set
    it up. See [Local Development](/essentials/local-development).
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Delayed Initialization Reference" icon="hourglass" href="/advanced/delayed-init">
    The init option in the advanced reference.
  </Card>

  <Card title="Game Launch & Close Events" icon="rocket" href="/guides/game-launch-close-events">
    React to gameLaunch, gameReady, and gameClose - including late subscription.
  </Card>

  <Card title="Game Events" icon="gamepad" href="/essentials/game-events">
    Event queuing and readyForGameEvents().
  </Card>

  <Card title="Overlayed Options" icon="gear" href="/packages/overlayed-options">
    Every option you can pass to overlayed().
  </Card>
</CardGroup>
