> Part of the walkerOS documentation. Project overview and full index: <https://www.walkeros.io/llms.txt>

# GCS

<!-- -->

[Server](#)[ ](https://github.com/elbwalker/walkerOS/tree/main/packages/server/stores/gcs)

<!-- -->

[Source code](https://github.com/elbwalker/walkerOS/tree/main/packages/server/stores/gcs)[ ](https://www.npmjs.com/package/@walkeros/server-store-gcs)

<!-- -->

[Package](https://www.npmjs.com/package/@walkeros/server-store-gcs)

Google Cloud Storage store with zero runtime dependencies. Uses raw `fetch` against the GCS JSON API with built-in auth: Application Default Credentials (ADC) on Cloud Run / GKE, or explicit service account JWT for non-GCP environments.

## Installation[​](#installation "Direct link to Installation")

```
npm install @walkeros/server-store-gcs
```

* Integrated
* Bundled

```
import { startFlow } from '@walkeros/collector';
import { storeGcsInit } from '@walkeros/server-store-gcs';

await startFlow({
  stores: {
    assets: {
      code: storeGcsInit,
      config: {
        // Omit config.credentials for ADC on Cloud Run/GKE
        settings: {
          bucket: 'my-assets',
          prefix: 'public',
        },
      },
    },
  },
});
```

Add to your `flow.json`:

```
"stores": {
  "assets": {
    "package": "@walkeros/server-store-gcs",
    "config": {
      "settings": {
        "bucket": "my-assets",
        "prefix": "public"
      }
    }
  }
}
```

## Configuration[​](#configuration "Direct link to Configuration")

This <!-- -->store<!-- --> uses the standard <!-- -->store<!-- --> config wrapper (consent, data, env, id, ...). For the shared fields see [store<!-- --> configuration](https://www.walkeros.io/docs/stores.md#configuration). Package-specific fields live under `config.settings` and are listed below.

## Settings[​](#settings "Direct link to Settings")

| Property      | Type               | Description                                                                                                 | More |
| ------------- | ------------------ | ----------------------------------------------------------------------------------------------------------- | ---- |
| `bucket*`     | `string`           | GCS bucket name                                                                                             |      |
| `prefix`      | `string`           | Key prefix prepended to all store keys for scoping                                                          |      |
| `credentials` | `string \| object` | Service account JSON (string or object). Omit for ADC on Cloud Run/GKE (deprecated: use config.credentials) |      |

\* Required fields

## Mapping[​](#mapping "Direct link to Mapping")

This package does not define custom rule-level settings. For the standard rule fields (consent, condition, data, batch, name, policy) see [mapping](https://www.walkeros.io/docs/mapping.md).

## Examples

### Prefix scoping

Key "walker.js" with prefix "public/" resolves to GCS path "public/walker.js"

Event

```
{
  "operation": "get",
  "key": "walker.js",
  "settings": {
    "bucket": "my-assets",
    "prefix": "public"
  }
}
```

Out

```
get("public/walker.js", "Bytes<...>")
```

### Read with ADC

Read object from GCS bucket using ADC (no credentials needed), bytes byte-exact

Event

```
{
  "operation": "get",
  "key": "walker.js"
}
```

Out

```
get("walker.js", "Bytes<(function(){...})()>")
```

## Provisioning[​](#provisioning "Direct link to Provisioning")

The package ships an idempotent `setup()` lifecycle that creates the GCS bucket described in flow config. It is invoked only by the explicit operator command:

```
walkeros setup store.<id>
```

It never runs automatically and never alters an existing bucket.

### Setup options[​](#setup-options "Direct link to Setup options")

| Option         | Type                                                  | Default      | Notes                                                          |
| -------------- | ----------------------------------------------------- | ------------ | -------------------------------------------------------------- |
| `projectId`    | `string`                                              | (resolved)   | GCP project that owns the bucket. Resolution order below.      |
| `location`     | `string`                                              | `'EU'`       | Multi-region or regional location.                             |
| `storageClass` | `'STANDARD' \| 'NEARLINE' \| 'COLDLINE' \| 'ARCHIVE'` | `'STANDARD'` | Default object storage class.                                  |
| `versioning`   | `boolean`                                             | `false`      | Object versioning. Off by default; opt in.                     |
| `lifecycle`    | `{ rule: unknown[] }`                                 | (none)       | Applied at create. Drift detection NOT included for lifecycle. |
| `kmsKeyName`   | `string`                                              | (none)       | Customer-managed encryption key (CMEK) at create time.         |
| `labels`       | `Record<string, string>`                              | (none)       | Cost-allocation labels.                                        |

`bucket` is taken from `settings.bucket` and is NOT duplicated under `setup`.

### projectId resolution[​](#projectid-resolution "Direct link to projectId resolution")

The GCS create call requires a project. Resolution order:

1. Explicit `setup.projectId`.
2. `project_id` field inside the `config.credentials` service-account JSON.
3. `process.env.GOOGLE_CLOUD_PROJECT` (Cloud Run / GKE convention).
4. Throws with an actionable error if none of the above is available.

### Behavior[​](#behavior "Direct link to Behavior")

* **Idempotent**: HTTP 409 (bucket exists) is treated as success. The setup never patches or mutates an existing bucket.
* **Drift detection**: when the bucket already exists, setup performs a `GET /b/<bucket>` and logs `WARN setup.drift { field, declared, actual }` for any of `location`, `storageClass`, `versioning`, `iamConfiguration` (uniform bucket-level access, public access prevention), and `labels` that do not match. Drift is logged, never auto-fixed.
* **Defaults enforced at create**: uniform bucket-level access on, public access prevention enforced. These are baked in by the package.

### Runtime hard-fail[​](#runtime-hard-fail "Direct link to Runtime hard-fail")

At runtime, the first `get` / `set` / `delete` call issues a single `HEAD /b/<bucket>` per process per bucket. On 404, it throws with an actionable message:

```
GCS bucket not found: <bucket> in project <projectId>. Run "walkeros setup store.<id>" to create it.
```

Operators see the error pointing at the exact command to fix it. Subsequent operations in the same process skip the check via an in-memory cache.

## Authentication[​](#authentication "Direct link to Authentication")

### Cloud Run / GKE (ADC)[​](#cloud-run--gke-adc "Direct link to Cloud Run / GKE (ADC)")

When running on GCP infrastructure, omit `credentials`. The store fetches access tokens from the metadata server automatically, with no configuration needed.

### Non-GCP (service account)[​](#non-gcp-service-account "Direct link to Non-GCP (service account)")

Pass a service account JSON via `config.credentials` with a `$env.` reference. The store signs JWTs locally using `node:crypto` and exchanges them for access tokens via Google's OAuth2 endpoint.

```
"config": {
  "credentials": "$env.GCS_SA_KEY",
  "settings": { "bucket": "my-assets", "prefix": "public" }
}
```

The `GCS_SA_KEY` environment variable should contain the full service account JSON (with `client_email` and `private_key` fields).

## File serving pattern[​](#file-serving-pattern "Direct link to File serving pattern")

The primary use case is serving static files via the file transformer. Set `file: true` so the store persists and returns bytes byte-exact:

```
{
  "stores": {
    "assets": {
      "package": "@walkeros/server-store-gcs",
      "config": {
        "settings": {
          "bucket": "my-assets",
          "prefix": "public"
        },
        "file": true
      }
    }
  },
  "transformers": {
    "file": {
      "package": "@walkeros/server-transformer-file",
      "config": { "settings": { "prefix": "/static" } },
      "env": { "store": "$store.assets" }
    }
  }
}
```

A request to `/static/walker.js` looks up `public/walker.js` in the `my-assets` bucket. Omit `file` for a structured key-value store instead.

## Security[​](#security "Direct link to Security")

* **Key validation**: Path traversal attempts (`..`, absolute paths) are rejected
* **Prefix scoping**: The `prefix` setting restricts all operations to a subdirectory
* **No ambient credentials**: ADC only works on GCP infrastructure; off-GCP requires explicit SA JSON

## Structured vs file mode[​](#structured-vs-file-mode "Direct link to Structured vs file mode")

The GCS store has two modes, decided once at init by `config.file`:

* **Structured (default):** values are structured `StoreValue` data, serialized by the shared core codec and stored with `Content-Type: application/json`. Use for session state, lookups, and cached responses.
* **File (`file: true`):** values persist as raw bytes byte-exact, stored with the real mime derived from the key (or `application/octet-stream` when unknown). `set()` accepts a `Uint8Array` or `string`; `get()` hands the exact bytes back. Use for serving assets such as walker.js.

One store instance is exactly one mode.

## API[​](#api "Direct link to API")

```
const file = await store.get('walker.js');           // StoreValue | undefined
await store.set('data.json', { ok: true });          // structured value
await store.set('walker.js', new Uint8Array([/*…*/])); // file mode: raw bytes
await store.delete('old-file.txt');                   // void
```

In file mode, `get()` returns the bytes as a `Uint8Array` leaf and `set()` rejects non-`Uint8Array`, non-`string` values with a clear error.
