Skip to content

Plugin basics

Alexandre 'kidev' Poumaroux edited this page Oct 21, 2026 · 3 revisions

Plugin basics

A plugin is a folder in ProLeak/plugins/ holding a plugin.yml and Python files. The folder name is the plugin id. Your first plugin makes one step by step; this page describes each part.

ProLeak/plugins/my.wave-toast/
  plugin.yml        # the manifest
  main.py           # the entry file
  waves.py          # more modules, imported with relative imports
  vendor/           # pure-Python dependencies (optional)

Contents

plugin.yml

# ProLeak/plugins/my.wave-toast/plugin.yml
id: my.wave-toast
name: Wave toast
version: 1.2.0
author: me
description: Says what each wave is and how your value compares.
proleak: ">=0.1,<0.2"
entry: main.py
depends:
  - my.utils >=1.0
update:
  github: me/ltd2-wave-toast
settings:
  show_toast: {type: bool, default: true, label: Toast each wave}
Key Required Meaning
id yes Lowercase letters and digits, with single . or - between them; at most 64 characters.
name yes Display name.
version yes Semantic version of the plugin.
author yes Author name.
description no One line for the Mods menu.
proleak yes Range of ProLeak versions the plugin works with. Outside it the plugin is unavailable.
entry no Python file to import, main.py by default.
depends no List of <id> <range>. Dependencies load first.
update no github: owner/repo, where releases are published. A registry install checks it against the registry entry (see Publishing plugins).
settings no Settings declared in the manifest, as an alternative to settings.add (see Plugin settings).

ProLeak validates the manifest before running any Python. An unknown key, a missing required key or a bad value refuses the plugin, and the Mods menu shows the key and the reason.

Note

Two ids, one module

Two plugin ids can map to the same Python module name (a.b and a-b both become proleak_plugins.a_b). The plugin later in load order is then refused, with a reason naming the other one.

Loading and unloading

ProLeak imports the entry file inside the package proleak_plugins.<id> (with . and - in the id turned into _), so files in the plugin folder import each other with relative imports (from .waves import describe). A vendor/ folder inside the plugin goes on sys.path for pure-Python dependencies.

Two optional functions in the entry file run as the plugin loads and unloads:

def on_load(ctx):
    ctx.log.info(f"loaded from {ctx.dir}")


def on_unload():
    pass
ctx member Meaning
id The plugin id.
dir The plugin folder.
settings The plugin's settings, read as attributes (ctx.settings.show_toast) or with get, set and as_dict.
log The plugin's logger.
data_dir A folder the plugin may write to: ProLeak/data/<id>.

On unload ProLeak calls on_unload(), then removes everything the plugin registered, newest first: handlers, chat commands, timers, threads, UI and patches, so on_unload only has to close what the plugin opened itself, such as files. Unloading a plugin unloads its dependents first.

The Mods menu reloads, enables and disables plugins. proleak.plugins.reload() reloads the calling plugin at the next frame boundary; the handler that called it finishes first. In dev mode (dev: true in Settings) a plugin also reloads when a file in its folder changes.

Threads

Handlers, UI callbacks and timers run on the game's main thread. Game APIs, proleak.on, chat and ui work there only, and raise WrongThread elsewhere. log and tasks.call_soon_main work from any thread.

Long work goes to a worker thread with tasks.run_in_thread (see Chat, camera, input and more). Sleeping, waiting or blocking on a socket in a main-thread handler raises BlockingCallOnMainThread.

Time budgets and faults

Code Budget
Handlers, UI callbacks, timers 50 ms
on_load, on_unload 2 s

Past the budget ProLeak raises PluginTimeout inside the handler, lets the game continue with the event unchanged, and unloads the plugin as faulted. A player can raise one plugin's budgets in Settings.

An exception in a handler is logged with its traceback, and the event goes on as if that handler had done nothing. Five exceptions from one plugin within 10 s unload it as faulted.

Warning

What ProLeak cannot contain

A crash inside a C extension takes the game down with it. A loop that catches BaseException swallows PluginTimeout, and a call blocked inside native code sees the timeout only when it returns.

Logging

log.debug, log.info, log.warning and log.error write as the calling plugin to the BepInEx console, ProLeak/logs/<id>.log and the Logs tab of the Mods page. print and the logging module inside a plugin go to the same place.

from proleak import log

log.info("ready")

Previous: Your first plugin | Next: Handling events

Clone this wiki locally