Send Base64 Images

Su Suriya Ravichandran Updated 10 Nov 2026
6 min read ·Lesson 109 of 200

Sending the Image Itself

A base64 image source embeds the image data directly in the request. Nothing needs to be hosted or uploaded first, which makes it the most direct option for one-off images, and the right choice for images that users upload to your application:

{
    "type": "image",
    "source": {
        "type": "base64",
        "media_type": "image/jpeg",
        "data": "<base64 text>",
    },
}

Base64 is an encoding, not compression. It represents binary data as text using 64 printable characters. Every three bytes become four characters, so encoded data is about one third larger than the original. That overhead is why the size limit applies to the encoded text (Lesson 111).


Building a Safe Image Block

Several checks belong in one function that turns a file into an image block:

  1. Read the bytes.
  2. Detect the format from its signature (Lesson 111), and reject anything unsupported.
  3. Check the encoded size against the limit, before sending.
  4. Build the block with the detected media type, not the file extension.
import base64

from media_utils import MAX_IMAGE_BASE64_BYTES, detect_media_type, encode_base64


def image_block(data: bytes) -> dict:
    media_type = detect_media_type(data)
    if media_type is None:
        raise ValueError("unsupported image format; use JPEG, PNG, GIF, or WebP")
    encoded = encode_base64(data)
    if len(encoded) > MAX_IMAGE_BASE64_BYTES:
        raise ValueError(f"image is too large after encoding ({len(encoded):,} characters); "
                         "resize it to under 10 MB encoded")
    return {"type": "image", "source": {"type": "base64", "media_type": media_type, "data": encoded}}

Because the function works on bytes, it fits both files from disk and uploads received by a web application.


Resizing Large Images

When an image is too large, resize it before encoding. The Pillow library does this in a few lines:

python -m pip install pillow
from io import BytesIO

from PIL import Image


def shrink(data: bytes, longest_edge: int = 1568) -> bytes:
    image = Image.open(BytesIO(data))
    image.thumbnail((longest_edge, longest_edge))     # Keeps proportions; only shrinks
    output = BytesIO()
    image.convert("RGB").save(output, format="JPEG", quality=90)
    return output.getvalue()
  • thumbnail() resizes in place while keeping the aspect ratio, and never enlarges.
  • convert("RGB") removes transparency, because JPEG does not support it.
  • quality=90 keeps reasonable detail at a smaller size.

Use 1568 pixels for standard models, as the documentation recommends, and larger only when fine detail matters. Resizing is lossy, so keep the original if you need it later. Note that the output format changes to JPEG, so the media type passed to image_block() must change with it.


Practical Example: Checking and Sending an Uploaded Image

This example validates an image's bytes, builds the block, and asks Claude a question. An offline check shows the rejection paths, which do not need network access.

"""base64_image.py: validate image bytes, then send them as a base64 image block."""

from anthropic import Anthropic

from media_utils import MAX_IMAGE_BASE64_BYTES, detect_media_type, encode_base64, make_png

client = Anthropic()


def image_block(data: bytes) -> dict:
    media_type = detect_media_type(data)
    if media_type is None:
        raise ValueError("unsupported image format; use JPEG, PNG, GIF, or WebP")
    encoded = encode_base64(data)
    if len(encoded) > MAX_IMAGE_BASE64_BYTES:
        raise ValueError(f"image is too large after encoding ({len(encoded):,} characters)")
    return {"type": "image", "source": {"type": "base64", "media_type": media_type, "data": encoded}}


def ask_about_image(data: bytes, question: str) -> str:
    message = client.messages.create(
        model="claude-opus-5-5",
        max_tokens=1024,
        messages=[{"role": "user", "content": [image_block(data), {"type": "text", "text": question}]}],
    )
    return "".join(block.text for block in message.content if block.type == "text").strip()


if __name__ == "__main__":
    # Offline checks: no API calls
    for label, data in [
        ("valid PNG", make_png(32, 32, (0, 128, 255))),
        ("text file", b"Hello, this is not an image."),
        ("oversized", b"\x89PNG\r\n\x1a\n" + b"0" * (8 * 1024 * 1024)),
    ]:
        try:
            block = image_block(data)
            print(f"{label}: accepted as {block['source']['media_type']}")
        except ValueError as error:
            print(f"{label}: rejected ({error})")

    print("Claude:", ask_about_image(make_png(32, 32, (0, 128, 255)), "What color is this? One word."))

Expected output:

valid PNG: accepted as image/png
text file: rejected (unsupported image format; use JPEG, PNG, GIF, or WebP)
oversized: rejected (image is too large after encoding (11,184,824 characters))
Claude: Blue

How It Works

  1. image_block() validates before sending. The text file fails format detection. The oversized file passes the format check, because its header is a valid PNG signature, and fails the size check instead. Each failure has its own message, so the caller can explain the problem.
  2. The size is checked after encoding, because that is the value the API limit applies to. The oversized example is 8 MB of data plus an 8-byte header, which encodes to 11,184,824 characters. Base64 always produces four characters for every three bytes, rounded up, so that number follows directly from the data length.
  3. The media type comes from the bytes. The block always matches what the data actually is.
  4. ask_about_image() builds the request using the validated block, followed by the question.
  5. The offline checks run first, so the rejection logic can be verified without spending tokens.

Note: a valid PNG signature does not mean the rest of the file is valid. A corrupt image can pass these checks and still fail at the API. Lesson 120 covers how to handle that case.


Common Mistakes

Checking the Original Size Instead of the Encoded Size

What happens: files just under the raw limit are rejected by the API.

Fix: measure the length of the encoded string.

Trusting the File Extension

What happens: a file named .jpg that is really a PDF is sent with the wrong media type.

Fix: detect the format from the bytes.

Sending Unnecessarily Large Images

What happens: slower requests and higher token costs, with no benefit for most tasks.

Fix: resize to 1568 pixels or less unless fine detail matters.

Keeping the Only Copy After Resizing

What happens: the original high-resolution image is lost.

Fix: store the original, and send the resized copy.

Logging Image Data

What happens: base64 strings in logs are very long, and expose the image content.

Fix: log the media type and size, not the data.


Best Practices

  • Validate bytes: detect the format and check the size before sending.
  • Measure the encoded size, since that is what the limit applies to.
  • Resize before encoding when the task allows.
  • Keep originals separately from processed copies.
  • Never log image data.
  • Reuse validated blocks when the same image is needed twice, or use the Files API (Lesson 116).

Exercise

  1. Use make_png() to create images of increasing size, such as 500 × 500, 2000 × 2000, and 4000 × 4000 pixels. Which ones are accepted, and why might a PNG of solid color not reach the limit?
  2. Modify shrink() to return a PNG instead of a JPEG, and explain what changes in image_block().
  3. Add a check that rejects images with a width or height over 8000 pixels, using Image.open() to read the dimensions without decoding everything.
  4. Explain why shrink() converts the image to RGB before saving as JPEG.
Show answers for steps 2 and 4

Step 2: a PNG output keeps transparency, but the output file is usually larger for photographs. image_block() does not need changes, because it detects the format from the bytes, not from the function that produced them. The media type will be image/png automatically. That is the benefit of detecting from the bytes rather than trusting a name.

Step 4: JPEG has no transparency channel. An image with transparency, or in a mode such as palette-based PNG, cannot be saved as JPEG directly, and attempting to do so raises an error or produces wrong colors. Converting to RGB first makes the output valid, and removes any transparency by flattening it onto black, which is acceptable for analysis. For graphics that depend on transparency, save as PNG instead.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Send Base64 Images and get answers drawn from it.

Signed-in readers only.