Skip to main content
Use storage to save plugin state. Use HTTP routes to turn a plugin into a small request handler. Route declarations live in manifest headers; storage functions are imported from middleware.py.

Default storage

Default storage uses the global otto bucket and is suitable for small plugin state.
del is a Python keyword and cannot be called as a normal function name. The SDK keeps del_ = delete and a runtime compatibility alias. New plugins should use delete(...).

bucket naming rules

bucket is a storage namespace. A . means a child bucket. For example, shop.orders means orders under shop. The runtime allows up to 3 bucket levels. Prefer at most 3 levels to avoid deep permission configuration, migrations, and troubleshooting.
str
required
Bucket name. It cannot be empty; use . to separate levels; each segment must be non-empty; maximum 3 levels. Examples: my_plugin, my_plugin.users, my_plugin.orders.paid.
Prefer plugin_name.domain.status, such as card_claim.users or card_claim.inventory.active. For small plugins, use one level, such as my_plugin.

Named buckets

Named buckets isolate plugin state by plugin or business domain. The bucket isolates the namespace; the key locates a record.
Sender provides bucket methods with the same names. Use them when you want the call tied to the current conversation context.

HTTP route requests

After declaring router and method in the manifest, use Sender to read the request and respond.
Python currently supports getRouter() / getMethod() / getRouterData() compatibility aliases. This documentation still recommends getRouterPath(), getRouterMethod(), and getRouterBody() because they describe the path, method, and parsed request body more precisely.
Top-level getRouter(), getMethod(), and getRouterData() create a default Sender from the current getSenderID(). They are useful for migrating old plugins; new plugins should create Sender explicitly.

Next steps

Last modified on June 3, 2026