Skip to main content

Use the AWS SDKs with Cloider Object Storage

Your code reaches Cloider buckets with the official AWS SDKs, because the buckets speak the S3 API. You configure the S3 client with Cloider's endpoint, the region eu-central-2, an access key and one checksum setting, and every S3 call (upload, download, list, delete, presign) works as it does on Amazon S3.

This page has a complete, runnable example for each language, then shows the patterns most apps need: large uploads, presigned URLs for downloads and browser uploads, and configuration through environment variables.

Before you begin​

  • A bucket, here acme-assets. See Create a bucket.
  • An access key for it, read and write if your code uploads or deletes.
  • The key in your app's environment, never in the source code:
.env
AWS_ACCESS_KEY_ID=CLDRZ8Q4M2X7KD1PLW0B
AWS_SECRET_ACCESS_KEY=7hT2kV9qWm4LxR8zNc3PbY6sJd1FgA5eUo0iKlMn
AWS_REGION=eu-central-2
AWS_ENDPOINT_URL_S3=https://s3.eu-central-2.cloider.com
AWS_REQUEST_CHECKSUM_CALCULATION=when_required
AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
S3_BUCKET=acme-assets

The four settings every SDK needs​

SettingValueWhy
Endpointhttps://s3.eu-central-2.cloider.comSends requests to Cloider instead of AWS
Regioneu-central-2The region requests are signed for
Credentialsaccess key id and secret keySigns every request
Checksum calculationwhen_requiredAWS SDKs released since January 2025 add a checksum to every upload by default, which Cloider's storage doesn't accept. when_required sends one only where the S3 API requires it

Path style addressing is optional: Cloider serves both https://acme-assets.s3.eu-central-2.cloider.com/key and https://s3.eu-central-2.cloider.com/acme-assets/key. The examples set it explicitly so you can see where the switch is. Older SDK versions that don't know the checksum settings don't send the default checksums either, so for them you can leave the setting out.

Configuration from the environment only

Recent versions of every AWS SDK read AWS_ENDPOINT_URL_S3, AWS_REGION, the credentials and AWS_REQUEST_CHECKSUM_CALCULATION from the environment. With the .env above, a client created with no options at all (new S3Client({}), boto3.client("s3")) talks to Cloider. The explicit examples below work with every SDK version that supports the settings, and make the configuration visible in code.

Complete examples​

Each example uploads a file, lists the bucket, downloads the file, creates a presigned download link and deletes the file.

Install the AWS SDK for JavaScript v3:

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner @aws-sdk/lib-storage
s3.mjs
import {
S3Client,
PutObjectCommand,
GetObjectCommand,
ListObjectsV2Command,
DeleteObjectCommand,
} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { readFile } from "node:fs/promises";

const s3 = new S3Client({
region: "eu-central-2",
endpoint: "https://s3.eu-central-2.cloider.com",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
forcePathStyle: false, // true works too
requestChecksumCalculation: "WHEN_REQUIRED",
responseChecksumValidation: "WHEN_REQUIRED",
});

const Bucket = "acme-assets";

// Upload
await s3.send(new PutObjectCommand({
Bucket,
Key: "images/logo.png",
Body: await readFile("./logo.png"),
ContentType: "image/png",
CacheControl: "public, max-age=86400",
}));

// List
const list = await s3.send(new ListObjectsV2Command({ Bucket, Prefix: "images/" }));
for (const obj of list.Contents ?? []) {
console.log(obj.Key, obj.Size, obj.LastModified);
}

// Download
const file = await s3.send(new GetObjectCommand({ Bucket, Key: "images/logo.png" }));
const bytes = await file.Body.transformToByteArray();
console.log("downloaded", bytes.length, "bytes");

// Presigned download link, valid for 15 minutes
const url = await getSignedUrl(s3, new GetObjectCommand({ Bucket, Key: "images/logo.png" }), {
expiresIn: 900,
});
console.log(url);

// Delete
await s3.send(new DeleteObjectCommand({ Bucket, Key: "images/logo.png" }));

Run it with node --env-file=.env s3.mjs (Node.js 20.6 or later).

Large files. @aws-sdk/lib-storage splits big files into a multipart upload and sends parts in parallel:

upload-large.mjs
import { S3Client } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { createReadStream } from "node:fs";

const s3 = new S3Client({
region: "eu-central-2",
endpoint: "https://s3.eu-central-2.cloider.com",
requestChecksumCalculation: "WHEN_REQUIRED",
responseChecksumValidation: "WHEN_REQUIRED",
});

const upload = new Upload({
client: s3,
params: {
Bucket: "acme-backups",
Key: "dumps/orders-2026-10-20.sql.gz",
Body: createReadStream("./orders.sql.gz"),
},
partSize: 16 * 1024 * 1024, // 16 MiB parts
queueSize: 4, // 4 parts in parallel
});

upload.on("httpUploadProgress", (p) => console.log(p.loaded, "/", p.total));
await upload.done();

Create a presigned URL​

A presigned URL is a normal object URL with a signature in its query string. Anyone holding it can perform that one request (download or upload one object) until it expires, without having an access key. It is the standard way to:

  • let users download private files (invoices, reports, purchased files) straight from the bucket,
  • let browsers and mobile apps upload directly to the bucket, so large files don't pass through your server,
  • hand a file to another service for a limited time.

