Skip to main content

Managed storage

Installable apps need durable application state (reservations, orders, drafts, …) without direct database access. Managed storage is the platform document plane (ADR-002).

Who may call it: only the install’s SDK application via ctx.storage / sdk.storage.* (injected on /qefro tool.invoke).

Who must not: workflows, UI sources, or any YAML that targets storage/insert, storage/find, etc. That path is deprecated and forbidden under ADR-003 — business logic belongs in the app process.

Solutions never receive a Mongo connection string and never invent storage-service URLs. Every op is scoped by tenant, workspace, and installation.

Mental model

Widget / WhatsApp / staff form
→ runtime → tool invoker → installation /qefro
→ app tool (e.g. restaurant.createReservation)
→ ctx.storage.insert|find|…
→ storage-service /v1/internal/storage/*
→ MongoDB database `managed_apps`
LayerRole
SDK app (src/)Domain tools; only caller of ctx.storage
Workflows / UICall {solution}/{tool} — never storage/*
storage-serviceIsolation, metadata, soft-delete, audit
MongoDB managed_appsPhysical collections {solution_slug}__{logical}

Control-plane data (packages, installs, secrets) stays in Postgres.

When to use storage vs connectors

NeedUse
Solution-owned app stateManaged storage from inside the SDK
External system of record (Shopify, Stripe, PMS, POS)Pool connector (connectors: + bridge)
Tenant runtime metrics / executionsRuntime sources (type: runtime)

[email protected] is the reference: connectors: [], self-hosted /qefro, all app state through ctx.storage.

Capabilities and permissions

Request both planes in the manifest so the SDK may use storage:

manifest.yaml (excerpt)
permissions:
- workflow.execute
- storage.read
- storage.write
- storage.update
- storage.delete
capabilities:
- storage.read
- storage.write
- storage.update
- storage.delete
- workflow.trigger
- runtime.query
# … theme.get, user.get, tenant.get
CapabilityWho uses it
storage.*SDK handlers via ctx.storage (platform enforces on storage-service)
runtime.queryUI sources targeting this install’s own tools ({solution}/…)
connector.invokeUI/workflow calls to declared pool connectors only

UI sources must not target storage/*. Prefer app list tools gated on runtime.query. See Sources.

Collection naming

FormExample
Logical (what you pass to ctx.storage)reservations
Qualifiedrestaurant-pro.reservations
Physical (Mongo)restaurant_pro__reservations

Use the logical name in the SDK. The platform derives the physical name (kebab-casesnake_case, then __).

There is one shared Mongo database (managed_apps). Isolation is enforced by injected filters on every op (tenant_id, workspace_id, installation_id, solution_id).

Reserved document fields

Platform injects and owns these fields. Client-supplied values for them are stripped or ignored:

_id, tenant_id, workspace_id, installation_id, solution_id, schema_version, created_at, updated_at, created_by, updated_by, deleted_at, deleted_by.

delete is soft-delete. Hard purge and restore are admin-only.

SDK API (canonical)

Inside a tool handler:

await ctx.storage.insert('reservations', { customer_name, guest_count, status: 'confirmed' });
await ctx.storage.find('reservations', { filter: { status: 'confirmed' }, limit: 50 });
await ctx.storage.get('reservations', id);
await ctx.storage.update('reservations', id, { status: 'cancelled' });
await ctx.storage.delete('reservations', id);
OpCapabilityNotes
insertstorage.writeOptional readable code allocation via platform helpers
findstorage.readOptional filter, limit, sort
getstorage.readBy id
updatestorage.updatePatch by id
deletestorage.deleteSoft

Internal HTTP shapes used by the platform (not by packages) are documented in the plugin platform storage API.

Deprecated: direct storage/* from YAML

# FORBIDDEN — do not ship this
- type: tool
tool: storage/insert
params: { collection: reservations, document: {} }
# FORBIDDEN — do not ship this
- id: reservations
type: connector
target: storage/find

Replace with app tools, e.g. restaurant-pro/restaurant.createReservation and restaurant-pro/restaurant.listReservations.

Reserved SDK namespaces

These roots are platform-owned. Solutions must not declare connectors named storage, vector, object, cache, queue, secret, or state. Manifest validation rejects them.

NamespaceStatus
storage.*Implemented (this page) — SDK-only
vector.*, object.*, cache.*, queue.*, secret.*, state.*Reserved — fail closed if invoked

What is deferred

v1 enforces isolation, indexes, metadata, soft delete, and audit. Not in v1: storage quotas, retention/archival policies, or automatic migration of legacy connector mock data.