Providers
Cloudflare
Uses the Ahena CLI (beta). Install it with npm install -g @ahena/cli, or prefix commands with npx @ahena/cli. See Installation. The dashboard covers projects, connections, Doctor, the Stack Graph, plans and approvals without it.
Overview
Ahena connects to your Cloudflare account to set up R2 object storage (buckets, CORS)
and DNS records, and to check them. Your app talks to R2 directly through the generated
src/ahena/storage code (S3-compatible API). Ahena is never in the request path.
Package: @ahena/provider-cloudflare · Category: storage
Capabilities: r2, r2-cors, dns, zone-inspection
Connection
| Field | Secret | Stored as |
|---|---|---|
CLOUDFLARE_API_TOKEN |
yes | envelope-encrypted, bound to the environment |
CLOUDFLARE_ACCOUNT_ID (32 hex characters) |
no | connection setting |
CLOUDFLARE_API_TOKEN=… ahena connect cloudflare -e production # lists your accounts to pick from
Both user tokens and account-owned tokens work.
Capabilities
| Capability | What Ahena does |
|---|---|
r2 |
Creates buckets (BILLABLE, always approved explicitly); generates an S3-compatible storage adapter with presigned URLs. |
r2-cors |
Manages one CORS rule (id ahena) and keeps rules you added. |
dns |
Creates and updates records you declared, including email records from Resend. Never deletes records. |
zone-inspection |
Checks zone status and nameservers. |
Network: only api.cloudflare.com (enforced). ahena lock tracks bucket existence, CORS
origins, declared DNS records and zone status for drift.
Permissions
Create a custom token with only what you use:
| Permission | Needed for |
|---|---|
| Account › Workers R2 Storage › Read | Doctor, inspect |
| Account › Workers R2 Storage › Write | creating buckets, setting CORS |
| Zone › Zone › Read | DNS checks, zone status |
| Zone › DNS › Read / Edit | checking / adding DNS records |
Ahena probes what the token can do and reports it (r2:read, zone:read). A missing
permission shows up as a named Doctor finding rather than a vague failure.
Configure
Declare intent in ahena.config.ts:
storage: {
provider: "cloudflare-r2",
bucket: { production: "leo-uploads", development: "leo-uploads-dev" },
corsOrigins: { production: ["https://example.com"], development: ["http://localhost:3000"] },
},
dns: {
provider: "cloudflare",
zone: "example.com",
records: { production: [{ type: "CNAME", name: "files", content: "uploads.example.net" }] },
},
Then ahena configure cloudflare -e production shows the diff and asks for approval.
--desired file.json takes the same shape as the provider's desiredSchema.
| Change | Classification |
|---|---|
| Create an R2 bucket | BILLABLE (R2 usage beyond the free tier is billed). Needs admin and an extra confirmation; --yes also needs --allow-billable. |
| Set CORS | CONFIRMATION_REQUIRED. Ahena manages one rule (id: "ahena") and keeps every other rule. |
| Add a DNS record | CONFIRMATION_REQUIRED |
| Change an existing DNS record (A/AAAA/CNAME content, or replace the SPF record) | DESTRUCTIVE: it overwrites a live record, and the zone is shared by every environment. --yes also needs --allow-billable; through MCP it always asks the developer. |
Refused outright: * or localhost origins in production, non-https production origins,
CNAMEs that would collide with other records, records outside the zone.
DNS behavior: A/AAAA/CNAME are single-valued per name, so Ahena updates them in place (old → new shown in the diff). TXT/MX can have several values, so Ahena adds yours alongside the others. The exception is SPF: a name may have only one SPF record, so Ahena updates the existing one.
Generate
ahena generate cloudflare writes src/ahena/providers/cloudflare.ts (S3 client for R2),
src/ahena/storage/index.ts (upload, download, remove, uploadUrl, downloadUrl, raw)
and .env.example.cloudflare. It uses an R2 access key (R2 → Manage API tokens),
never your Cloudflare API token. Install @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner.
Limitations
- Ahena never deletes buckets, objects, CORS rules it didn't create, or DNS records.
- It doesn't create R2 access keys for your app. Create them in the dashboard.
- Zones must already be added to the account. Ahena doesn't add domains or change registrars.
- Workers routes and custom domains aren't managed yet.
Manual steps
| Situation | What to do |
|---|---|
| Zone pending | Turn off DNSSEC at the registrar, replace its nameservers with the two Cloudflare shows (Doctor prints them), wait for activation. |
| Token missing a permission | Edit the token in the Cloudflare dashboard and add the permission Doctor names. |
| Token expiring | Roll it, then ahena disconnect cloudflare and ahena connect cloudflare. |
Doctor checks
| Id | Severity | Meaning |
|---|---|---|
cloudflare.token |
PASS / WARNING / FAIL | Active, expiring within 14 days, or inactive. |
cloudflare.r2.access |
PASS / INFO / FAIL | Token can or can't list R2 buckets. |
cloudflare.r2.bucket |
PASS / FAIL | Configured bucket exists. |
cloudflare.r2.cors_wildcard |
FAIL | Production bucket allows every origin. |
cloudflare.r2.cors_local |
WARNING | localhost/private origins on a production bucket. |
cloudflare.r2.cors_missing |
PASS / FAIL | Your configured origins are allowed. |
cloudflare.r2.cors / _unmanaged |
INFO | No CORS policy, or rules Ahena didn't create. |
cloudflare.dns.zone |
PASS / WARNING / FAIL | Active, pending nameservers, missing, or no permission. |
cloudflare.dns.record.<type>.<name> |
PASS / FAIL | Configured record present, wrong, missing or conflicting. |
Disconnect behavior
Removes the connection and the token Ahena stored. Ahena didn't create the token, so it doesn't revoke it. It links you to https://dash.cloudflare.com/profile/api-tokens. Buckets, objects and DNS records are untouched.