Skip to content

GCP.Storage reference

Source: src/GCP/Storage/Bucket.ts

A Google Cloud Storage bucket.

Generated name

const bucket = yield* GCP.Storage.Bucket("assets", {
forceDestroy: true,
});

Explicit name, location, and labels

const bucket = yield* GCP.Storage.Bucket("assets", {
bucketName: "my-app-assets",
location: "US-CENTRAL1",
storageClass: "STANDARD",
versioning: true,
labels: { env: "prod" },
forceDestroy: true,
});

Hierarchical namespace (folders)

const bucket = yield* GCP.Storage.Bucket("tree", {
hierarchicalNamespace: true,
uniformBucketLevelAccess: true,
forceDestroy: true,
});

Static website bucket with public read

const site = yield* GCP.Storage.Bucket("site", {
uniformBucketLevelAccess: true,
website: { mainPageSuffix: "index.html", notFoundPage: "404.html" },
forceDestroy: true,
});
yield* GCP.IAM.Member("PublicRead", {
kind: "storage.bucket",
name: site.bucketName,
role: "roles/storage.objectViewer",
member: "allUsers",
});
const bucket = yield* GCP.Storage.Bucket("assets", {
forceDestroy: true,
labels: { env: "prod" },
});
export class Api extends GCP.Function<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
const bucket = yield* GCP.Storage.Bucket("assets", { forceDestroy: true });
const putObject = yield* GCP.Storage.PutObject(bucket);
return {
fetch: Effect.gen(function* () {
yield* putObject({ name: "hello.txt", body: "Hello!" }).pipe(
Effect.orDie,
);
return HttpServerResponse.text("ok");
}),
};
}).pipe(Effect.provide([GCP.Storage.PutObjectHttp])),
) {}
const bucket = yield* GCP.Storage.Bucket("assets", {
forceDestroy: true,
});

forceDestroy: true deletes objects before the bucket. alchemy destroy waits until the bucket is gone.

Source: src/GCP/Storage/BucketAccessControl.ts

A Cloud Storage bucket ACL entry.

Fine-grained ACLs are identity (bucket, entity). Role is mutable; changing bucketName or entity replaces the entry. Entries have no labels field, so list / pnpm nuke:gcp discover them by enumerating alchemy-labeled buckets and skipping project-team defaults.

Uniform bucket-level access buckets do not support bucket ACLs.

BucketAccessControl: Creating a Bucket ACL

Section titled “BucketAccessControl: Creating a Bucket ACL”

Grant a service account writer access

const bucket = yield* GCP.Storage.Bucket("assets", {
forceDestroy: true,
});
const acl = yield* GCP.Storage.BucketAccessControl("writer", {
bucketName: bucket.bucketName,
entity: "user-app@project.iam.gserviceaccount.com",
role: "WRITER",
});

Update the role

const acl = yield* GCP.Storage.BucketAccessControl("writer", {
bucketName: bucket.bucketName,
entity: "user-app@project.iam.gserviceaccount.com",
role: "READER",
});

Source: src/GCP/Storage/BucketEventSource.ts

Event source streaming a Cloud Storage Bucket’s object changes (created, deleted, archived, metadata updated) into the hosting compute.

GCP.Storage.BucketEventSourceLive provisions a Pub/Sub topic owned by the host plus a JSON_API_V1 bucket notification on it (filtered by event type and object prefix), then delegates delivery to whichever GCP.PubSub.TopicEventSource implementation is provided alongside it: GCP.Run.TopicEventSource (push, for GCP.Function / GCP.CloudFunctions.Function) or GCP.Run.TopicPullEventSource (pull, for GCP.Run.Job / GCP.Run.WorkerPool).

Consume it through consumeBucketEvents.

BucketEventSource: Consuming Bucket Events

Section titled “BucketEventSource: Consuming Bucket Events”

Process uploads on a Cloud Run service

export class Thumbnails extends GCP.Function<Thumbnails>()(
"Thumbnails",
{ main: import.meta.url },
Effect.gen(function* () {
const uploads = yield* GCP.Storage.Bucket("Uploads", {});
yield* GCP.Storage.consumeBucketEvents(
uploads,
{ prefix: "incoming/" },
(events) =>
events.pipe(
Stream.runForEach((event) =>
Effect.log(`${event.object} (${event.metadata.size} bytes)`),
),
),
);
}).pipe(
Effect.provide(GCP.Storage.BucketEventSourceLive),
Effect.provide(GCP.Run.TopicEventSource),
),
) {}

Deletions on a worker pool

yield* GCP.Storage.consumeBucketEvents(
uploads,
{ eventTypes: ["OBJECT_DELETE", "OBJECT_ARCHIVE"] },
(events) => events.pipe(Stream.runForEach(handle)),
);
// …provided with GCP.Storage.BucketEventSourceLive
// and GCP.Run.TopicPullEventSource