The URL is signed locally by the SDK with your secret key: creating one doesn't call Cloider, and it can't be revoked individually. It stops working when it expires, or when the access key that signed it is deleted or loses access to the bucket. The longest validity S3 allows is 7 days (604800 seconds).

Let a browser upload directly to the bucket​

Your server creates a presigned PUT URL for one key and content type, and the browser sends the file to it. The dashboard's own file browser uploads files exactly this way.

server.mjs (Node.js, Express)
import express from "express";
import { randomUUID } from "node:crypto";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({
region: "eu-central-2",
endpoint: "https://s3.eu-central-2.cloider.com",
requestChecksumCalculation: "WHEN_REQUIRED",
responseChecksumValidation: "WHEN_REQUIRED",
});

const app = express();
app.use(express.json());

app.post("/api/uploads", async (req, res) => {
// Check here that the user may upload, and validate the type and size
const { contentType } = req.body;
const key = `uploads/${randomUUID()}`;
const url = await getSignedUrl(
s3,
new PutObjectCommand({ Bucket: "acme-assets", Key: key, ContentType: contentType }),
{ expiresIn: 600 },
);
res.json({ url, key });
});

app.listen(3000);
browser.js
async function upload(file) {
const res = await fetch("/api/uploads", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ contentType: file.type || "application/octet-stream" }),
});
const { url, key } = await res.json();

// The Content-Type must be the one the URL was signed for
const put = await fetch(url, {
method: "PUT",
headers: { "Content-Type": file.type || "application/octet-stream" },
body: file,
});
if (!put.ok) throw new Error(`Upload failed: ${put.status}`);
return key;
}

Headers you sign (such as Content-Type) must be sent with exactly the same value, otherwise the storage answers SignatureDoesNotMatch.

Force a download file name​

Add a response header override to a presigned GET to make browsers download the file under a given name instead of displaying it:

url = s3.generate_presigned_url(
"get_object",
Params={
"Bucket": "acme-assets",
"Key": "invoices/2026/0042.pdf",
"ResponseContentDisposition": 'attachment; filename="invoice-0042.pdf"',
},
ExpiresIn=600,
)

Common patterns​

Store the key, not the URL​

Save an object's key (uploads/3f2c...) in your database rather than a full URL. You can then build the public URL (https://acme-assets.s3.eu-central-2.cloider.com/<key> on a public bucket) or create a fresh presigned URL whenever you need one.

Reuse one client​

Create the S3 client once when your app starts and reuse it. Each client keeps a pool of connections, and creating one per request pays a new TLS handshake every time.

Set Content-Type on upload​

Browsers display or download a file based on its Content-Type. Set it when uploading (ContentType in the SDKs), otherwise files are stored as application/octet-stream or whatever the SDK guesses.

Handle versioned buckets​

On a bucket with versioning, PutObject returns a VersionId, GetObject and DeleteObject accept a VersionId, and ListObjectVersions lists the history. Deleting without a version id adds a delete marker instead of removing data.

Troubleshooting​

ErrorFix
XAmzContentSHA256Mismatch, InvalidRequest or another error on every uploadSet the checksum calculation to when_required as in the examples. See Checksum errors on upload
getaddrinfo ENOTFOUND acme-assets.s3.eu-central-2.amazonaws.com or a similar AWS hostThe endpoint isn't set, so the SDK talks to AWS
SignatureDoesNotMatchWrong secret key, a header sent differently from the presigned one, or the client signs for another host. See Troubleshooting
AccessDeniedThe key is read only, doesn't open the bucket, or a policy denies the action. See Troubleshooting
NoSuchKeyNo object under that key. Keys are case sensitive and have no leading /
TypeError: Got an unexpected keyword argument 'request_checksum_calculation' (Python)boto3 is older than 1.36. Upgrade it, or remove the two checksum arguments (older versions don't send the new checksums)

Next steps​

Frequently asked questions

Can I use the AWS SDK with Cloider Object Storage?

Yes. Configure the SDK's S3 client with the endpoint https://s3.eu-central-2.cloider.com, the region eu-central-2, a Cloider access key and secret, and request checksum calculation set to when_required. Uploads, downloads, listing, deletes, multipart uploads and presigned URLs then work as on Amazon S3.

Do I need forcePathStyle for Cloider?

No. Cloider serves both virtual hosted style (https://<bucket>.s3.eu-central-2.cloider.com) and path style (https://s3.eu-central-2.cloider.com/<bucket>), so forcePathStyle can be true or false.

Why do uploads fail with the latest AWS SDK?

AWS SDKs released since January 2025 add a checksum to every upload by default, which Cloider's storage doesn't accept. Set requestChecksumCalculation (or request_checksum_calculation) to WHEN_REQUIRED, or set AWS_REQUEST_CHECKSUM_CALCULATION=when_required and AWS_RESPONSE_CHECKSUM_VALIDATION=when_required in the environment.

How long can a presigned URL be valid?

Up to 7 days (604800 seconds), as on Amazon S3. It also stops working as soon as the access key that signed it is deleted or loses access to the bucket.

Can users upload files from the browser directly to a bucket?

Yes. Your server creates a presigned PUT URL for one key and content type with an SDK, and the browser sends the file to that URL with the same Content-Type header. The dashboard's file browser uploads this way.