Asset Service
Most admin frameworks ship “upload to a local directory and return a URL.” That works in development and breaks in production: files vanish when a container restarts or scales out, private files have no access check, URLs leak, every project re-writes its own OSS/S3 wiring, and large uploads saturate the application server.
StackRivet treats files as a first-class Asset Service: it manages asset metadata, storage adapters, upload/download security and business references in one place.
The core rule: store assetId, not URLs
Section titled “The core rule: store assetId, not URLs”Business tables store an assetId, never a raw file URL. A URL is fetched on demand through the Asset API — so it can be short-lived, permission-checked, and independent of which storage backend you use.
business row → assetId → Asset API → (permission check) → short-lived signed URLThis is what keeps private files private and lets you switch storage backends without rewriting business code.
One storage adapter, three backends
Section titled “One storage adapter, three backends”A single StorageAdapter interface covers Local Dev, S3-compatible (e.g. MinIO, AWS S3) and Aliyun OSS. The adapter:
- Never leaks a cloud-vendor SDK object into the business layer.
- Converts errors into one unified exception type.
- Takes endpoint / bucket / region / credentials from configuration (env vars, never committed).
- Issues short-lived signed URLs by default.
Switching from local storage to S3 or OSS is a configuration change (STACKRIVET_STORAGE_TYPE), not a code change. See the Configure object storage guide.
System-generated object keys
Section titled “System-generated object keys”The storage object key is generated by the system — the user never controls it, and the original filename is kept only in metadata:
{env}/{tenantId}/{yyyy}/{MM}/{dd}/{assetId}.{extension}# e.g. prod/default/2026/05/27/01JABC.pdfPrivate files never get a permanent URL.
Lifecycle
Section titled “Lifecycle”An asset moves through a small state machine:
stateDiagram-v2 [*] --> pending pending --> active: upload completed pending --> deleted: aborted / expired active --> deleted: soft delete deleted --> [*]
Community implements pending, active and deleted. Enterprise environments can extend the lifecycle with compliance and security states when required.
Upload paths
Section titled “Upload paths”| File | Path |
|---|---|
| Small (≤ 8 MB) | Through the backend, which writes to the storage adapter |
| Large | A presigned direct upload — the file goes straight to object storage, so the app server never carries the full file traffic |
| Very large | Multipart upload sessions (initiate / complete / abort) |
Upload responses return a stable assetId, so clients can retry around the returned asset record instead of storing raw URLs or vendor-specific object keys.
Private access is checked on the server
Section titled “Private access is checked on the server”A private file’s download URL is only minted after a permission check, via the AssetAccessPolicy SPI:
public interface AssetAccessPolicy { boolean canRead(CurrentUser user, Asset asset); boolean canDelete(CurrentUser user, Asset asset);}By default the uploader can read, a system administrator can read and delete, and a business module can register its own policy. An unauthorized download returns 403; an unknown/oversized/forbidden upload returns 415 or 413.
Edition boundary
Section titled “Edition boundary”Community includes Local / S3-compatible / Aliyun OSS, private signed URLs, small + large + multipart upload, security validation and business references. A recycle bin, CDN domains, image resize/watermark and dedicated asset access logs are Pro; virus scanning, Object Lock and GCS/Azure/COS/OBS are Enterprise — see the pricing page.