Skip to main content

Reading

4 min read

This page covers reading objects from your server: get for the bytes, info for the facts, exists for a boolean, list for a page of keys, and signedReadUrl for a link that reads a private object without going through your server.

The records these return (BlobObject, BlobInfo, BlobDownload) are on Types.


get#

body is a stream and nothing is buffered for you. Wrap it in a Response for the usual conversions:

A missing object throws a BlobError with code not_found, status 404. See Errors.

There is no range option. For byte ranges, use an S3 client.


info#

One HEAD request. The same record as get without the bytes, so reading a 2 GB object's facts is cheap. A missing object throws not_found.

metadata comes back from get and info only, with keys lowercased. See Metadata.


exists#

The same HEAD request as info, answering false instead of throwing.

If you need the etag, size or metadata anyway, call info() and catch not_found instead of making two round trips.


list#

All three are optional.

OptionTypeDefaultDescription
prefixstringnone, the whole bucketOnly keys starting with this.
limitnumberstorage picks, at most 1000Page size, clamped to 1 to 1000.
cursorstringnone, the first pageThe cursor from the previous page.

A full walk is a do ... while:

Each entry is a BlobObject: path, size, etag, timestamp and URLs. There is no contentType or metadata; reading those is one info() per object.

prefix is the only filter. There is no query by owner, type or date, so "this user's files" has to be a prefix you chose at upload time. An app that needs to query its files should keep its own table and treat the bucket as storage, not an index.


Public URLs#

Every record on a public bucket already carries url; publicUrl gives you one for any path. It returns undefined on a private bucket and throws a TypeError for an empty path or one with a . or .. segment.

The URL itself is built from the token, but whether the bucket has a public host at all is known only to the backend, so the first call on a fresh client fetches credentials. They are cached, so every call after that is local.

versionedUrl#

versionedUrl is ${url}?v=${etag}, so it changes whenever the content does. Use it for a stable path that gets overwritten: if avatars/u7.png is replaced every time the user picks a new picture, url never changes and caches keep serving the old bytes.

Pair it with cache: 'immutable' at upload. See Caching.


Private buckets#

A private bucket has no public host, so url and versionedUrl are undefined on every record. Nothing in the code declares this: the SDK learns it from the backend when it fetches credentials, and objects are stored with Cache-Control: private. Reads go through signedReadUrl().


signedReadUrl#

A time-limited URL anyone can GET. Use it on a private bucket, or for an object you do not want linked from a public page.

All three are optional.

OptionTypeDefaultDescription
expiresInDuration'5m'How long to ask for. '15m', '2h', or a bare number of seconds.
downloadAsstringnone, displayed inlineSave as this filename instead of displaying inline.
contentTypestringthe stored typeWhat storage answers with as Content-Type, overriding what was stored.

Use expiresAt, not expiresIn#

expiresIn is what you asked for. expiresAt is what you got, and it can be sooner, because a link cannot outlive the credential that signed it. Cache the link until expiresAt, never until a deadline you compute yourself. This applies to signedUploadUrl too. The reason is on How signing works.

downloadAs#

Sets Content-Disposition: attachment, so the browser saves the file under that name rather than rendering it. Unicode names arrive intact. The filename is signed into the URL, so it cannot be edited afterwards.

contentType#

Overrides what storage answers with, without rewriting the object. Throws invalid_input if it is not a valid media type.


Next steps#