Storage
Read and write the objects in your project’s S3-compatible buckets.
import { Ghayma } from "@ghayma/sdk";
const ghayma = new Ghayma({ apiKey: process.env.GHAYMA_API_KEY });These calls need the storage capability, which the Application purpose
grants. Creating and deleting buckets, rotating an app’s key and switching
them public or private are not in the SDK — they live in the
Dashboard and the ghayma storage commands.
Keys created before storage was enabled keep their capabilities. The
Application preset is expanded once, when the key is created, so an older key
has no storage capability and these calls answer 403. Create a new
Application key to pick it up.
Upload
Upload a file to a bucket.
// Node.js
import { readFileSync } from "fs";
const buffer = readFileSync("./photo.jpg");
await ghayma.storage.upload("bucket-id", "images/photo.jpg", new Blob([buffer]), {
contentType: "image/jpeg",
});
// Browser
const file = document.getElementById("fileInput").files[0];
await ghayma.storage.upload("bucket-id", `uploads/${file.name}`, file);Parameters:
| Name | Type | Description |
|---|---|---|
bucketId | string | Bucket ID |
key | string | Object key (path) |
data | Blob | File | File content |
options.contentType | string? | MIME type (auto-detected from key if omitted) |
options.timeout | number? | Upload timeout in ms (default: 5 minutes) |
Download
Download a file’s content as a stream. Ideal for server-side usage.
const file = await ghayma.storage.download("bucket-id", "images/photo.jpg");
console.log(file.contentType); // "image/jpeg"
console.log(file.size); // 245760Returns: DownloadResult
| Property | Type | Description |
|---|---|---|
body | ReadableStream | File content stream |
contentType | string | MIME type |
size | number | File size in bytes |
arrayBuffer() | Promise<ArrayBuffer> | Read entire file into memory |
blob() | Promise<Blob> | Read as Blob |
Save to disk (Node.js):
import { writeFileSync } from "fs";
const file = await ghayma.storage.download("bucket-id", "reports/q4.pdf");
writeFileSync("q4.pdf", Buffer.from(await file.arrayBuffer()));Serve in a Next.js API route:
// app/api/files/route.ts
export async function GET(req: Request) {
const key = new URL(req.url).searchParams.get("key")!;
const file = await ghayma.storage.download("bucket-id", key);
return new Response(file.body, {
headers: {
"Content-Type": file.contentType,
"Cache-Control": "public, max-age=3600",
},
});
}List Objects
List files in a bucket with optional prefix filtering and pagination.
const result = await ghayma.storage.listObjects("bucket-id", {
prefix: "images/",
maxKeys: 50,
});
for (const obj of result.objects) {
console.log(obj.key, obj.size, obj.last_modified);
}
// Paginate
if (result.is_truncated) {
const next = await ghayma.storage.listObjects("bucket-id", {
prefix: "images/",
continuationToken: result.continuation_token,
});
}Options:
| Name | Type | Default | Description |
|---|---|---|---|
prefix | string? | — | Filter by key prefix |
maxKeys | number? | 100 | Max results per page |
continuationToken | string? | — | Pagination token |
Every object in one call
listAllObjects walks every page and every folder under a prefix and returns
one flat array. Convenient for analytics or a full sweep; slow on a large
bucket, where listObjects and its pagination are the better tool.
const allFiles = await ghayma.storage.listAllObjects("bucket-id", "images/");Presigned URLs
Generate time-limited URLs that let browsers reach private files directly — no proxy route, and no credential in the page.
Download URL
const { url, expires_in } = await ghayma.storage.getDownloadUrl("bucket-id", "images/photo.jpg");
// url = "https://s3.ghayma.tech/...?X-Amz-Signature=..."
// expires_in = 3600 (seconds)Use directly in HTML:
<img src={url} alt="Photo" />
<a href={url} download>Download PDF</a>
<video src={url} controls />Upload URL
Let browsers upload files without ever seeing your project API key:
const { url } = await ghayma.storage.getUploadUrl("bucket-id", "uploads/photo.jpg", "image/jpeg");
// Client-side upload
await fetch(url, {
method: "PUT",
body: file,
headers: { "Content-Type": "image/jpeg" },
});These typed helpers (and a plain fetch PUT to the returned URL) are not
affected by the AWS SDK checksum change. If you instead build your own
@aws-sdk/client-s3 client (v3.729.0+) against the bucket credentials,
its default checksums make Garage reject uploads with 400 InvalidDigest —
see S3 Checksums for the one-line client config.
Listing Images with Presigned URLs (Next.js)
// app/api/images/route.ts
import { ghayma } from "@/lib/ghayma";
export async function GET() {
const result = await ghayma.storage.listObjects("bucket-id", { prefix: "images/" });
const images = await Promise.all(
result.objects
.filter((obj) => !obj.is_folder)
.map(async (obj) => ({
key: obj.key,
url: (await ghayma.storage.getDownloadUrl("bucket-id", obj.key)).url,
}))
);
return Response.json({ images });
}
// Frontend: <img src={image.url} /> — no proxy neededDelete Object
await ghayma.storage.deleteObject("bucket-id", "images/old-photo.jpg");Bucket Details
List Buckets
const buckets = await ghayma.storage.listBuckets();With a project API key this lists the key’s own project’s buckets.
Get Bucket
const bucket = await ghayma.storage.getBucket("bucket-id");
console.log(bucket.endpoint); // "https://s3.ghayma.tech"endpoint is the S3 endpoint the bucket is reached on, with no key; it needs
@ghayma/sdk 1.8.0 or later.
Get S3 Credentials
Get the site’s own S3 key, for use with any S3-compatible client:
const creds = await ghayma.storage.getCredentials("bucket-id");
console.log(creds.endpoint); // "https://s3.ghayma.tech"
console.log(creds.access_key); // "GK..."
console.log(creds.secret_key); // "..."
console.log(creds.bucket); // "st-myassets-a1b2c3d4"
console.log(creds.region); // "espace-garage"
console.log(creds.level); // "read-write" or "read"
console.log(creds.credential); // always "connection"Map them onto any S3 client as accessKeyId: access_key and
secretAccessKey: secret_key.
With a site’s managed GHAYMA_API_KEY this returns that site’s own key —
the one in its STORAGE_* variables — at the connection’s level (a read
key cannot write); credential is always "connection". Both fields need
@ghayma/sdk 1.8.0 or later. A bucket the site isn’t connected to is refused
with 403 (not_connected), and so is a key you created yourself
(site_key_required): it belongs to no site. A connection that has no key of
its own yet is refused with 409 (no_own_credential) — retry in a few
minutes — and an account token with 410 (shared_credentials_retired): a
bucket’s own key is never handed out. From your laptop,
ghayma env pull writes your site’s
STORAGE_* variables. See Managed keys.