Table of Contents
Modules - Lifecycle
A module goes through a fixed sequence of stages from startup to shutdown.
1. Discovery
On startup, built-in modules are discovered from the package, then the user modules directory is scanned for Python files and packages. Discovered modules with enabled: false in the config are skipped and do not progress beyond this stage.
If a user module has the same name as a built-in module, the user module takes precedence and the built-in is skipped. This allows you to replace any built-in module with your own implementation by placing a module with the same name in your modules directory. When an override is detected, Owlbot logs an informational message indicating that the built-in module will be skipped. User modules are resolved in order of package form (modules/<name>/__init__.py) first, then single-file form (modules/<name>.py).
2. Import
Modules are imported in alphabetical order. The module file is read and its top-level code runs, but services like storage and config are not available in this step. Avoid doing any real work outside of functions (network calls, database access, etc.). An unhandled exception during import will prevent the module from loading, but other modules will continue to load normally.
During this step, all @on_command, @on_event, and @on_route decorated handlers are registered across every module before moving on to the setup step.
3. Setup
If the module defines a function decorated with @on_setup, it is called with a ModuleContext. This is the first point where services are available, making it the right place for:
- Creating database tables
- Registering config defaults
- Dynamic handler, command, or route registration
- Any other one-time initialization
@on_setup is optional. Modules that only use decorator-based handlers and don't need storage or config can skip it entirely.
If @on_setup raises an exception, the module is fully cleaned up: all registered handlers, commands, and routes are removed, storage is closed, and the module is unloaded. Other modules are not affected and continue loading normally. Any storage operations that already completed (such as CREATE TABLE) will have been committed independently.
4. Runtime
Once all modules are loaded, webhooks from Owncast are accepted and events are dispatched to handlers. Modules remain in this stage until they are unloaded.
5. Teardown
When a module is unloaded, if it defines a function decorated with @on_teardown, it is called with the module's ModuleContext. This is the place to clean up external resources like open connections or background tasks.
@on_teardown is optional. Most modules don't need it since storage connections and registered handlers are cleaned up automatically in the next step.
6. Cleanup
After teardown completes, all commands, event handlers, and routes registered by the module are removed. No manual cleanup is needed for these.
Built-in Modules
Creating New Modules