# Cloudflare Secrets Store provider

> Publish SecretSpec values to Cloudflare account-level Secrets Store

**New in version 0.20**

The [Cloudflare](https://www.cloudflare.com/) provider publishes declared values to an account-level [Cloudflare Secrets Store](https://developers.cloudflare.com/secrets-store/) through the Cloudflare REST API.

## At a glance

|                 |                                                                                                            |
| --------------- | ---------------------------------------------------------------------------------------------------------- |
| Provider        | `cloudflare` (0.20+)                                                                                       |
| URI             | `cloudflare://STORE_ID[?account_id=ACCOUNT_ID][&OPTIONS]`                                                  |
| Access          | Write, delete, and discover names; plaintext values cannot be read back                                    |
| Best for        | Publishing secrets to Workers and other Cloudflare services from a separate source of truth                |
| Authentication  | API token or credentials from `wrangler auth token --json`                                                 |
| Availability    | SecretSpec 0.20+; included in official and default builds (`cloudflare` feature for custom minimal builds) |
| Default storage | Account secret named `{key}` in the selected store                                                         |

## Quick start

Find the account ID and Secrets Store ID in the Cloudflare dashboard or with Wrangler, then authenticate and configure an alias:

```bash
$ wrangler login
$ wrangler secrets-store store list --remote
```

**secretspec.toml**

```toml
[providers]
cloudflare_prod = "cloudflare://0123456789abcdef0123456789abcdef?account_id=abcdef0123456789abcdef0123456789&auth=wrangler"


[profiles.production]
DATABASE_URL = { description = "Production database URL" }
```

```bash
# Publish or replace the account secret
$ secretspec set DATABASE_URL --profile production --provider cloudflare_prod


# Remove it
$ secretspec delete DATABASE_URL --profile production --provider cloudflare_prod
```

Cloudflare never returns plaintext through its management API, so `secretspec get`, `check`, and `run` cannot resolve a value from this provider. Keep the authoritative value in a readable provider and select `cloudflare_prod` explicitly when publishing it.

## Setup

### Prerequisites

**New in version 0.20**

* SecretSpec 0.20 or newer
* A Cloudflare account with a Secrets Store
* Account **Secrets Store Write** permission for publishing and deletion
* The account ID and Secrets Store ID

The official SecretSpec CLI includes this provider. Custom minimal Rust builds enable it with `--features cloudflare`.

### Wrangler authentication

**New in version 0.20**

With `auth=wrangler`, SecretSpec runs:

```bash
$ wrangler auth token --json
```

Wrangler can return an API token, a refreshed OAuth token from `wrangler login`, or legacy API-key/email credentials. SecretSpec uses the returned credential only in HTTPS request headers. It never passes the account secret value to Wrangler.

Wrangler supports named authentication profiles:

```text
cloudflare://STORE_ID?account_id=ACCOUNT_ID&auth=wrangler&wrangler_profile=production
```

If the executable has another name or location, set `SECRETSPEC_WRANGLER_PATH`. SecretSpec never invokes `npx` automatically.

### Authentication with provider credentials

**New in version 0.20**

For CI or a machine identity, declare the `api_token` [provider credential](/reference/provider-credentials/):

**secretspec.toml**

```toml
[providers]
bootstrap = "keyring://"


[providers.cloudflare_prod]
uri = "cloudflare://0123456789abcdef0123456789abcdef?account_id=abcdef0123456789abcdef0123456789&auth=token"
credentials = { api_token = "bootstrap" }
```

Store it once:

```bash
$ secretspec config provider login cloudflare_prod
Enter api_token for provider 'cloudflare_prod' (source: bootstrap): ****
```

Use a scoped user or account API token with **Secrets Store Write** on only the required account. Do not use the full-access Global API Key for new setups.

### Environment fallback

**New in version 0.20**

`CLOUDFLARE_API_TOKEN` supplies the `api_token` credential when no explicit provider credential exists. `CLOUDFLARE_ACCOUNT_ID` supplies the account ID when the URI omits `account_id`.

The default `auth=auto` uses the provider credential or `CLOUDFLARE_API_TOKEN` first, then falls back to Wrangler. Use `auth=token` to require a token or `auth=wrangler` to require Wrangler credentials.

## Provider credentials

| Credential  | Environment fallback   | Available since |
| ----------- | ---------------------- | --------------- |
| `api_token` | `CLOUDFLARE_API_TOKEN` | 0.20+           |

In 0.21+, credentials retain their exact bytes during resolution. Interfaces that require text validate UTF-8 when consuming the credential; invalid input returns an error without selecting an environment fallback. Unix CLI environments accept non-UTF-8 bytes, but cannot contain NUL bytes. A configured credential that resolves to an empty value is an error rather than a fall through to the environment.

See the complete [provider credential reference](/reference/provider-credentials/) for all supported providers and environment fallbacks.

## Configuration

### URI format

**New in version 0.20**

```text
cloudflare://STORE_ID[?account_id=ACCOUNT_ID][&scopes=LIST][&auth=MODE][&wrangler_profile=NAME]
```

* `STORE_ID` is required and selects the account-level Secrets Store.
* `account_id` selects the Cloudflare account and falls back to `CLOUDFLARE_ACCOUNT_ID`.
* `scopes` is a comma-separated list applied when a secret is created or replaced. It defaults to `workers`. Supported values are `workers`, `ai_gateway`, `dex`, `access`, `containers`, and `websearch`.
* `auth` is `auto` (default), `token`, or `wrangler`.
* `wrangler_profile` selects a named Wrangler auth profile and requires `auth=wrangler`.

### URI examples

**New in version 0.20**

```text
cloudflare://STORE_ID?account_id=ACCOUNT_ID
cloudflare://STORE_ID?account_id=ACCOUNT_ID&auth=token
cloudflare://STORE_ID?account_id=ACCOUNT_ID&auth=wrangler
cloudflare://STORE_ID?account_id=ACCOUNT_ID&scopes=workers,containers
cloudflare://STORE_ID?account_id=ACCOUNT_ID&auth=wrangler&wrangler_profile=production
```

## Storage model

**New in version 0.20**

The provider maps a declaration key directly to an account-secret name. For example, `DATABASE_URL` maps to:

```text
account: ACCOUNT_ID
store:   STORE_ID
secret:  DATABASE_URL
```

Project and profile names are not added to the secret name. The store selected by the provider alias supplies isolation. Use a different alias and store when two profiles must hold different values for the same key.

Cloudflare Workers can bind that account secret to any binding name; the SecretSpec key does not need to match the Worker’s binding variable.

## Use existing secrets

**New in version 0.20**

A [`ref`](/reference/configuration/#secret-references) changes the Cloudflare secret name updated or deleted by SecretSpec:

**secretspec.toml**

```toml
[profiles.production]
DATABASE_URL = {
  description = "Production database URL",
  ref = { item = "PRIMARY_DATABASE_URL" }
}
```

The reference remains write-only: it can select an existing name but cannot retrieve its plaintext value.

## Discover secret names

**New in version 0.20**

Cloudflare’s list API exposes names, IDs, scopes, comments, and status without returning values. SecretSpec uses that metadata for declaration discovery:

```bash
$ secretspec init \
    --from 'cloudflare://STORE_ID?account_id=ACCOUNT_ID&auth=wrangler' \
    --project my-app --profile production
```

The generated manifest contains required declarations for active or pending secret names, not defaults or values.

## CI/CD

**New in version 0.20**

Use a short-lived or account-owned token scoped to Secrets Store Write:

```yaml
- run: secretspec set DATABASE_URL --profile production --provider cloudflare_prod
  env:
    CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
```

The account ID and store ID are attribution, not credentials, and can stay in the checked-in provider URI.

## Security considerations and limitations

**New in version 0.20**

* Cloudflare’s management API accepts values for creation and replacement but never returns them. Plaintext access exists only inside a Cloudflare service with a Secrets Store binding. This provider therefore cannot support `get`, `check`, `run`, fallback reads, generation-on-miss, prompting-on-miss, or value comparisons.
* Secret values are serialized directly into an HTTPS request body. They do not appear in the provider URI, command arguments, Wrangler input, or SecretSpec diagnostics.
* HTTP redirects are rejected so credentials and secret-bearing request bodies remain confined to Cloudflare’s API origin.
* `secretspec set` lists metadata to resolve an existing name to the secret ID, then creates or patches it. `secretspec delete` uses the same metadata lookup and remains idempotent when the name is absent.
* A replacement applies the `scopes` configured in the provider URI. Review those scopes because changing them affects which Cloudflare services may bind the secret.
* Secret values cannot exceed Cloudflare’s 65,536-byte limit.
* Cloudflare Secrets Store is currently a beta service; API behavior and scope availability may change upstream.