Source: src/GCP/Storage/BucketEventSourceLive.ts Kind: Layer · Provides: GCP.Storage.BucketEventSource

Implementation of GCP.Storage.BucketEventSource over Pub/Sub.

Deploy-time: creates a Pub/Sub topic owned by the host ({bucket}-BucketEvents) and a JSON_API_V1 bucket notification that publishes the requested event types (and object prefix) to it; the notification grants the Cloud Storage service agent roles/pubsub.publisher on the topic. Delivery is delegated to the provided GCP.PubSub.TopicEventSource implementation, so provide one alongside this layer: GCP.Run.TopicEventSource (push, HTTP hosts) or GCP.Run.TopicPullEventSource (pull, Jobs and WorkerPools). Runtime: each message is parsed into a BucketEvent.

BucketEventSourceLive: Choosing the delivery

Section titled “BucketEventSourceLive: Choosing the delivery”

Push to a Cloud Run service

Effect.gen(function* () {
yield* GCP.Storage.consumeBucketEvents(bucket, (events) =>
events.pipe(Stream.runForEach((event) => Effect.log(event.object))),
);
}).pipe(
Effect.provide(GCP.Storage.BucketEventSourceLive),
Effect.provide(GCP.Run.TopicEventSource),
);

Pull on a worker pool

Effect.gen(function* () {
yield* GCP.Storage.consumeBucketEvents(bucket, (events) =>
events.pipe(Stream.runForEach((event) => Effect.log(event.object))),
);
return { run: Effect.never };
}).pipe(
Effect.provide(GCP.Storage.BucketEventSourceLive),
Effect.provide(GCP.Run.TopicPullEventSource),
);

Source: src/GCP/Storage/DefaultObjectAccessControl.ts

A Cloud Storage default object ACL entry on a bucket.

Default object ACLs are copied onto objects created without an explicit ACL. Identity is (bucket, entity). Role is mutable; changing bucketName or entity replaces the entry. Entries have no labels field, so list / pnpm nuke:gcp discover them by enumerating alchemy-labeled buckets and skipping project-team defaults.

Uniform bucket-level access buckets do not support default object ACLs.

DefaultObjectAccessControl: Creating a Default Object ACL

Section titled “DefaultObjectAccessControl: Creating a Default Object ACL”

Grant a service account reader access on new objects

const bucket = yield* GCP.Storage.Bucket("assets", {
forceDestroy: true,
});
const acl = yield* GCP.Storage.DefaultObjectAccessControl("reader", {
bucketName: bucket.bucketName,
entity: "user-app@project.iam.gserviceaccount.com",
role: "READER",
});

Update the role

const acl = yield* GCP.Storage.DefaultObjectAccessControl("reader", {
bucketName: bucket.bucketName,
entity: "user-app@project.iam.gserviceaccount.com",
role: "OWNER",
});

Source: src/GCP/Storage/DeleteObject.ts

Runtime binding for Cloud Storage objects.delete.

Bind this operation to a Bucket in a Function/Action init phase. Provide DeleteObjectHttp.

const deleteObject = yield* GCP.Storage.DeleteObject(bucket);
yield* deleteObject({ object: "hello.txt" });

Source: src/GCP/Storage/DeleteObjectHttp.ts Kind: Layer · Provides: GCP.Storage.DeleteObject

HTTP implementation of DeleteObject.

Source: src/GCP/Storage/Files.ts

Upload a local directory into a Cloud Storage bucket: one Object per file, keyed by its POSIX path relative to path, with Content-Type inferred from the extension. Files removed from the directory are deleted from the bucket on the next deploy (their Object resources become orphans).

const bucket = yield* GCP.Storage.Bucket("Site", { forceDestroy: true });
const objects = yield* GCP.Storage.Files("SiteFiles", {
bucketName: bucket.bucketName,
path: "./dist",
cacheControl: (key) =>
key.endsWith(".html") ? "no-cache" : "public, max-age=31536000, immutable",
});

Source: src/GCP/Storage/Folder.ts

A Cloud Storage folder in a hierarchical-namespace bucket.

Folders exist only on buckets created with hierarchicalNamespace: true (which also requires uniform bucket-level access). Identity is (bucket, folder name). Folders have no mutable fields and no labels, so list / pnpm nuke:gcp discover them by enumerating alchemy-labeled buckets.

Generated name

const bucket = yield* GCP.Storage.Bucket("tree", {
hierarchicalNamespace: true,
uniformBucketLevelAccess: true,
forceDestroy: true,
});
const folder = yield* GCP.Storage.Folder("uploads", {
bucketName: bucket.bucketName,
});

Explicit path

const folder = yield* GCP.Storage.Folder("uploads", {
bucketName: bucket.bucketName,
folderName: "uploads/incoming/",
});

