Skip to main content
This page covers every way to write an object from your server: put for bytes you have, copy and move to rearrange them, a JSON read-modify-write, and signed upload URLs to hand one write to somebody else.
bucket is the client from Connecting. Everything here runs on your server with the bucket token. Method signatures and option types: TypeScript, Python, Go. For files a user picks in the browser, do not proxy the bytes through your app. Use an upload handler instead.

put

Options

Every option is optional. Sizes are byte counts like 4096, '20mb', or '32MiB' (Types). The content type grammar and wildcards are on Constraints.

Return value

put returns the stored object’s record: path, URL, versioned URL, size in bytes, etag (what if-unchanged takes), upload time and content type (Records). On a private bucket the URL and versioned URL are not set.

Bodies

A body that carries its own length and content type, such as a file or an incoming request, needs nothing declared. In Go a request body is a plain reader that needs a size or a max size. One that carries neither, such as a stream, needs a size, or at least a max size. The body types each SDK accepts: TypeScript, Python, Go. An incoming request with no body, or one that has already been read, is refused with empty_body. Python and Go have no request body type, so their put never returns it.

Streams and unknown lengths

Storage needs a content length before the first byte goes out, and a stream has none. Pass one of two options: a max size buffers up to that cap, and a stream that runs past it is cancelled with too_large; an exact size streams straight through with nothing buffered. A body that does not match the declared size fails the request rather than being stored at the wrong length. A request that arrived chunked has no content-length and counts as an unknown length too. When proxying bytes through a route, keep the max size under the platform’s own request body cap, since that refusal happens before your route runs. See Platform body limits.

Paths

A path is any non-empty string, with / as structure. It is percent-encoded for you, so spaces and unicode are fine. . and .. segments are rejected, not normalized, by every method that takes a path. Avoid empty segments (dir/, /a, a//b). put stores them, but no signed URL can name one, so a private object at such a path can only be read with get, and an upload handler refuses the path at begin.

Unique paths

The unique path helper adds one random suffix to the final filename while preserving the rest of the path. Case, underscores, spaces, punctuation, and Unicode stay exactly as supplied. User IDs such as Alice_123 remain Alice_123; values are not lowercased, slugged, truncated, or Unicode-normalized. Pass the whole path as one string. Slashes define directories wherever they appear, so a prefix can go in as is. Like every path, the result may not contain . or .. segments; that is checked when the path is used. The suffix has 8 random characters and goes before the final extension when it is 1 to 8 ASCII letters or digits, preserving the extension’s case. Otherwise it goes at the end. A leading dot stays part of the filename: .env becomes .env-<random>, while .env.local becomes .env-<random>.local. An empty final filename uses file as its stem. The SDK handles URL encoding when it sends the path to storage. Store the returned path with the original owner ID and use that exact path for reads and deletion. The helper creates a unique key; ownership checks still belong in your application. See Private documents. Two uploads of photo.png never land on the same object. To overwrite on purpose, write the path yourself.

Metadata

Metadata is a flat map of string keys to string values, stored as x-amz-meta-* headers. Three rules:
  • Keys come back lowercased. Write them lowercase to begin with.
  • Values must be printable ASCII. Anything else is refused with invalid_input. Percent-encode other text and decode it on the way back.
  • It comes back from info and get, not list. Reading metadata for many objects is one info call each.

Conditional writes

Turning allow-overwrite off refuses the write if anything is already at the path, with already_exists carrying the etag and size of what is there. If-unchanged takes an etag you read and refuses with conflict if somebody else wrote first. Both are enforced by storage, so there is no race window. Both turn multipart off, so a conditional write of a large body goes up as one request. Combining either with multipart forced on is refused with invalid_input:

Updating JSON

The JSON update runs in the SDK, not in storage. It reads the document, calls your function with the parsed value, and writes the result back with if-unchanged. If somebody wrote in between, it pauses briefly, reads again and re-runs your function. After the maximum number of attempts it throws conflict.
  • Your function gets null (None in Python, nil in Go) when there is nothing to read. An empty object reads as null too.
  • It may be async (in Python, only on AsyncBucket). It runs on every attempt, so keep it a pure transform.
  • The object is written as application/json. Existing metadata is carried over unless you pass your own.

Options

Every option is optional. Names and types: TypeScript, Python, Go.

copy and move

copy runs inside storage, so the bytes never travel through your app. Storage has no rename, so move is a copy followed by a delete of the source. Both return the destination’s record. A missing source throws not_found. An existing destination is overwritten. There is no allow-overwrite option here because storage does not honor a precondition on a copy’s destination.

Options

Every option is optional, and move takes the same ones as copy. Names and types: TypeScript, Python, Go. With no options the destination is an exact copy. Passing any one of them makes storage rewrite all three, so the SDK reads the other two off the source first and sends them back unchanged. A move is not atomic. If the copy lands and the delete fails, move throws move_left_a_copy and keeps both objects. Retry the source delete to recover; see Deleting.

Large bodies

A body over the multipart threshold of 16 MB goes up in parts instead of one PUT. The multipart option changes the threshold: a size sets a new one, on always uses parts, off never does.
  • A single PUT cannot carry more than about 5 GiB. Multipart off on a body that big throws too_large.
  • Parts are sent four at a time, fewer for bodies over about 3 GB to bound the memory held by buffered parts. Any failure aborts the whole upload before throwing, so nothing is left behind.
  • A body that does not match a declared size throws invalid_input.

Signed upload URLs

A signed upload URL comes back with the headers to send and when it expires: a URL somebody else can PUT exactly one object to. Use it for a CLI, a build step, or a server-to-server job whose bytes you do not want to relay. Names and types: TypeScript, Python, Go. Every option is optional. The returned headers are signed into the URL and must be sent verbatim. Drop one, change one, or add one, and storage answers 403. That is what stops the uploader from changing metadata. The returned expiry may be sooner than what you asked for. Cache the link until then rather than computing your own deadline; see Use the returned expiry. For a browser upload, use the upload handler instead. A signed URL is one PUT: no multipart, no resume, and nothing tells your server it happened.

Next steps

Reading

get, info, exists and paging through list.

Deleting

One path, a list, a prefix, and what a partial delete reports.

Caching

What cache accepts and why it is written once, at upload.

Connecting

Client options, retries, and using an S3 client directly.