Uploads images and files to Cloudflare R2 storage and returns a public URL.

Install

mkdir -p .claude/skills/r2-upload && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/11092" && unzip -o skill.zip -d .claude/skills/r2-upload && rm skill.zip

Installs to .claude/skills/r2-upload

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Upload images or files to Cloudflare R2 and get back a permanent public URL. Use this skill whenever any other skill (image generation, cover creation, infographic, comic, slide deck, etc.) produces an image that needs to be hosted or shared as a public URL. Also trigger directly when the user asks to "upload to R2", "store image on R2", "get a public URL for this image", "host this image", "save to Cloudflare", or "把图片上传到 R2". Supports local file paths, base64 / data-URL strings, and remote URLs (fetch + re-upload). Always invoke this skill rather than writing ad-hoc upload code.
587 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Intermediate

Key capabilities

  • Upload files to R2
  • Get public URLs
  • Handle local/remote files
  • Auto-prefix by MIME type

How it works

It uploads files to a specified R2 bucket and returns a permanent public URL, with automatic prefixing based on file type.

Inputs & outputs

You give it
File path or URL
You get back
Public URL

When to use r2-upload

  • Storing generated images
  • Hosting assets for web
  • Getting public URLs for files

About this skill

R2 Upload Skill

Upload a file to Cloudflare R2 and return its permanent public URL.


Step 0 — First-time setup (only when something fails)

Skip this step entirely if the user has used r2-upload before without issues — go straight to Step 2. upload.py already reports clear errors on its own.

Run the environment checker only when:

  • This is the user's first time using the skill, or
  • An upload just failed with an unfamiliar error
python3 ~/.claude/skills/r2-upload/scripts/check_env.py

The script outputs JSON and exits with code 0 (all OK) or 1 (issues found). Read the fix field of each failing item and work through them in order:

  1. python_version fails → Python is too old or missing
  2. boto3 fails → run pip3 install boto3 (Note: upload.py already detects this and prints the install command itself — you may not need check_env.py for this one)
  3. Any R2_* env var fails → walk the user through setting it up (see below)

After applying fixes, re-run the checker to confirm before proceeding.

If check_env.py itself fails to run (Python not found)

Python 3 is not available on this system. Please install it:
- macOS:   brew install python3  OR download from https://python.org
- Ubuntu:  sudo apt install python3
- Windows: download from https://python.org
After installation, restart your terminal and try again.

Step 1 — First-time setup guide

Only needed when env var checks fail. Walk the user through this:

1a. Create a Cloudflare R2 bucket (if not already done)

  1. Go to Cloudflare Dashboard → R2
  2. Click Create bucket, choose a name (e.g. my-assets)
  3. In bucket Settings → Public Access, enable public access
  4. Copy the pub-xxx.r2.dev URL shown — this is R2_PUBLIC_URL

1b. Create an R2 API token

  1. Go to Cloudflare Dashboard → R2 → Manage API tokens
  2. Click Create API token
  3. Set permissions: Object Read & Write on your specific bucket
  4. Save the generated Access Key ID (R2_ACCESS_KEY_ID) and Secret Access Key (R2_SECRET_ACCESS_KEY)
  5. Your Account ID appears in the URL: dash.cloudflare.com/<account_id>/

1c. Set environment variables

Ask the user to add these five lines to their shell profile (~/.zshrc or ~/.bashrc):

export R2_ACCOUNT_ID="<your-cloudflare-account-id>"
export R2_ACCESS_KEY_ID="<your-r2-token-key-id>"
export R2_SECRET_ACCESS_KEY="<your-r2-token-secret>"
export R2_BUCKET_NAME="<your-bucket-name>"
export R2_PUBLIC_URL="https://pub-xxx.r2.dev"

Then reload the shell:

source ~/.zshrc   # or source ~/.bashrc

After reloading, re-run the checker to confirm.


Step 2 — Running the upload

Auto-prefix by file type

When --prefix is not specified, the script automatically picks a prefix based on the detected MIME type:

