Lean, modern, unofficial S3-compatible client for Rust.
- Async + blocking clients with closely aligned APIs
- Presigned URLs and multipart upload
- Optional checksums, tracing, and metrics
- Integration-tested against MinIO and RustFS
- Small dependency surface (feature-gated)
- Structured errors (status/code/request id/body snippet)
# async + rustls (default)
cargo add s3
# blocking (disable defaults, pick one TLS backend)
cargo add s3 --no-default-features --features blocking,rustls
# async + native-tls
cargo add s3 --no-default-features --features async,native-tlsMost applications only touch a small set of layers:
Authdecides how requests are signed: anonymous, env credentials, a custom provider, or optional IMDS/STS/profile flowsClient::builder(...)/BlockingClient::builder(...)capture endpoint, region, retry, TLS, and addressing policy onceclient.objects()andclient.buckets()return typed request builders for object and bucket operationss3::typescontains the public request/response models you work with; protocol XML DTOs stay internals3::providersoffers endpoint presets for common S3-compatible services when enabled
Most code follows the same flow: choose Auth, build a client, then use objects() or
buckets() to send requests and consume public output types from s3::types.
use s3::{Auth, Client};
# async fn demo() -> Result<(), s3::Error> {
let client = Client::builder("https://s3.example.com")?
.region("us-east-1")
.auth(Auth::from_env()?)
.build()?;
let obj = client.objects().get("my-bucket", "path/to/object.txt").send().await?;
let bytes = obj.bytes().await?;
println!("{} bytes", bytes.len());
# Ok(())
# }Auth::from_env() reads AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and optionally AWS_SESSION_TOKEN.
use s3::{Auth, BlockingClient};
fn demo() -> Result<(), s3::Error> {
let client = BlockingClient::builder("https://s3.example.com")?
.region("us-east-1")
.auth(Auth::from_env()?)
.build()?;
let obj = client.objects().get("my-bucket", "path/to/object.txt").send()?;
let bytes = obj.bytes()?;
println!("{} bytes", bytes.len());
Ok(())
}use std::time::Duration;
use s3::{AddressingStyle, AsyncTlsRootStore, Auth, Client};
# async fn demo() -> Result<(), s3::Error> {
let client = Client::builder("https://s3.example.com")?
.region("us-east-1")
.auth(Auth::from_env()?)
.addressing_style(AddressingStyle::Auto)
.tls_root_store(AsyncTlsRootStore::BackendDefault)
.timeout(Duration::from_secs(30))
.max_attempts(3)
.build()?;
# Ok(())
# }use s3::{Auth, Client};
# async fn demo() -> Result<(), s3::Error> {
let client = Client::builder("https://s3.example.com")?
.region("us-east-1")
.auth(Auth::from_env()?)
.build()?;
let buckets = client.buckets().list().send().await?;
let objects = client
.objects()
.list_v2("my-bucket")
.prefix("logs/")
.send()
.await?;
println!("{} buckets, {} objects", buckets.buckets.len(), objects.contents.len());
# Ok(())
# }use s3::{Auth, Client};
# async fn demo() -> Result<(), s3::Error> {
let client = Client::builder("https://s3.example.com")?
.region("us-east-1")
.auth(Auth::from_env()?)
.build()?;
let presigned = client
.objects()
.presign_get("my-bucket", "path/to/object.txt")
.build()?;
println!("GET {}", presigned.url);
# Ok(())
# }use s3::{Auth, providers};
# async fn demo() -> Result<(), s3::Error> {
let preset = providers::minio_local();
let client = preset
.async_client_builder()?
.auth(Auth::from_env()?)
.build()?;
# Ok(())
# }Use Auth as the single entry point for signing strategy. Static credentials, refreshable
providers, and optional cloud credential flows all feed the same client builder API.
Auth::Anonymous: unsigned requests (for public buckets / anonymous endpoints)Auth::from_env(): static credentials from env varsAuth::provider(...): plug in your own refreshable provider (cached/singleflight refresh)Auth::from_imds_with_tls_root_store(...)/Auth::from_web_identity_env_with_tls_root_store(...): explicit TLS root policy for credentials fetch- Optional features:
credentials-profile: shared config/profile loadercredentials-imds: IMDS credentials (async/blocking APIs)credentials-sts: web identity / STS flows
- Addressing styles:
AddressingStyle::Auto(default),Path,VirtualHosted - Targets: any S3-compatible service; CI runs against MinIO and RustFS
- Modes:
async(default),blocking - TLS:
rustls(default),native-tls - Optional:
multipart,checksums,providers,credentials-profile,credentials-imds,credentials-sts,tracing,metrics
Examples follow the same layering as the crate docs: choose Auth, build a client, then work
through objects() or buckets().
- Basic object lifecycle:
examples/async_put_get_delete.rs - List buckets:
examples/async_list_buckets.rs - List objects v2 + pagination:
examples/async_list_objects.rs - Batch delete:
examples/async_delete_objects_batch.rs - Copy object + replace metadata:
examples/async_copy_object.rs - Streaming upload (requires Content-Length):
examples/async_put_stream.rs - Multipart upload (feature =
multipart):examples/async_multipart_upload.rs
- Presign with static credentials:
examples/presign_get.rs - Presign with a refreshable provider:
examples/async_presign_build_async.rs - IMDS credentials (feature =
credentials-imds):examples/async_auth_imds.rs - Web identity credentials (feature =
credentials-sts):examples/async_auth_web_identity.rs - MinIO local preset (feature =
providers):examples/minio_local_put_get_delete.rs - Cloudflare R2 preset (feature =
providers):examples/r2_put_get_delete.rs
- Async request TLS root policy:
examples/async_tls_root_store.rs - Blocking request TLS root policy:
examples/blocking_tls_root_store.rs - Blocking object lifecycle:
examples/blocking_put_get_delete.rs - Blocking list buckets:
examples/blocking_list_buckets.rs - Blocking presign:
examples/blocking_presign_get.rs
See examples/README.md for environment variables and feature requirements.
Run just ci after local changes to validate formatting, feature combinations, tests, and clippy.