Source: src/GCP/Storage/GetObject.ts

Runtime binding that downloads an object’s content from a Cloud Storage Bucket (objects.get with alt=media). Grants roles/storage.objectViewer on the bucket.

Bind this operation to a Bucket in a Function/Action init phase. Provide GetObjectHttp.

const getObject = yield* GCP.Storage.GetObject(bucket);
const { body } = yield* getObject({ object: "hello.txt" });
const text = new TextDecoder().decode(body);

Source: src/GCP/Storage/GetObjectHttp.ts Kind: Layer · Provides: GCP.Storage.GetObject

HTTP implementation of GetObject.

Source: src/GCP/Storage/HmacKey.ts

A Cloud Storage HMAC key for a service account.

HMAC keys authenticate S3-compatible interoperable requests. Cloud Storage returns the secret once at create time; Alchemy stores it redacted and never re-reads it. Keys have no labels field, so list enumerates every HMAC key in the project (excluding DELETED) for pnpm nuke:gcp. A service account may have at most ten keys in ACTIVE or INACTIVE state. Changing serviceAccountEmail replaces the key. Destroy deactivates the key, then deletes it.

Active key for a service account

const key = yield* GCP.Storage.HmacKey("interop", {
serviceAccountEmail: "app@project.iam.gserviceaccount.com",
});

Inactive key

const key = yield* GCP.Storage.HmacKey("interop", {
serviceAccountEmail: "app@project.iam.gserviceaccount.com",
state: "INACTIVE",
});

Source: src/GCP/Storage/Managed.ts

A Cloud Storage managed folder.

Managed folders provide IAM on an object-name prefix in a bucket with uniform bucket-level access. Identity is (bucket, folder name). Managed folders have no mutable fields and no labels, so list / pnpm nuke:gcp discover them by enumerating alchemy-labeled buckets.

Generated name

const bucket = yield* GCP.Storage.Bucket("assets", {
uniformBucketLevelAccess: true,
forceDestroy: true,
});
const folder = yield* GCP.Storage.Managed("team", {
bucketName: bucket.bucketName,
});

Explicit path

const folder = yield* GCP.Storage.Managed("team", {
bucketName: bucket.bucketName,
managedFolderName: "teams/payments/",
});

Source: src/GCP/Storage/Notification.ts

A Cloud Storage Pub/Sub notification configuration on a bucket.

GCS has no update API for notifications — every user-facing field is immutable and changing it replaces the config. The Cloud Storage service account is granted roles/pubsub.publisher on the topic during reconcile so events can actually be published.

Notifications carry no labels, and customAttributes are delivered on every message, so Alchemy does not stamp ownership markers. read finds the config by its recorded id; without one, an identical config on the bucket is reported as unowned.

Notify on every object event

const bucket = yield* GCP.Storage.Bucket("assets", {
forceDestroy: true,
});
const topic = yield* GCP.PubSub.Topic("events", {});
const notification = yield* GCP.Storage.Notification("object-events", {
bucketName: bucket.bucketName,
topic: topic.name,
});

Filtered events, prefix, and custom attributes

const notification = yield* GCP.Storage.Notification("uploads", {
bucketName: bucket.bucketName,
topic: topic.name,
payloadFormat: "JSON_API_V1",
eventTypes: ["OBJECT_FINALIZE"],
objectNamePrefix: "uploads/",
customAttributes: { env: "prod" },
});

Source: src/GCP/Storage/Object.ts

A single object in a Cloud Storage bucket whose content is declared as infrastructure — inline content or a local file.

Reconcile observes the live object and uploads only when its MD5, Content-Type, Cache-Control, or ownership metadata differ from the desired state. Use Files to upload a whole directory.

Inline content

const bucket = yield* GCP.Storage.Bucket("assets", { forceDestroy: true });
yield* GCP.Storage.Object("Robots", {
bucketName: bucket.bucketName,
key: "robots.txt",
content: "User-agent: *\nAllow: /\n",
});

A local file with caching

yield* GCP.Storage.Object("Logo", {
bucketName: bucket.bucketName,
key: "logo.svg",
file: "./public/logo.svg",
cacheControl: "public, max-age=31536000, immutable",
});

Source: src/GCP/Storage/ObjectAccessControl.ts

A Cloud Storage object ACL entry.

Fine-grained object ACLs are identity (bucket, object, entity). Role is mutable; changing bucketName, object, or entity replaces the entry. Entries have no labels field, so list / pnpm nuke:gcp discover them by enumerating objects in alchemy-labeled buckets and skipping project-team and numeric-owner defaults.

Uniform bucket-level access buckets do not support object ACLs.

ObjectAccessControl: Creating an Object ACL

Section titled “ObjectAccessControl: Creating an Object ACL”

