Features Apograph CMS on GitHub

Google Cloud Storage

Media plugin @apograph/media-server@apograph/media-admin

Native auth, Workload Identity, and when you do not need this package at all.

Documents 0.5.2 Updated Edit this page Report a problem

On this page

You may not need this package

GCS speaks the S3 XML API in interoperability mode, so the S3 provider reaches it today:

createS3StorageProvider({
    bucket,
    endpoint: 'https://storage.googleapis.com',
    credentials: { accessKeyId: HMAC_ACCESS_ID, secretAccessKey: HMAC_SECRET }
});

That is one fewer package to keep, and it is the right answer for most deployments.

This adapter exists for the one it is not. HMAC keys are a long-lived secret many organizations forbid by policy, and they rule out Workload Identity. Native authentication is the reason to be here.

Install

npm install @apograph/media-provider-gcs
import { createGcsStorageProvider } from '@apograph/media-provider-gcs';

MediaServerPlugin({
    provider: createGcsStorageProvider(config.plugins.media.storage),
    config: config.plugins.media
});

Configuration

{ bucket, projectId?, keyFilename?, credentials?, keyPrefix?, signWithIam?, bucketClient? }

VariableWhat it does
MEDIA_GCS_BUCKETThe bucket name. Required
MEDIA_GCS_PROJECT_IDOptional; Application Default Credentials otherwise
MEDIA_GCS_KEY_FILEPath to a service-account key file
MEDIA_GCS_SIGN_WITH_IAMSign through IAM rather than a private key

With no key file and no inline credentials the client uses Application Default Credentials, which is what a GKE or Cloud Run deployment wants.

bucketClient takes an already-built Bucket — hand-built auth, or a test stub.

directUrl is computed, not hardcoded

Signing needs a private key, or an explicit opt-in to IAM signBlob.

How you authenticatedirectUrl
keyFilename or inline credentialstrue
signWithIam: truetrue
Bare Application Default Credentialsfalse — downloads are proxied

signWithIam is opt-in, not assumed

It needs the iam.serviceAccounts.signBlob permission. Assuming it would mint URLs that fail at request time, and would let the plugin accept MEDIA_DIRECT_SERVE=signed-url on a deployment that cannot honour it.

Under Workload Identity, this is how you sign: the library asks IAM to sign for it.

What not to lose in a refactor

get reads metadata before it opens the stream. createReadStream opens lazily, so a missing object would surface as an error on the stream — by which point the response is already a streaming 200 that can no longer become the 404 the route owes the caller.

One extra call buys that back. It is the port’s contract, not an optimization waiting to be removed.

A failed upload deletes the object. A resumable upload that dies part-way leaves an incomplete object the bucket keeps until a lifecycle rule sweeps it, and the key never reached a caller.