Skip to content

Programmatically Upload Images to Airtable Cells with Python

In enterprise automation, simply syncing text data is rarely enough. Advanced workflows—such as automated product cataloging, dynamic report generation, or AI-driven image analysis—require the ability to programmatically push visual assets into a database. While Airtable’s UI makes image management intuitive, doing so via Python requires a specific architectural understanding of how Airtable handles file hosting and attachment arrays.

The “Aha!” Solution: The URL-First Approach

Section titled “The “Aha!” Solution: The URL-First Approach”

The primary hurdle for developers is that Airtable’s REST API does not allow you to upload a raw binary file (multipart/form-data) directly into a cell in a single request.

The “Aha!” moment comes from realizing that Airtable acts as a crawler: You provide a publicly accessible URL of the image, and Airtable’s servers fetch, optimize, and host that image internally. To “insert” an image, you must pass an array of objects containing a url key. Airtable then handles the ingestion asynchronously.

Before writing code, ensure your environment is ready. You will need a Personal Access Token (PAT) with data.records:write scopes.

Requirement Where to find it
Base ID Help > API Documentation or the URL (starts with app...)
Table ID/Name The name of your table (e.g., “Inventory”)
Record ID The unique ID of the row (starts with rec...)
Field Name The exact name of the “Attachment” type column

When updating an attachment field, the payload must be an array, even if you are only uploading a single image.

Required JSON Payload:

{
"fields": {
"Photos": [
{
"url": "https://example.com/path/to/your/image.jpg",
"filename": "optional_custom_name.jpg"
}
]
}
}

Using the requests library, we will send a PATCH request to update an existing record with a new image.

import requests
# Configuration
BASE_ID = 'your_base_id'
TABLE_ID = 'your_table_name_or_id'
RECORD_ID = 'your_record_id'
PERSONAL_ACCESS_TOKEN = 'your_pat_token'
url = f"https://api.airtable.com/v0/{BASE_ID}/{TABLE_ID}/{RECORD_ID}"
headers = {
"Authorization": f"Bearer {PERSONAL_ACCESS_TOKEN}",
"Content-Type": "application/json"
}
# The data payload
data = {
"fields": {
"ImageField": [
{
"url": "https://cdn.pixabay.com/photo/2023/01/01/00/00/demo.jpg"
}
]
}
}
response = requests.patch(url, headers=headers, json=data)
if response.status_code == 200:
print("Success: Image queued for upload.")
else:
print(f"Error: {response.status_code}", response.text)

By default, the PATCH request shown above will overwrite all existing images in that cell. If you want to append an image to an existing list:

  1. GET the record first to retrieve the current list of attachment objects.
  2. Append your new { "url": "..." } object to that list.
  3. PATCH the entire updated list back to Airtable.

Since Airtable requires a URL, you cannot pass a path like C:/images/photo.jpg. If your images are local, your workflow must include an interim step:

  1. Upload the local file to a temporary S3 bucket, Google Cloud Storage, or a service like Cloudinary.
  2. Pass the resulting public URL to the Airtable API.
  3. (Optional) Delete the temporary file after Airtable has finished processing.
  • File Size: Airtable has a limit of 5GB per attachment for Enterprise plans, but for API uploads, keep individual files under 20MB to prevent timeout issues during the fetch phase.
  • Asynchronous Fetching: When your script receives a 200 OK, the image may not appear in the UI immediately. Airtable is still downloading the file in the background.
  • Public Accessibility: The URL provided must be accessible without authentication or headers. If your source requires a login, Airtable’s crawler will return a 403 error and the cell will remain empty.
  • Rate Limits: The Airtable API is limited to 5 requests per second per base. Use time.sleep(0.2) if you are looping through hundreds of image uploads.