services.storage
File/object storage contract for upload/download and presigned URL workflows.
- Stability:
stable - Canonical source:
packages/spec/src/contracts/storage-service.ts - Registry slot:
storage— resolve withctx.getService('storage'). The pre-rename spellingfile-storagestays accepted as a deprecated alias within v17. See Registry slot and its deprecated alias.
Registry slot and its deprecated alias
storage is the canonical registry slot (maintainer ruling, 2026-08-18, issue
#9683): it is a member of CoreServiceName
(packages/spec/src/system/core-services.zod.ts), CORE_SERVICE_PROVIDER maps
it to @objectstack/service-storage, and it is the key the /api/v1/discovery
document reports this service's availability under. The accessor spelling and
the slot are now the same word, like every other service in this chapter.
The slot was spelled file-storage before the rename, and that spelling is a
published CoreServiceName member — so it stays accepted as a deprecated
alias within v17. @objectstack/service-storage registers the same
instance under both names:
ctx.getService('storage'); // ✓ canonical — the IStorageService documented below
ctx.getService('file-storage'); // ✓ deprecated v17 alias — the SAME instanceNew code resolves storage. The alias is retired through the standard
retirement flow at the next major. If you ship a custom provider for this
slot in v17, register it under both names the way service-storage does, so
callers of either spelling keep resolving it.
Not this: the client SDK storage accessor
A second storage accessor exists and is easy to reach for by mistake.
ObjectStackClient.storage (packages/client/src/index.ts) is the browser/HTTP client
surface — upload(file, scope), getDownloadUrl(fileId), getPresignedUrl(req),
initChunkedUpload(req) — which calls /api/v1/storage over the wire. It is a different
shape from the server-side IStorageService documented on this page, which takes storage
keys and returns Buffers. ctx.getService('storage') resolves the server-side
service documented here — never the client accessor.
Core Methods
services.storage.upload(key: string, data: Buffer | ReadableStream, options?: StorageUploadOptions): Promise<void>
services.storage.download(key: string): Promise<Buffer>
services.storage.delete(key: string): Promise<void>
services.storage.exists(key: string): Promise<boolean>
services.storage.getInfo(key: string): Promise<StorageFileInfo>
services.storage.getSignedUrl?(key: string, expiresIn: number, options?: PresignedDownloadOptions): Promise<string>Presigned / Chunked (optional)
services.storage.getPresignedUpload?(key: string, expiresIn: number, options?: StorageUploadOptions): Promise<PresignedUploadDescriptor>
services.storage.getPresignedDownload?(key: string, expiresIn: number, options?: PresignedDownloadOptions): Promise<PresignedDownloadDescriptor>
services.storage.initiateChunkedUpload?(key: string, options?: StorageUploadOptions): Promise<string>
services.storage.uploadChunk?(uploadId: string, partNumber: number, data: Buffer): Promise<string>
services.storage.completeChunkedUpload?(uploadId: string, parts: Array<{ partNumber: number; eTag: string }>): Promise<string>
services.storage.abortChunkedUpload?(uploadId: string): Promise<void>Removed
services.storage.list?(prefix) was removed in @objectstack/spec 5.x — the contract
has no prefix-enumeration method. It had no consumer, and the two shipped adapters gave
the same call two different, silently-incomplete answers (local: one level, directories
reported as files; S3: recursive, truncated at 1000 objects). Query the file records you
wrote rather than the bucket; a future enumeration returns cursor-shaped
(list(prefix, { cursor, limit })). See the
contract reference.
Typical Errors
Storage methods reject with a plain Error carrying a descriptive message — the
built-in adapters do not attach a stable error code. Common cases include:
- File not found on
download/getInfo— the local adapter surfaces the underlying filesystemENOENTerror; reference/mock adapters reject with aFile not found: <key>message. - Invalid or expired upload session on the chunked methods (e.g.
Upload session "<id>" not found). - Path-traversal / invalid key rejection from the local adapter (e.g.
path traversal not allowed).
A request that fails an upstream permission check surfaces the framework-wide
PERMISSION_DENIED error from the security layer (code: 'PERMISSION_DENIED'), not the storage adapter.