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

# Hook system — Intercept and extend launcher methods

## Hook system — Intercept and extend launcher methods

Through the Hook system (Hook system)a plugin can inject logic **before and after a launcher method runs**: modify arguments, modify the return value, block execution, or replace the implementation entirely. This suits feature enhancements, performance tuning, and logic reworking (for example: injecting custom JVM arguments at launch, adding virtual versions to version scanning, intercepting downloads).

## Execution model: a Koa-style onion pipeline

Each hook is a middleware function `(ctx, next) => Promise<void>`, executed as an **onion model**:

```text theme={null}
Plugin A hook ─► Plugin B hook ─► default implementation (impl) ─► Plugin B hook ─► Plugin A hook
   before                before                        after         after
```

* **before**: code before `await next()`, may modify `ctx.args` (arguments)
* **after**: code after `await next()`, may modify `ctx.result` (return value)
* **Block**: call `ctx.prevent()` to skip the default implementation and all later hooks
* **Replace**: `ctx.prevent()` plus a `ctx.result` value replaces the implementation entirely
* **Nesting**: multiple plugins run as an onion in registration order, **the first registered sits outermost**

## Permissions and declarations

### Permission

`hook:register` (**danger** level). The manifest `permissions` must include it, otherwise registration is rejected.

### manifest declaration (optional)

```json theme={null}
{
  "permissions": ["hook:register"],
  "contributes": {
    "hooks": [
      { "method": "scanVersions", "priority": 10 }
    ]
  }
}
```

The declaration documents intent and supports permission pre-checks; the actual handler is registered at runtime with `registerHook`.

## registerHook — register a hook

```js theme={null}
__PLUGIN_API__.registerHook('scanVersions', async (ctx, next) => {
  // before: modify arguments before next()
  ctx.args[0] = ctx.args[0] + '/custom'

  // continue to the default implementation (impl receives the modified args)
  await next()

  // after:fter: modify the return value after next()
  ctx.result = [...ctx.result, {
    name: 'virtual-1.20.1',
    gameVersion: '1.20.1',
    state: 'Available'
  }]
})
```

* Permission: `hook:register`
* Signature: `(method: string, handler: (ctx, next) => Promise<void>)`
* `method` is the name of a launcher method that can be hooked (see the table below)
* Disabling a plugin unregisters all of its hooks automatically

### The Context object

| Field | Description |
| - | - |
| `ctx.method` | Method name |
| `ctx.pluginId` | The current plugin ID |
| `ctx.args` | Argument array (modifiable in the before phase) |
| `ctx.result` | Return value (modifiable in the after phase) |
| `ctx.prevented` | Whether execution has already been blocked |
| `ctx.prevent()` | Block the default implementation and all later hooks |

## Hookable methods

| Method name | Location | Signature | Typical use |
| - | - | - | - |
| `launchInstanceFlow` | Frontend launch orchestration (RunningContext) | `(id, name, javaInfo?, quickJoin?)` | Intercept launching, inject arguments, block launching |
| `launchInstance` | Launch API | `(id, options?)` | Inject joinServer/accountUuid, modify the launch request |
| `syncScan` | Instance sync API | `(gameDir, versions)` | Modify scan results, filter or enrich the instance list |
| `scanVersions` | Version scan API | `(gameDir)` | Virtual versions, filter versions, custom loader detection |

## Typical examples

### Block launching a specific instance

```js theme={null}
__PLUGIN_API__.registerHook('launchInstance', async (ctx) => {
  const [id] = ctx.args
  if (id === 'blocked-instance') {
    ctx.prevent()
    ctx.result = { success: false, error: 'launch blocked by the plugin' }
  }
})
```

### Replace the implementation entirely

```js theme={null}
__PLUGIN_API__.registerHook('scanVersions', async (ctx) => {
  ctx.prevent()
  ctx.result = [
    { name: 'custom', gameVersion: '1.20.1', state: 'Available', loaders: [{ type: 'Fabric', version: '0.14.0' }] }
  ]
})
```

### Modify launch arguments (inject JVM arguments)

```js theme={null}
__PLUGIN_API__.registerHook('launchInstanceFlow', async (ctx, next) => {
  // the frontend orchestration stage attaches fields to javaInfo (exact fields depend on the launcher version)
  await next()
})
```

## Execution location and architecture

Plugin hook functions run inside the **plugin iframe sandbox**, while the default implementation (impl) runs in the main window. The two communicate over a two-phase bridge (before → impl → after), which preserves onion semantics across windows:

```text theme={null}
main window hookable call
  └─ HookRegistry.run(method, args, impl)
       ├─ hook(iframe): __plugin_hook_invoke → modify args → before_done
       ├─ impl(...args) → ctx.result
       ├─ __plugin_hook_continue(result) → resume hook after → modify result → after_done
       └─ return the final result
```

## Known limitations

* Only the frontend method layer is covered (methods exposed through the `hookable()` wrapper); hooks inside the Rust backend process are planned.
* Hooks of inline-rendered plugins (`render: "inline"`) run directly in the main window context (they share the same registry).
* L4 WebView plugins do not yet support cross-window registerHook.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.