Skip to Content

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:

NameTypeDescription
bucketIdstringBucket ID
keystringObject key (path)
dataBlob | FileFile content
options.contentTypestring?MIME type (auto-detected from key if omitted)
options.timeoutnumber?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); // 245760

Returns: DownloadResult

PropertyTypeDescription
bodyReadableStreamFile content stream
contentTypestringMIME type
sizenumberFile 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:

NameTypeDefaultDescription
prefixstring?—Filter by key prefix
maxKeysnumber?100Max results per page
continuationTokenstring?—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 needed

Delete 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.