---
title: "Building a Plugin · Docs · SourceAnt"
description: "Documentation: Extend the SourceAnt core with your own capture, enrichment, and automation, or swap in your own storage behind the knowledge and topology interfaces."
url: "https://sourceant.ai/docs/plugins/"
---

# Building a Plugin

SourceAnt's plugin system lets you extend the platform with custom integrations, authentication providers, LLM backends, and more.

## Overview

Plugins are Python classes that inherit from `BasePlugin`. Each plugin declares its type, metadata, and lifecycle hooks. The plugin manager handles discovery, dependency resolution, and lifecycle management automatically.

### Plugin Types

Every plugin must declare one of the following types via the `PluginType` enum:

-   **INTEGRATION.** External service integrations (GitHub, GitLab, etc.)
-   **AUTHENTICATION.** Auth providers (OAuth, SAML, etc.)
-   **LLM.** Language model backends
-   **WEBHOOK.** Webhook handlers
-   **NOTIFICATION.** Notification channels (Slack, Email, etc.)
-   **UTILITY.** Helper utilities

## Quick Start

Create a new directory under `src/plugins/` with a `plugin.py` file:

```
src/plugins/
  my_plugin/
    plugin.py
    __init__.py
```

Here's a minimal plugin:

```
from src.core.plugins import BasePlugin, PluginMetadata, PluginType


class MyPlugin(BasePlugin):

    @property
    def metadata(self) -> PluginMetadata:
        return PluginMetadata(
            name="my_plugin",
            version="1.0.0",
            description="A custom plugin",
            author="Your Name",
            plugin_type=PluginType.UTILITY,
        )

    async def _initialize(self) -> None:
        # One-time setup: register routes, connect to services
        pass

    async def _start(self) -> None:
        # Called after all plugins are initialized
        pass

    async def _stop(self) -> None:
        # Graceful shutdown
        pass

    async def _cleanup(self) -> None:
        # Final cleanup before unload
        pass
```

## Plugin Metadata

The `PluginMetadata` dataclass defines your plugin's identity and configuration:

| Field | Type | Description |
| --- | --- | --- |
| `name` | `str` | Unique plugin identifier |
| `version` | `str` | Semantic version (e.g., `"1.0.0"`) |
| `description` | `str` | Short description |
| `author` | `str` | Plugin author |
| `plugin_type` | `PluginType` | One of the plugin type enum values |
| `dependencies` | `List[str]` | Names of plugins this depends on (default: `[]`) |
| `config_schema` | `Dict` | JSON Schema for config validation (default: `None`) |
| `enabled` | `bool` | Whether the plugin is active (default: `True`) |
| `priority` | `int` | Load priority, lower = earlier (default: `100`) |

## Lifecycle Hooks

Plugins follow a strict lifecycle managed by the `PluginManager`:

1.  **Discovery.** The manager scans plugin directories for `plugin.py` or `__init__.py`
2.  **Loading.** Plugin classes are imported and instantiated
3.  **Registration.** Metadata is validated and the plugin is registered
4.  **Initialization.** `_initialize()` is called in dependency order
5.  **Startup.** `_start()` is called after all plugins are initialized
6.  **Shutdown.** `_stop()` is called in reverse dependency order
7.  **Cleanup.** `_cleanup()` runs before the plugin is unloaded

Override `_initialize`, `_start`, `_stop`, and `_cleanup` in your plugin. The base class methods (`initialize`, `start`, etc.) handle state tracking and call your implementations.

## Event System

Plugins interact with SourceAnt through hook points and event subscriptions.

### Hook Points

Register callbacks for built-in lifecycle events:

```
from src.core.plugins import event_hooks, HookPriority

# In your _initialize method:
event_hooks.register_hook(
    hook_name="before_review_generation",
    callback=self._on_before_review,
    plugin_name=self.metadata.name,
    priority=HookPriority.NORMAL,
)
```

Available hook points:

-   **App lifecycle.** `app_startup`, `app_shutdown`
-   **Webhooks.** `before_webhook_processing`, `after_webhook_processing`
-   **Code reviews.** `before_review_generation`, `after_review_generation`, `before_review_posting`, `after_review_posting`
-   **Authentication.** `before_user_authentication`, `after_user_authentication`, `before_user_logout`, `after_user_logout`
-   **Repositories.** `before_repository_authorization`, `after_repository_authorization`, `before_webhook_setup`, `after_webhook_setup`
-   **Errors.** `on_error`, `on_plugin_error`

### Event Subscriptions

Subscribe to domain-specific events for more granular control:

```
event_hooks.subscribe_to_events(
    plugin_name=self.metadata.name,
    callback=self._handle_event,
    event_types=[
        "pull_request.opened",
        "pull_request.synchronize",
    ],
)
```

## Hook Priority

Control the order hooks execute with `HookPriority`:

| Level | Value | Use Case |
| --- | --- | --- |
| `HIGHEST` | 0 | Security checks, validation |
| `HIGH` | 25 | Pre-processing, enrichment |
| `NORMAL` | 50 | Standard processing (default) |
| `LOW` | 75 | Post-processing, logging |
| `LOWEST` | 100 | Cleanup, analytics |

## Plugin Discovery

The `PluginManager` automatically discovers plugins by scanning registered directories. For a plugin to be discovered, it must:

1.  Be in a subdirectory of a registered plugin directory
2.  Contain either a `plugin.py` or `__init__.py` file
3.  Export a class that inherits from `BasePlugin`

Directories starting with an underscore are skipped.

## Dependencies

Declare dependencies in your metadata to ensure correct load order:

```
PluginMetadata(
    name="my_plugin",
    version="1.0.0",
    description="Depends on GitHub OAuth",
    author="Your Name",
    plugin_type=PluginType.INTEGRATION,
    dependencies=["github_oauth"],
)
```

The plugin registry uses topological sorting to resolve the dependency graph. Circular dependencies are detected and will raise an error at load time. A plugin's `_initialize` method is only called after all its dependencies have been initialized.

## Configuration Validation

Define a JSON Schema in `config_schema` to validate plugin configuration at load time:

```
PluginMetadata(
    name="my_notifier",
    version="1.0.0",
    description="Slack notifications",
    author="Your Name",
    plugin_type=PluginType.NOTIFICATION,
    config_schema={
        "type": "object",
        "properties": {
            "webhook_url": {"type": "string"},
            "channel": {"type": "string"},
        },
        "required": ["webhook_url"],
    },
)
```

Access validated config values in your plugin with `self.get_config("webhook_url")`.
