---
title: Google Cloud Storage
icon: google
description: Set up Google Cloud Storage as a MIRAGE resource from Node or the browser.
---

GCS exposes an S3-compatible API when you use HMAC keys, so MIRAGE signs GCS requests with the same AWS Signature V4 used for S3, against the endpoint `https://storage.googleapis.com`.

Credentials (HMAC access key + secret for a service account) are created the same way in both runtimes, see [GCS Credentials](/home/setup/gcs).

## Node (server-side)

```bash
pnpm add @struktoai/mirage-node
```

```ts
import { GCSResource, MountMode, Workspace } from '@struktoai/mirage-node'

const gcs = new GCSResource({
  bucket: process.env.GCS_BUCKET!,
  accessKeyId: process.env.GCS_ACCESS_KEY_ID!,
  secretAccessKey: process.env.GCS_SECRET_ACCESS_KEY!,
})

const ws = new Workspace({ '/bucket/': gcs }, { mode: MountMode.READ })
const res = await ws.execute('ls /bucket/')
console.log(res.stdoutText)
```

## Browser (presigned URLs)

```bash
pnpm add @struktoai/mirage-browser
```

The browser `GCSResource` is secret-free, your backend signs each operation using your HMAC keys and hands back a URL. Since GCS accepts AWS Signature V4, the same `@aws-sdk/s3-request-presigner` code used for S3 works, pointed at the GCS endpoint.

### 1. Server: sign URLs with the GCS endpoint

```ts
import {
  CopyObjectCommand,
  DeleteObjectCommand,
  GetObjectCommand,
  HeadObjectCommand,
  ListObjectsV2Command,
  PutObjectCommand,
  S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'

const client = new S3Client({
  region: 'auto',
  endpoint: 'https://storage.googleapis.com',
  credentials: {
    accessKeyId: process.env.GCS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.GCS_SECRET_ACCESS_KEY!,
  },
})
const BUCKET = process.env.GCS_BUCKET!

app.post('/presign/gcs', async (req, res) => {
  const { path, op, opts } = req.body
  const key = path.replace(/^\/+/, '')
  const ttl = typeof opts?.ttlSec === 'number' ? opts.ttlSec : 300
  let cmd
  switch (op) {
    case 'GET':    cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key }); break
    case 'PUT':    cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: opts?.contentType }); break
    case 'HEAD':   cmd = new HeadObjectCommand({ Bucket: BUCKET, Key: key }); break
    case 'DELETE': cmd = new DeleteObjectCommand({ Bucket: BUCKET, Key: key }); break
    case 'LIST':   cmd = new ListObjectsV2Command({
      Bucket: BUCKET,
      Prefix: opts?.listPrefix,
      Delimiter: opts?.listDelimiter,
      ContinuationToken: opts?.listContinuationToken,
    }); break
    case 'COPY':   cmd = new CopyObjectCommand({
      Bucket: BUCKET,
      Key: key,
      CopySource: `${BUCKET}/${opts?.copySource}`,
    }); break
  }
  res.json({ url: await getSignedUrl(client, cmd, { expiresIn: ttl }) })
})
```

### 2. Browser: wire it up

```ts
import { GCSResource, MountMode, Workspace } from '@struktoai/mirage-browser'

const gcs = new GCSResource({
  bucket: 'my-gcs-bucket',
  presignedUrlProvider: async (path, op, opts) => {
    const r = await fetch('/presign/gcs', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ path, op, opts }),
    })
    const { url } = await r.json()
    return url
  },
})

const ws = new Workspace({ '/bucket/': gcs }, { mode: MountMode.READ })
```

<Note>
If you prefer Google's native signer (Google-provided IAM-signed URLs via `@google-cloud/storage`'s `getSignedUrl`), you can use it here too, the presigner contract only cares that the returned URL works with `fetch()`. Native signing avoids shipping HMAC keys to your backend.
</Note>

### 3. Configure CORS on the bucket

GCS rejects S3-flavored `PutBucketCors` calls, its CORS schema differs. Use **either** the native XML API (via HMAC keys, same creds your presigner uses) **or** `gsutil cors set`.

**Option A, helper script (no gcloud install):**

The browser example ships a signer that crafts the GCS-shaped CORS XML and submits it via AWS SigV4 to `https://storage.googleapis.com/<bucket>?cors`:

```bash
cd examples/typescript/browser
npx tsx scripts/configure-gcs-cors.ts http://localhost:5173 https://app.example.com
```

**Option B, `gsutil`:**

```bash
brew install google-cloud-sdk
gcloud auth login

cat > cors.json <<'EOF'
[
  {
    "origin": ["http://localhost:5173", "https://app.example.com"],
    "method": ["GET", "PUT", "HEAD", "DELETE", "POST"],
    "responseHeader": ["Content-Type", "ETag", "Last-Modified"],
    "maxAgeSeconds": 3000
  }
]
EOF

gsutil cors set cors.json gs://$GCS_BUCKET
```

<Warning>
GCS's XML API accepts `<CorsConfig><Cors><Origins>…</Origins><Methods>…</Methods><ResponseHeaders>…</ResponseHeaders><MaxAgeSec>…</MaxAgeSec></Cors></CorsConfig>`. There is no `<Headers>` / `<AllowedHeaders>` element, GCS allows all request headers through automatically.
</Warning>

See [Python GCS Setup](/python/setup/gcs) for the equivalent Python wiring.