Grant a service account reader access

const bucket = yield* GCP.Storage.Bucket("assets", {
forceDestroy: true,
});
const acl = yield* GCP.Storage.ObjectAccessControl("reader", {
bucketName: bucket.bucketName,
object: "hello.txt",
entity: "user-app@project.iam.gserviceaccount.com",
role: "READER",
});

Update the role

const acl = yield* GCP.Storage.ObjectAccessControl("reader", {
bucketName: bucket.bucketName,
object: "hello.txt",
entity: "user-app@project.iam.gserviceaccount.com",
role: "OWNER",
});

Source: src/GCP/Storage/PutObject.ts

Runtime binding that uploads an object’s content to a Cloud Storage Bucket (multipart objects.insert), overwriting any live object with the same name. Grants roles/storage.objectUser on the bucket (overwrite needs delete permission, which objectCreator lacks).

Bind this operation to a Bucket in a Function/Action init phase. Provide PutObjectHttp.

Write a text object

const putObject = yield* GCP.Storage.PutObject(bucket);
yield* putObject({ name: "hello.txt", body: "Hello, World!" });

Write bytes with metadata

yield* putObject({
name: "report.json",
body: new TextEncoder().encode(JSON.stringify(report)),
contentType: "application/json",
metadata: { source: "nightly" },
});

Source: src/GCP/Storage/PutObjectHttp.ts Kind: Layer · Provides: GCP.Storage.PutObject

HTTP implementation of PutObject.

Source: src/GCP/Storage/ReadBucket.ts

Read access to a Cloud Storage Bucket: head, get, list. Grants roles/storage.objectViewer on the bucket only.

const uploads = yield* GCP.Storage.ReadBucket(bucket);
const object = yield* uploads.get("hello.txt");
const text = object && new TextDecoder().decode(object.body);
const { objects } = yield* uploads.list({ prefix: "images/" });
// …provided with Effect.provide(GCP.Storage.ReadBucketHttp)

Source: src/GCP/Storage/ReadBucketHttp.ts Kind: Layer · Provides: GCP.Storage.ReadBucket

HTTP implementation of ReadBucket over the Cloud Storage JSON API.

Source: src/GCP/Storage/ReadWriteBucket.ts

Read and write access to a Cloud Storage Bucket. Grants roles/storage.objectUser on the bucket only.

const files = yield* GCP.Storage.ReadWriteBucket(bucket);
const source = yield* files.get("in.txt");
if (source) yield* files.put("out.txt", source.body);
// …provided with Effect.provide(GCP.Storage.ReadWriteBucketHttp)

Source: src/GCP/Storage/ReadWriteBucketHttp.ts Kind: Layer · Provides: GCP.Storage.ReadWriteBucket

HTTP implementation of ReadWriteBucket over the Cloud Storage JSON API.

Source: src/GCP/Storage/SignGetObjectUrl.ts

Mint V4 signed download (GET) URLs for objects in a Cloud Storage Bucket, so a browser can fetch an object without Google credentials.

The URL is signed with the host’s own runtime service account through the IAM Credentials signBlob API (no key file). The binding grants the host roles/storage.objectViewer on the bucket (a signed URL carries the signer’s permissions) and roles/iam.serviceAccountTokenCreator on its own service account only. See the signed URL guide.

Mint a signed GET URL

const signGetObjectUrl = yield* GCP.Storage.SignGetObjectUrl(bucket);
const url = yield* signGetObjectUrl({ object: "reports/2026.pdf" });
// hand `url` to a browser — it downloads the object without credentials
// …provided with Effect.provide(GCP.Storage.SignGetObjectUrlHttp)

Custom expiry and response Content-Type

const url = yield* signGetObjectUrl({
object: "reports/2026.pdf",
expiresIn: 3600,
contentType: "application/pdf",
});

Source: src/GCP/Storage/SignGetObjectUrlHttp.ts Kind: Layer · Provides: GCP.Storage.SignGetObjectUrl

HTTP implementation of SignGetObjectUrl: V4 signing where the signature comes from IAM Credentials signBlob as the runtime’s own service account (read from the metadata server).

Source: src/GCP/Storage/WriteBucket.ts

Write access to a Cloud Storage Bucket: put, delete. Grants roles/storage.objectUser on the bucket only (overwriting needs delete, which objectCreator lacks).

const uploads = yield* GCP.Storage.WriteBucket(bucket);
yield* uploads.put("hello.txt", "Hello!", { contentType: "text/plain" });
yield* uploads.delete("old.txt");
// …provided with Effect.provide(GCP.Storage.WriteBucketHttp)

Source: src/GCP/Storage/WriteBucketHttp.ts Kind: Layer · Provides: GCP.Storage.WriteBucket

HTTP implementation of WriteBucket over the Cloud Storage JSON API.