Skip to main content
Production servers often read, upload, and write files to S3-compatible object storage services instead of the local filesystem. Historically, that means local filesystem APIs you use in development can’t be used in production. When you use Bun, things are different.

Bun’s S3 API is fast

Bun's S3 API is fast

Left: Bun v1.1.44. Right: Node.js v23.6.0

Bun provides fast, native bindings for interacting with S3-compatible object storage services. Its S3 API is designed to feel similar to fetch’s Response and Blob APIs (like Bun’s local filesystem APIs).
s3.ts
S3 is the de facto standard internet filesystem. Bun’s S3 API works with S3-compatible storage services like:
  • AWS S3
  • Cloudflare R2
  • DigitalOcean Spaces
  • MinIO
  • Backblaze B2
  • …and any other S3-compatible storage service

Basic Usage

There are several ways to interact with Bun’s S3 API.

Bun.S3Client & Bun.s3

Bun.s3 is equivalent to new Bun.S3Client(), relying on environment variables for credentials. To explicitly set credentials, pass them to the Bun.S3Client constructor.
s3.ts

Working with S3 Files

The file method in S3Client returns a lazy reference to a file on S3.
s3.ts
Like Bun.file(path), the S3Client’s file method is synchronous. It does zero network requests until you call a method that depends on a network request.

Reading files from S3

If you’ve used the fetch API, you’re familiar with the Response and Blob APIs. S3File extends Blob. The same methods that work on Blob also work on S3File.
s3.ts

Memory optimization

Methods like text(), json(), bytes(), or arrayBuffer() avoid duplicating the string or bytes in memory when possible. If the text happens to be ASCII, Bun directly transfers the string to JavaScriptCore (the engine) without transcoding and without duplicating the string in memory. When you use .bytes() or .arrayBuffer(), it will also avoid duplicating the bytes in memory. These helper methods not only simplify the API, they also make it faster.

Writing & uploading files to S3

Writing to S3 works the same way.
s3.ts

Working with large files (streams)

Bun automatically handles multipart uploads for large files and provides streaming capabilities. The same API that works for local files also works for S3 files.
s3.ts

Presigning URLs

When your production service needs to let users upload files to your server, it’s often more reliable for the user to upload directly to S3 instead of your server acting as an intermediary. To facilitate this, you can presign URLs for S3 files. This generates a URL with a signature that allows a user to securely upload that specific file to S3, without exposing your credentials or granting them unnecessary access to your bucket. The default behaviour is to generate a GET URL that expires in 24 hours. Bun attempts to infer the content type from the file extension. If inference is not possible, it will default to application/octet-stream.
s3.ts

Setting ACLs

To set an ACL (access control list) on a presigned URL, pass the acl option:
s3.ts
You can pass any of the following ACLs:

Expiring URLs

To set an expiration time for a presigned URL, pass the expiresIn option.
s3.ts

method

To set the HTTP method for a presigned URL, pass the method option.
s3.ts

new Response(S3File)

To redirect users to a presigned URL for an S3 file, pass an S3File instance to a Response object as the body. This will automatically redirect the user to the presigned URL for the S3 file, saving you the memory, time, and bandwidth cost of downloading the file to your server and sending it back to the user.
s3.ts

Support for S3-Compatible Services

Bun’s S3 implementation works with any S3-compatible storage service. Just specify the appropriate endpoint:

Using Bun’s S3Client with AWS S3

AWS S3 is the default. You can also pass a region option instead of an endpoint option for AWS S3.
s3.ts

Using Bun’s S3Client with Google Cloud Storage

To use Bun’s S3 client with Google Cloud Storage, set endpoint to "https://storage.googleapis.com" in the S3Client constructor.
s3.ts

Using Bun’s S3Client with Cloudflare R2

To use Bun’s S3 client with Cloudflare R2, set endpoint to the R2 endpoint in the S3Client constructor. The R2 endpoint includes your account ID.
s3.ts

Using Bun’s S3Client with DigitalOcean Spaces

To use Bun’s S3 client with DigitalOcean Spaces, set endpoint to the DigitalOcean Spaces endpoint in the S3Client constructor.
s3.ts

Using Bun’s S3Client with MinIO

To use Bun’s S3 client with MinIO, set endpoint to the URL that MinIO is running on in the S3Client constructor.
s3.ts

Using Bun’s S3Client with supabase

To use Bun’s S3 client with supabase, set endpoint to the supabase endpoint in the S3Client constructor. The supabase endpoint includes your account ID and /storage/v1/s3 path. Make sure to set Enable connection via S3 protocol on in the supabase dashboard in https://supabase.com/dashboard/project/<account-id>/settings/storage and to set the region informed in the same section.
s3.ts

Using Bun’s S3Client with S3 Virtual Hosted-Style endpoints