File categoryAuto prefix
Images (image/*)images
Videos (video/*)videos
Audio (audio/*)audios
Everything elsefiles

You can always override this with --prefix for finer-grained organisation (e.g. --prefix images/covers).

From a local file (preferred)

python3 ~/.claude/skills/r2-upload/scripts/upload.py \
  --file /path/to/image.png \
  [--prefix images/covers] \
  [--name my-image] \
  [--bucket my-other-bucket]

From a base64 / data-URL string

# Short strings — inline is fine
python3 ~/.claude/skills/r2-upload/scripts/upload.py \
  --base64 "data:image/png;base64,iVBORw0KGgo..."

# Long strings — always use --base64-file to avoid shell ARG_MAX limits
printf '%s' "<base64-data>" > /tmp/r2_b64.txt
python3 ~/.claude/skills/r2-upload/scripts/upload.py \
  --base64-file /tmp/r2_b64.txt
rm -f /tmp/r2_b64.txt

From a remote URL (fetch + re-upload)

python3 ~/.claude/skills/r2-upload/scripts/upload.py \
  --url "https://example.com/photo.jpg"

All options

FlagDescription
--filePath to a local file
--base64Base64 string or data URL (data:image/png;base64,…)
--base64-filePath to a file containing base64 data (recommended for large payloads)
--urlRemote image URL to fetch and re-upload
--prefixPath prefix inside the bucket, e.g. images/covers
--nameCustom filename without extension (auto-generated if omitted)
--content-typeOverride auto-detected MIME type
--bucketOverride default bucket (R2_BUCKET_NAME env var)
--cache-controlCache-Control header (default: public, max-age=31536000, immutable)
--content-dispositionContent-Disposition header, e.g. attachment; filename=doc.pdf
--timeoutNetwork timeout in seconds (default: 60)

Step 3 — Output

On success, the script prints one JSON line to stdout:

{
  "url": "https://pub-xxx.r2.dev/images/covers/my-image-a1b2c3d.png",
  "key": "images/covers/my-image-a1b2c3d.png",
  "size": 204800,
  "contentType": "image/png",
  "uploadedAt": 1710835200000
}

Capture the URL in a shell variable:

RESULT=$(python3 ~/.claude/skills/r2-upload/scripts/upload.py --file /tmp/out.png)
URL=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['url'])")
echo "Uploaded: $URL"

On error, the script writes to stderr and exits with a non-zero code. Surface the full error message to the user.

Presenting the URL to the user

Always display the final URL inside a code block so it can be copied in full without line-break truncation:

`https://pub-xxx.r2.dev/images/my-image-a1b2c3d.png`

Never paste the URL as plain inline text — long URLs wrap in the terminal and are easy to copy incompletely.


Integration pattern for other skills

When another skill generates a file and wants a hosted URL, the prefix is chosen automatically based on file type (images → images/, videos → videos/, etc.), so you rarely need to pass --prefix explicitly.

# 1. Upload — prefix is auto-selected from MIME type
OUTPUT=$(python3 ~/.claude/skills/r2-upload/scripts/upload.py \
  --file /tmp/skill_output.png)

# 2. Extract URL
URL=$(echo "$OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['url'])")
echo "Hosted at: $URL"

If the upload fails, run check_env.py to diagnose the issue.

Pass --prefix only when you need a specific sub-path (e.g. --prefix images/covers).


Troubleshooting upload errors

Error messageLikely causeFix
Missing required environment variableEnv var not setRe-run check_env.py and follow its fix instructions
ModuleNotFoundError: No module named 'boto3'boto3 not installedpip3 install boto3
Upload failed: InvalidAccessKeyIdWrong key IDCheck R2_ACCESS_KEY_ID in Cloudflare R2 → Manage API tokens
Upload failed: SignatureDoesNotMatchWrong secretRegenerate API token and update R2_SECRET_ACCESS_KEY
Upload failed: NoSuchBucketBucket doesn't existCreate the bucket in Cloudflare Dashboard first
Failed to fetch URL (HTTP 403)Remote URL is access-controlledDownload manually, use --file instead
Remote file is too largeFile exceeds 500 MB limitDownload the file manually and use --file instead
File is too largeLocal file exceeds 500 MB limitSplit or compress the file before uploading
Decoded data is too largebase64 payload exceeds 500 MB after decodingUse a smaller file or compress first
Invalid base64 dataCorrupted or truncated base64 stringRe-generate the base64 data and try again
File not foundPath passed to --file or --base64-file does not existCheck the file path and try again
URL returns 403 after uploadBucket not publicEnable "Public access" in R2 bucket → Settings

When not to use it

  • Non-Cloudflare R2 storage
  • Files exceeding 500MB

Prerequisites

Cloudflare R2 bucketAPI token

Limitations

  • 500MB file size limit
  • Requires Cloudflare R2 setup

How it compares

It provides a standardized, automated upload and hosting workflow for generated assets.

Compared to similar skills

r2-upload side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
r2-upload (this skill)02moReviewIntermediate
telegram-bot-builder1066moReviewIntermediate
workflow-orchestration-patterns102moNo flagsAdvanced
bullmq-specialist256moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

telegram-bot-builder

davila7

Expert in building Telegram bots that solve real problems - from simple automation to complex AI-powered bots. Covers bot architecture, the Telegram Bot API, user experience, monetization strategies, and scaling bots to thousands of users. Use when: telegram bot, bot api, telegram automation, chat bot telegram, tg bot.

106130

workflow-orchestration-patterns

wshobson

Design durable workflows with Temporal for distributed systems. Covers workflow vs activity separation, saga patterns, state management, and determinism constraints. Use when building long-running processes, distributed transactions, or microservice orchestration.

10117

bullmq-specialist

davila7

BullMQ expert for Redis-backed job queues, background processing, and reliable async execution in Node.js/TypeScript applications. Use when: bullmq, bull queue, redis queue, background job, job queue.

2595

unity-mcp-orchestrator

CoplayDev

Orchestrate Unity Editor via MCP (Model Context Protocol) tools and resources. Use when working with Unity projects through MCP for Unity - creating/modifying GameObjects, editing scripts, managing scenes, running tests, or any Unity Editor automation. Provides best practices, tool schemas, and workflow patterns for effective Unity-MCP integration.

1795

async-python-patterns

wshobson

Master Python asyncio, concurrent programming, and async/await patterns for high-performance applications. Use when building async APIs, concurrent systems, or I/O-bound applications requiring non-blocking operations.

1299

modal

davila7

Run Python code in the cloud with serverless containers, GPUs, and autoscaling. Use when deploying ML models, running batch processing jobs, scheduling compute-intensive tasks, or serving APIs that require GPU acceleration or dynamic scaling.

587

Search skills

Search the agent skills registry