> ## 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 a storage provider

> Implement the storage contract.

Author a storage provider plugin behind the storage contract.

A storage provider is where content is stored. It implements the `Storage` ABC — a text surface (`load` / `list` / `upload` / `delete` / `delete_dir`) plus a binary surface (`load_bytes` / `upload_bytes` / `stat`) — and registers as the process's single provider. The binary methods ship text-bridging defaults, so a text-only backend satisfies the whole contract with no new code; binary-native backends override them.

## Implement the contract

Subclass `Storage` and implement the five text methods. `load` raises `FileNotFoundError` for a missing path; `delete_dir` removes a prefix. This in-memory provider is the minimal shape:

```python examples/storage_provider/memory_storage.py theme={null}
"""A minimal fictional storage provider: an in-memory content store.

Implements the ``Storage`` ABC — the text surface (``load`` / ``list`` /
``upload`` / ``delete`` / ``delete_dir``) — and registers as the process's single
provider. The binary methods (``load_bytes`` / ``upload_bytes`` / ``stat``) ship
text-bridging defaults, so this text-only backend satisfies the whole contract
with no extra code. A real backend swaps the dict for its own store.
"""

from tai42_contract.app import tai42_app
from tai42_contract.storage import Storage, assert_not_root


@tai42_app.storage.register_storage
class InMemoryStorage(Storage):
    def __init__(self) -> None:
        self._items: dict[str, str] = {}

    async def load(self, path: str) -> str:
        try:
            return self._items[path]
        except KeyError as exc:
            # The manager relies on this exact type to map a missing item to
            # Jinja's ``TemplateNotFound`` — never return an empty string.
            raise FileNotFoundError(path) from exc

    async def list(self) -> list[str]:
        return sorted(self._items)

    async def upload(self, path: str, content: str) -> None:
        self._items[path] = content

    async def delete(self, path: str) -> None:
        self._items.pop(path, None)

    async def delete_dir(self, path: str) -> None:
        # Refuse a delete that resolves to the storage root before touching keys.
        assert_not_root(path)
        prefix = path.rstrip("/") + "/"
        for key in [k for k in self._items if k.startswith(prefix)]:
            del self._items[key]
```

Override `load_bytes` / `upload_bytes` / `stat` only for a binary-native backend; otherwise the contract's text-bridging defaults handle media. The module-level `assert_not_root` guard refuses a directory delete that resolves to the storage root — the example calls it in `delete_dir`, and a real backend does the same.

## Register through the handle

Decorate the class with `@tai42_app.storage.register_storage`, as the example does. The registry is dead by default — it holds no provider until one is registered, and the registered class is instantiated as the active provider. One provider per process.

## Load it

The host selects the provider by naming its module under `storage_module`. Importing the module runs the decorator.

```yaml manifest.yml theme={null}
storage_module: your_package.storage
```

Document any backend-specific settings (bucket names, credentials, endpoints) in your repository's README — that impl-specific configuration lives in your repo, not on this site.

## Shipped implementations

Shipped storage providers appear in the [ecosystem catalog](/reference/catalog), each linking to its own repository.

## See also

* [Storage and resources](/concepts/storage-and-resources) — the storage and resource-manager model.
* [Manage stored content and templates](/guides/manage-stored-content) — using storage.
* [Python SDK reference](/reference/python-sdk) — the `Storage` contract surface.
