Skip to content
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 161 additions & 29 deletions alchemy-web/src/content/docs/providers/cloudflare/account-api-token.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
title: AccountApiToken
description: Learn how to create and manage Cloudflare Account API Tokens using Alchemy for secure access to the Cloudflare API.
description: Learn how to create and manage Cloudflare Account API Tokens using Alchemy for secure access to the Cloudflare API and R2 storage.
---

Creates a [Cloudflare API Token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) with specified permissions and access controls.
Creates a [Cloudflare API Token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) with specified permissions and access controls. Account API Tokens are the primary way to generate S3-compatible credentials for R2 storage, enabling pre-signed URLs and direct S3 API access.

## Minimal Example

Create a basic API token with read-only permissions.
Create a basic API token with read-only permissions for zones.

```ts
import { AccountApiToken } from "alchemy/cloudflare";
Expand All @@ -26,9 +26,126 @@ const token = await AccountApiToken("readonly-token", {
});
```

## R2 Bucket with Pre-Signed URLs

Create an API token for R2 bucket access and use it to generate pre-signed URLs. This is the recommended way to provide temporary access to R2 objects.

```ts
import { AccountApiToken, R2Bucket } from "alchemy/cloudflare";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

// Create R2 bucket
const bucket = await R2Bucket("my-bucket", {
name: "my-bucket",
});

// Create API token with R2 permissions
const r2Token = await AccountApiToken("r2-access-token", {
name: "R2 Access Token",
policies: [
{
effect: "allow",
permissionGroups: [
"Workers R2 Storage Read",
"Workers R2 Storage Write",
],
resources: {
[`com.cloudflare.edge.r2.bucket.${process.env.CLOUDFLARE_ACCOUNT_ID}_${bucket.jurisdiction ?? "default"}_${bucket.name}`]:
"*",
},
},
],
});

// Configure S3 client with the token
const s3Client = new S3Client({
region: "auto",
endpoint: `https://${process.env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: r2Token.accessKeyId.unencrypted,
secretAccessKey: r2Token.secretAccessKey.unencrypted,
},
});

// Generate a pre-signed URL for an object
const command = new GetObjectCommand({
Bucket: bucket.name,
Key: "path/to/file.pdf",
});

const signedUrl = await getSignedUrl(s3Client, command, {
expiresIn: 3600, // URL expires in 1 hour
});

console.log("Pre-signed URL:", signedUrl);
```

## R2 Storage in Worker

Bind an R2 bucket directly to a Worker for runtime access, and use an API token for generating pre-signed URLs from within the Worker.

```ts
import { Worker, AccountApiToken, R2Bucket } from "alchemy/cloudflare";

const bucket = await R2Bucket("storage", {
name: "my-storage",
});

const r2Token = await AccountApiToken("r2-token", {
name: "R2 Token for Signed URLs",
policies: [
{
effect: "allow",
permissionGroups: ["Workers R2 Storage Read"],
resources: {
[`com.cloudflare.edge.r2.bucket.${process.env.CLOUDFLARE_ACCOUNT_ID}_default_${bucket.name}`]:
"*",
},
},
],
});

await Worker("file-server", {
name: "file-server",
bindings: {
BUCKET: bucket,
R2_ACCESS_KEY_ID: r2Token.accessKeyId,
R2_SECRET_ACCESS_KEY: r2Token.secretAccessKey,
},
script: `
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

export default {
async fetch(request, env) {
// Generate pre-signed URL
const s3 = new S3Client({
region: "auto",
endpoint: "https://${process.env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com",
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
},
});

const command = new GetObjectCommand({
Bucket: "my-storage",
Key: "file.pdf",
});

const signedUrl = await getSignedUrl(s3, command, { expiresIn: 3600 });

return Response.json({ url: signedUrl });
}
}
`,
});
```

## With Time and IP Restrictions

Create a token with time-based and IP address restrictions.
Create a token with time-based and IP address restrictions for enhanced security.

```ts
import { AccountApiToken } from "alchemy/cloudflare";
Expand All @@ -55,19 +172,19 @@ const restrictedToken = await AccountApiToken("restricted-token", {
});
```

## R2 Storage Access Token
## Account-Level Permissions

Create a token with R2 storage write permissions.
Create a token with broad account-level permissions for R2 storage operations.

```ts
import { AccountApiToken } from "alchemy/cloudflare";

const storageToken = await AccountApiToken("account-access-token", {
name: "alchemy-account-access-token",
const accountToken = await AccountApiToken("account-access-token", {
name: "Account R2 Token",
policies: [
{
effect: "allow",
permissionGroups: ["Workers R2 Storage Write"],
permissionGroups: ["Workers R2 Storage Write", "Workers R2 Storage Read"],
resources: {
"com.cloudflare.api.account": "*",
},
Expand All @@ -76,31 +193,46 @@ const storageToken = await AccountApiToken("account-access-token", {
});
```

## Bind to a Worker
## Understanding Access Credentials

Use the token in a Worker binding.
The `AccountApiToken` resource provides S3-compatible credentials through two properties:

```ts
import { Worker, AccountApiToken } from "alchemy/cloudflare";
- **`accessKeyId`**: The token's ID, used as the AWS access key ID
- **`secretAccessKey`**: A SHA-256 hash of the token value, used as the AWS secret access key

These credentials work with any S3-compatible client library, including the AWS SDK.

const token = await AccountApiToken("api-token", {
name: "Worker API Token",
```ts
const token = await AccountApiToken("my-token", {
name: "My Token",
policies: [
{
effect: "allow",
permissionGroups: ["Zone Read"],
resources: {
"com.cloudflare.api.account.zone.*": "*",
},
},
/* ... */
],
});

await Worker("my-worker", {
name: "my-worker",
script: "console.log('Hello, world!')",
bindings: {
API_TOKEN: token,
},
});
// Use with S3 SDK
console.log("Access Key ID:", token.accessKeyId.unencrypted);
console.log("Secret Access Key:", token.secretAccessKey.unencrypted);
```

## Important Notes

:::tip[Pre-signed URLs]
Pre-signed URLs are the recommended way to provide temporary access to R2 objects. They're more secure than public access and work with existing S3 tooling.
:::

:::caution[API Token Requirements]
Creating Account API Tokens programmatically requires either:

- A Global API Key
- An API Token with permission to create other API Tokens

See the [Cloudflare setup guide](/guides/cloudflare/#api-token) for more details.
:::

:::note[Resource Naming]
When specifying R2 bucket resources, use the format:
`com.cloudflare.edge.r2.bucket.{accountId}_{jurisdiction}_{bucketName}`

For buckets without a jurisdiction, use `default` as the jurisdiction value.
:::
Loading