Loading...
Loading...
Setup domains in Cloudflare with DNS for Clerk, Vercel, and email routing. Use when adding new domains, configuring DNS records, or setting up email redirects.
npx skill4agent add andrehfp/tinyplate cloudflare# Add to .env.local
CLOUDFLARE_API_TOKEN="your-api-token"
CLOUDFLARE_ACCOUNT_ID="your-account-id"# Install wrangler
bun add -g wrangler
# Login (opens browser)
wrangler login
# Verify
wrangler whoami# Vercel CLI (required)
bun add -g vercel
vercel loginexample.commy-appcontactsupportme@gmail.com# If using API token
curl -X GET "https://api.cloudflare.com/client/v4/zones?name=DOMAIN" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" | jq '.result[0].id'
# If using wrangler
wrangler pages project list # Shows associated zones# Example: CNAME record
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "CNAME",
"name": "clerk",
"content": "frontend-api.clerk.dev",
"ttl": 1,
"proxied": false
}'
# Example: TXT record for verification
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "TXT",
"name": "@",
"content": "clerk-verification=xxxxx",
"ttl": 1
}'# Add domain to Vercel project
vercel domains add DOMAIN --scope=TEAM_SLUG
# Or link to specific project
vercel domains add DOMAIN PROJECT_NAME# A record for root domain
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "A",
"name": "@",
"content": "76.76.21.21",
"ttl": 1,
"proxied": false
}'
# CNAME for www subdomain
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "CNAME",
"name": "www",
"content": "cname.vercel-dns.com",
"ttl": 1,
"proxied": false
}'# Create destination address (must be verified first)
curl -X POST "https://api.cloudflare.com/client/v4/accounts/ACCOUNT_ID/email/routing/addresses" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"email": "your-main-email@gmail.com"
}'
# Create routing rule for contact@domain.com
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/email/routing/rules" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"name": "Forward contact",
"enabled": true,
"matchers": [{"type": "literal", "field": "to", "value": "contact@DOMAIN"}],
"actions": [{"type": "forward", "value": ["your-main-email@gmail.com"]}]
}'# MX records for Cloudflare Email Routing
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "MX",
"name": "@",
"content": "route1.mx.cloudflare.net",
"priority": 69,
"ttl": 1
}'
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "MX",
"name": "@",
"content": "route2.mx.cloudflare.net",
"priority": 46,
"ttl": 1
}'
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "MX",
"name": "@",
"content": "route3.mx.cloudflare.net",
"priority": 89,
"ttl": 1
}'
# TXT record for SPF
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "TXT",
"name": "@",
"content": "v=spf1 include:_spf.mx.cloudflare.net ~all",
"ttl": 1
}'# List all DNS records
curl -X GET "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result[] | {type, name, content}'
# Check Vercel domain status
vercel domains inspect DOMAIN
# Test email routing (send test email to contact@DOMAIN)/cloudflareWhat domain are you setting up?
> example.com
Paste the Clerk DNS records from your Clerk dashboard:
> [user pastes records]
What's the Vercel project name?
> my-saas-app
What email addresses should I create? (comma-separated)
> contact, support, hello
What email should these redirect to?
> myemail@gmail.com| Type | Use Case | Proxied |
|---|---|---|
| A | Root domain to IP | No (for Vercel) |
| CNAME | Subdomain to hostname | No (for Clerk/Vercel) |
| TXT | Verification, SPF | N/A |
| MX | Email routing | N/A |
| Issue | Solution |
|---|---|
| Zone not found | Domain must be added to Cloudflare first |
| DNS propagation slow | Wait 5-10 minutes, check with |
| Email not forwarding | Verify destination email first |
| Vercel 404 | Check DNS proxied=false for Vercel records |
| Clerk verification failed | Ensure TXT record is on root (@) |
# Check DNS propagation
dig DOMAIN +short
dig DOMAIN MX +short
dig DOMAIN TXT +short
# List zones in account
curl -X GET "https://api.cloudflare.com/client/v4/zones" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | jq '.result[] | {name, id}'
# Delete a DNS record
curl -X DELETE "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records/RECORD_ID" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"What do you want to do with R2?
1. Create new bucket (full setup)
2. Configure existing bucket (CORS, public access)
3. Setup custom domain for bucketBucket name?
> my-app-uploads
What will this bucket store?
1. User uploads (images, files) - needs CORS + presigned URLs
2. Static assets (public CDN) - needs public access
3. Backups (private) - no public access
> 1
Custom domain? (optional, press enter to skip)
> uploads.myapp.com# Create bucket via wrangler
wrangler r2 bucket create my-app-uploads
# Or via API
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/{account_id}/r2/buckets" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"name": "my-app-uploads", "locationHint": "wnam"}'cors.json{
"corsRules": [
{
"allowedOrigins": ["https://myapp.com", "http://localhost:3000"],
"allowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"allowedHeaders": ["*"],
"exposeHeaders": ["ETag", "Content-Length"],
"maxAgeSeconds": 3600
}
]
}wrangler r2 bucket cors put my-app-uploads --file=cors.json# Add CNAME record
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "CNAME",
"name": "uploads",
"content": "{account_id}.r2.cloudflarestorage.com",
"ttl": 1,
"proxied": true
}'.env.localR2_ACCESS_KEY_ID="your-access-key"
R2_SECRET_ACCESS_KEY="your-secret-key"
R2_ENDPOINT="https://{account_id}.r2.cloudflarestorage.com"
R2_BUCKET_NAME="my-app-uploads"# List buckets
wrangler r2 bucket list
# Create bucket
wrangler r2 bucket create BUCKET_NAME
# Delete bucket
wrangler r2 bucket delete BUCKET_NAME
# List objects
wrangler r2 object list BUCKET_NAME
# Upload file
wrangler r2 object put BUCKET_NAME/path/file.png --file=./local.png
# View CORS config
wrangler r2 bucket cors get BUCKET_NAME| Use Case | CORS | Public | Custom Domain |
|---|---|---|---|
| User uploads | Yes | No | Optional |
| Static assets/CDN | No | Yes | Recommended |
| Backups | No | No | No |
| Public downloads | No | Yes | Optional |
| Issue | Solution |
|---|---|
| CORS error in browser | Add domain to allowedOrigins |
| 403 Forbidden | Check API token has R2:Edit permission |
| Custom domain not working | Ensure CNAME is proxied (orange cloud) |
| Upload fails | Verify Content-Type header matches file |