Application storage (TenancyEngine)
Manage the object-storage buckets and runtime access patterns your application uses to hold tenant file uploads, public previews, and purchased downloads — without provisioning cloud storage credentials yourself.
Console: Application workspace → Storage (/applications/:id/storage)
Requires Applications configure to create, update, or delete buckets and their access rules. Read-only access shows existing buckets and usage.
How it works
Each bucket is a logical container scoped to your application. Files uploaded by tenants through the portal storage API are tagged with the owning tenant. Files uploaded by your backend through the runtime storage API are app-owned by default and should be exposed only through explicit business-subject grants. Both surfaces are billed against your application's storage entitlement.
Creating a bucket
- Click Add bucket.
- Give it a name — this is a label shown in the console, not the underlying storage key.
- Choose the access mode for this bucket's intended objects:
- Tenant-private — use the portal storage API with
TenancyEngineStorageVisibility.UserPrivate. Only the uploading user, an explicit share, or a role with storage view-all access can read the object. - Tenant-shared — use the portal storage API with
TenancyEngineStorageVisibility.TenantShared. Every member of that tenant can read the object. Do not use this for paid downloads, support evidence, customer-specific files, or anything that still needs application-level authorization. - Public preview — use a deliberately public object for thumbnails, samples, or marketing media that are safe for anonymous inspection. Keep previews separate from the private source file.
- Purchased download — use the runtime storage API. Upload the paid file as app-owned/private, create a read grant keyed to the purchase/order/work item, and request download URLs only after your app has rechecked its own entitlement or payment state.
- Tenant-private — use the portal storage API with
- Optionally set a maximum object size and a retention policy (auto-delete objects after N days).
Uploading and retrieving objects
Buckets are managed here, but objects themselves are uploaded and retrieved through the platform storage API from your application's backend — this page does not upload tenant files directly.
Safe reference flows
Tenant-private attachment
Use for user-specific attachments such as private forms, imports, or customer evidence.
await storage.UploadPortalObjectAsync(
TenancyEngineStoragePatterns.TenantPrivateUpload("intake.pdf", "application/pdf", "intake"),
content,
ct);Tenant-shared workspace file
Use only when every current tenant member should be able to read the file.
await storage.UploadPortalObjectAsync(
TenancyEngineStoragePatterns.TenantSharedUpload("team-export.csv", "text/csv", "exports"),
content,
ct);Public preview media
Use for thumbnails, samples, and other inspection-safe assets. Do not point this at the paid/private source file.
await storage.UploadRuntimeObjectAsync(
TenancyEngineStoragePatterns.PublicPreviewUpload("assetvault", "preview.png", "image/png"),
previewContent,
ct);Purchased download
Use for commercial files and anything gated by checkout, license, job, ticket, or support workflow. The object stays private; the grant is keyed to the business subject that your app already authorizes.
var upload = await storage.UploadRuntimeObjectAsync(
TenancyEngineStoragePatterns.PurchasedDownloadUpload("assetvault", "bundle.zip", "application/zip"),
fileContent,
ct);
await storage.CreateRuntimeStorageGrantAsync(
TenancyEngineStoragePatterns.PurchasedDownloadGrant(
"assetvault",
upload.ObjectId,
purchaseId: purchase.Id.ToString("D"),
expiresAtUtc: DateTime.UtcNow.AddHours(2)),
ct);
// Call this only after your app verifies the purchaser, payment/license state, and refund state.
var downloadUrl = await storage.GetRuntimeDownloadUrlAsync(
TenancyEngineStoragePatterns.PurchasedDownloadUrl(
"assetvault",
upload.ObjectId,
purchaseId: purchase.Id.ToString("D")),
ct);Required runtime API-key scopes:
| Operation | Scope |
|---|---|
| Runtime private/public upload and confirm | storage.write |
| Runtime grant create/list/revoke | storage.grants.write |
| Runtime grant-checked download URL | storage.read |
storage.read, storage.write, and storage.grants.write are not included in broad MCP helper scopes. Grant them deliberately to the application org API key that performs storage operations.
Per-tenant isolation
Portal-uploaded objects are associated with the tenant that uploaded them. Runtime-uploaded objects are app-owned/private unless you explicitly make them public. For purchased downloads, tenant membership alone is not enough: your app should verify the purchase/order/license first, then ask TenancyEngine for a grant-checked download URL.
Usage and limits
The bucket list shows object count and total bytes stored per bucket. Storage entitlement limits are configured under your application's plan/offering — see Offerings.
Deleting a bucket
Deleting a bucket permanently deletes every object inside it. This cannot be undone — the console requires typing the bucket name to confirm.
Related
- Application features — gate storage-dependent features behind entitlements
- Application offerings — where storage limits are set per plan
- Knowledge base — a specialized storage surface for AI-searchable documents