When using a S3 Virtual Hosted-Style endpoint, you need to set the virtualHostedStyle option to true.
  • If you don’t specify an endpoint, Bun will automatically determine the AWS S3 endpoint using the provided region and bucket. - If no region is specified, Bun defaults to us-east-1. - If you explicitly provide an endpoint, you don’t need to specify a bucket name.
s3.ts

Credentials

Credentials are one of the hardest parts of using S3. By default, Bun reads the following environment variables for credentials. If the S3_* environment variable is not set, Bun will also check for the AWS_* environment variable, for each of the above options. These environment variables are read from .env files or from the process environment at initialization time (process.env is not used for this). These defaults are overridden by the options you pass to s3.file(credentials), new Bun.S3Client(credentials), or any of the methods that accept credentials. So if, for example, you use the same credentials for different buckets, you can set the credentials once in your .env file and then pass bucket: "my-bucket" to the s3.file() function without having to specify all the credentials again.

S3Client objects

When you’re not using environment variables or using multiple buckets, you can create a S3Client object to explicitly set credentials.
s3.ts

S3Client.prototype.write

To upload or write a file to S3, call write on the S3Client instance.
s3.ts

S3Client.prototype.delete

To delete a file from S3, call delete on the S3Client instance.
s3.ts

S3Client.prototype.exists

To check if a file exists in S3, call exists on the S3Client instance.
s3.ts

S3File

S3File instances are created by calling the S3Client instance method or the s3.file() function. Like Bun.file(), S3File instances are lazy. They don’t refer to something that necessarily exists at the time of creation. That’s why all the methods that don’t involve network requests are fully synchronous.
Type Reference
Like Bun.file(), S3File extends Blob, so all the methods that are available on Blob are also available on S3File. The same API for reading data from a local file is also available for reading data from S3. That means using S3File instances with fetch(), Response, and other web APIs that accept Blob instances works out of the box.

Partial reads with slice

To read a partial range of a file, you can use the slice method.
s3.ts
Internally, this works by using the HTTP Range header to request only the bytes you want. This slice method is the same as Blob.prototype.slice.

Deleting files from S3

To delete a file from S3, you can use the delete method.
s3.ts
delete is the same as unlink.

Error codes

When Bun’s S3 API throws an error, it will have a code property that matches one of the following values:
  • ERR_S3_MISSING_CREDENTIALS
  • ERR_S3_INVALID_METHOD
  • ERR_S3_INVALID_PATH
  • ERR_S3_INVALID_ENDPOINT
  • ERR_S3_INVALID_SIGNATURE
  • ERR_S3_INVALID_SESSION_TOKEN
When the S3 Object Storage service returns an error (that is, not Bun), it will be an S3Error instance (an Error instance with the name "S3Error").

S3Client static methods

The S3Client class provides several static methods for interacting with S3.

S3Client.write (static)

To write data directly to a path in the bucket, you can use the S3Client.write static method.
s3.ts
This is equivalent to calling new S3Client(credentials).write("my-file.txt", "Hello World").

S3Client.presign (static)

To generate a presigned URL for an S3 file, you can use the S3Client.presign static method.
s3.ts
This is equivalent to calling new S3Client(credentials).presign("my-file.txt", { expiresIn: 3600 }).

S3Client.list (static)

To list some or all (up to 1,000) objects in a bucket, you can use the S3Client.list static method.
s3.ts
This is equivalent to calling new S3Client(credentials).list().

S3Client.exists (static)

To check if an S3 file exists, you can use the S3Client.exists static method.
s3.ts
The same method also works on S3File instances.
s3.ts

S3Client.size (static)

To check the size of an S3 file without downloading it, you can use the S3Client.size static method.
s3.ts
This is equivalent to calling new S3Client(credentials).size("my-file.txt").

S3Client.stat (static)

To get the size, etag, and other metadata of an S3 file, you can use the S3Client.stat static method.
s3.ts

S3Client.delete (static)

To delete an S3 file, you can use the S3Client.delete static method.
s3.ts

s3:// protocol

To make it easier to use the same code for local files and S3 files, the s3:// protocol is supported in fetch and Bun.file().
s3.ts
You can additionally pass s3 options to the fetch and Bun.file functions.
s3.ts

UTF-8, UTF-16, and BOM (byte order mark)

Like Response and Blob, S3File assumes UTF-8 encoding by default. When calling one of the text() or json() methods on an S3File:
  • When a UTF-16 byte order mark (BOM) is detected, it will be treated as UTF-16. JavaScriptCore natively supports UTF-16, so it skips the UTF-8 transcoding process (and strips the BOM). This is mostly good, but it does mean if you have invalid surrogate pairs characters in your UTF-16 string, they will be passed through to JavaScriptCore (same as source code).
  • When a UTF-8 BOM is detected, it gets stripped before the string is passed to JavaScriptCore and invalid UTF-8 codepoints are replaced with the Unicode replacement character (\uFFFD).
  • UTF-32 is not supported.