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

# Author code plugins

> The four manifest-wired code plugin types.

Author the lightweight code plugin types — routes, middleware, lifecycle handlers, and pooled clients.

These four are the smallest kinds of extension. Three of them — routes, middleware, and lifecycle handlers — share one pattern: a module the manifest imports, plus a registration decorator that runs on import. The fourth — a pooled client — is a code helper you open from within one of those modules. All register through the `tai42_app` handle and depend on `tai42-contract` only.

<Note>
  These are **server-side** code plugins — they run in the Python process. To extend the browser workbench instead — contribute pages, tool panels, or sidebar nav entries to the Studio shell — see [Author a Studio plugin](/studio/plugins).
</Note>

## Routes

Add an HTTP route with `@tai42_app.http.custom_route`. It registers a Starlette handler and its self-describing OpenAPI metadata in one call — `summary`, `tags`, and `response_model` are required so the route describes itself to the API reference.

```python examples/code_plugins/routes.py theme={null}
"""A minimal fictional HTTP route plugin.

``@tai42_app.http.custom_route`` registers a Starlette handler and its
self-describing OpenAPI metadata in one call — ``summary``, ``tags``, and
``response_model`` are required so the route describes itself to the API
reference. Load the module under ``routers_modules``.
"""

from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from tai42_contract.app import tai42_app


@tai42_app.http.custom_route(
    "/api/weather/regions",
    methods=["GET"],
    summary="List available weather regions",
    tags=["weather"],
    response_model=None,
)
async def regions(request: Request) -> Response:
    return JSONResponse({"data": ["north", "south"]})
```

Load the module under `routers_modules`. A packaged plugin can also ship this route as a `router` item in its [`tai-plugin.yml`](/guides/authors/plugin-descriptor), so the marketplace installer wires it in.

## Middleware

Add ASGI middleware with `@tai42_app.http.middleware`, which registers the middleware onto the app's stack.

```yaml manifest.yml theme={null}
middlewares_modules:
  - your_package.middleware
```

A packaged plugin can also ship middleware as a `middleware` item in its [`tai-plugin.yml`](/guides/authors/plugin-descriptor).

## Lifecycle handlers

Run code at process startup, shutdown, or after an in-place reload. Register a handler with `@tai42_app.lifecycle.on_startup`, `on_shutdown`, or `on_reload`. An `on_reload` handler re-runs after every reload — use it for dynamic loaders that `on_startup` ran once.

```python examples/code_plugins/lifecycle.py theme={null}
"""A minimal fictional lifecycle plugin.

Register a handler with ``@tai42_app.lifecycle.on_startup``, ``on_shutdown``, or
``on_reload`` to run code at process startup, shutdown, or after an in-place
reload. An ``on_reload`` handler re-runs after every reload — use it for dynamic
loaders that ``on_startup`` ran once. Load the module under ``lifecycle_modules``.
"""

from tai42_contract.app import tai42_app

_warm: dict[str, str] = {}


@tai42_app.lifecycle.on_startup
def warm_caches() -> None:
    _warm["region_index"] = "loaded"


@tai42_app.lifecycle.on_reload
def reload_caches() -> None:
    _warm["region_index"] = "reloaded"
```

Load the module under `lifecycle_modules`. This is also how import-only registrations — a webhook verifier, a connector provider — reach the process.

## Pooled clients

A pooled client shares one connection per event loop across tool calls. Subclass the kit's `PooledClient` (which implements the `BaseClient` contract), then open it through `tai42_app.clients.client_ctx` from inside a tool, route, or lifecycle handler. The context manager yields a connected client, pooled per loop and connection params (or one-shot with `fresh=True`).

```python examples/code_plugins/pooled_client.py theme={null}
"""A minimal fictional pooled client.

A pooled client shares one connection per event loop across tool calls. Subclass
the kit's ``PooledClient`` (which implements the contract ``BaseClient``), then
open it through ``tai42_app.clients.client_ctx`` from inside a tool, route, or
lifecycle handler. The context manager yields a connected client, pooled per loop
and connection params (or one-shot with ``fresh=True``).
"""

from tai42_contract.app import tai42_app
from tai42_kit.clients import PooledClient


class GeoConnection:
    """The live connection object the pool hands out."""

    async def lookup(self, name: str) -> str:
        return f"region:{name}"


class GeoClient(PooledClient[GeoConnection]):
    async def _create(self, **kwargs: object) -> GeoConnection:
        return GeoConnection()

    async def _close(self, client: GeoConnection) -> None:
        return None


async def resolve_region(name: str) -> str:
    async with tai42_app.clients.client_ctx(GeoClient) as connection:
        return await connection.lookup(name)
```

A client is not wired by a manifest field of its own — it rides into the process through whichever module imports it, and is opened via `client_ctx` at call time.

## See also

* [The manifest](/concepts/manifest) — the `routers_modules`, `middlewares_modules`, and `lifecycle_modules` sections.
* [Live operations](/concepts/live-operations) — the reload an `on_reload` handler responds to.
* [HTTP API reference](/reference/api) — the routes a `custom_route` contributes.
* [Python SDK reference](/reference/python-sdk) — the `tai42_app.http`, `tai42_app.lifecycle`, and `tai42_app.clients` surfaces.
