Skip to content

Cloud Storage

Cloud Storage holds files. Declare a Bucket, bind it to your service, and get, put, list, and delete objects.

src/resources.ts
import * as GCP from "@alchemy.run/gcp";
export const Uploads = GCP.Storage.Bucket("Uploads", {});

Alchemy generates a globally unique name.

Ask for the least access you need:

Binding Methods Role on the bucket
ReadBucket head, get, list roles/storage.objectViewer
WriteBucket put, delete roles/storage.objectUser
ReadWriteBucket all of the above roles/storage.objectUser
src/Api.ts
import * as GCP from "@alchemy.run/gcp";
import * as Effect from "effect/Effect";
import { HttpServerRequest } from "effect/http/HttpServerRequest";
import * as HttpServerResponse from "effect/http/HttpServerResponse";
import { Uploads } from "./resources.ts";
export default class Api extends GCP.Function<Api>()(
"Api",
{ main: import.meta.url, invokerIamDisabled: true },
Effect.gen(function* () {
const files = yield* GCP.Storage.ReadWriteBucket(Uploads);
return {
fetch: Effect.gen(function* () {
const request = yield* HttpServerRequest;
const name = new URL(request.originalUrl, "http://localhost").pathname.slice(1);
if (request.method === "PUT") {
const body = new Uint8Array(yield* request.arrayBuffer);
yield* files.put(name, body, {
contentType: request.headers["content-type"],
});
return HttpServerResponse.empty({ status: 204 });
}
const object = yield* files.get(name);
if (object === undefined) {
return HttpServerResponse.text("Not found", { status: 404 });
}
return HttpServerResponse.uint8Array(object.body, {
contentType: object.contentType ?? "application/octet-stream",
});
}).pipe(Effect.orDie),
};
}).pipe(Effect.provide(GCP.Storage.ReadWriteBucketHttp)),
) {}

get returns the bytes and content type, or undefined when the object is missing:

const object = yield* files.get("reports/2026.json");
const text = object && new TextDecoder().decode(object.body);

head returns only the metadata.

yield* files.put("reports/2026.json", JSON.stringify(report), {
contentType: "application/json",
});

put takes a string or bytes and overwrites any existing object.

const { objects, prefixes, nextPageToken } = yield* files.list({
prefix: "reports/",
delimiter: "/",
});

SignGetObjectUrl gives a browser a link to a private object:

const sign = yield* GCP.Storage.SignGetObjectUrl(Uploads);
// …
const url = yield* sign({ object: "reports/2026.pdf", expiresIn: 3600 });

The URL is signed with the service’s own identity. There’s no key file. Provide GCP.Storage.SignGetObjectUrlHttp.

Cloud Storage won’t delete a Bucket that still has objects. Set forceDestroy on Buckets whose contents you can lose:

export const Cache = GCP.Storage.Bucket("Cache", { forceDestroy: true });

Leave it off for user data.

Storage.Files uploads a local directory, one object per file, and only re-uploads what changed. See Static sites.

Run code when an object lands with Storage events.