+1 (415) 612-6492

Airtable Attachments at Scale: Expiring URLs, the Upload API, and Storage That Does Not Bite

Attachments are the part of Airtable that quietly breaks things six months after launch. A base that works beautifully with 200 records and a few PDFs starts throwing 404s on image links, a workspace hits its storage ceiling nobody was watching, and a migration script copies 12,000 files into the wrong place. None of that is exotic — it is all the predictable result of two facts most teams learn the hard way.

Fact one: the URLs Airtable gives you for attachment files are temporary. Fact two: attachment storage is billed at the workspace level and counts every copy. This tutorial works through both, with the patterns we use on client builds.

The attachment field, briefly

An attachment field holds an array of objects, not a string. Each object has an id, url, filename, size, type, and (for images) a thumbnails object. When you read a record through the API you get something like:

"Documents": [
  {
    "id": "attXXXXXXXXXXXXXX",
    "url": "https://v5.airtableusercontent.com/v3/u/...",
    "filename": "signed-contract.pdf",
    "size": 184320,
    "type": "application/pdf"
  }
]

That url is the one that will betray you.

Step 1: understand expiring URLs

Since late 2022 Airtable serves attachment content from signed, time-limited URLs. They are valid for a couple of hours from the moment you fetch the record, then they stop resolving. This is a security improvement — previously anyone with a guessed link could read your files forever — but it invalidates a very common pattern:

Pull the attachment URL, paste it into a website, a Notion page, a client proposal, a Google Sheet, or a cached CMS field, and expect it to render next week.

It will not. The symptom is always the same: images that worked yesterday now show broken-image icons, and re-running the sync "fixes" it for another couple of hours.

The rule: treat an attachment URL as a short-lived download ticket, not as an address. If something outside Airtable needs to display the file, that system must download the bytes while the ticket is valid and host its own copy.

Three safe patterns:

  1. Fetch fresh, use immediately. An automation that emails a PDF, or a script that pushes a file to a client portal, should read the record and use the URL in the same run.
  2. Rehost in storage you control. On attachment change, copy the file to S3, Google Cloud Storage, Cloudinary, or your CMS, and write the permanent URL back into a plain text field (Public URL). Everything downstream reads that field.
  3. Let Airtable render it. Inside Interfaces, shared views, and the Airtable UI, attachments render correctly because Airtable mints a fresh URL each time. If the audience can live inside an interface, you avoid the problem entirely — see our tutorial on client portals and external interfaces.

A minimal rehosting automation script, triggered when the attachment field is not empty and Public URL is empty:

let table = base.getTable('Documents');
let record = await input.recordAsync('Record', table);
let files = record.getCellValue('File') || [];
if (files.length) {
    let file = files[0];
    let res = await fetch(file.url);
    let blob = await res.blob();
    // POST blob to your own storage endpoint, which returns a permanent URL
    let upload = await fetch('https://files.example.com/upload', {
        method: 'POST',
        headers: { 'Authorization': 'Bearer ' + mySecret, 'X-Filename': file.filename },
        body: blob
    });
    let { publicUrl } = await upload.json();
    await table.updateRecordAsync(record, { 'Public URL': publicUrl });
}

Keep the fetch and the upload in one run. If you queue the URL for later processing, the ticket expires before the worker wakes up.

Step 2: get files into Airtable properly

There are three ways to put a file in an attachment field, and they behave differently.

By URL. Write [{ "url": "https://example.com/file.pdf", "filename": "file.pdf" }] to the field via the REST API. Airtable downloads the file server-side and stores its own copy. Simple, but the URL must be publicly reachable without authentication — no signed S3 links that expire in 60 seconds, no intranet paths, no Google Drive "anyone with the link" URLs that actually serve an HTML interstitial. If the download fails, you often get a silently empty cell rather than an error.

Upload Attachment endpoint. Airtable offers a dedicated upload endpoint that takes base64-encoded file content and a content type, and attaches it to a specific record and field:

POST https://content.airtable.com/v0/{baseId}/{recordId}/{attachmentFieldIdOrName}/uploadAttachment
{
  "contentType": "application/pdf",
  "file": "<base64 string>",
  "filename": "signed-contract.pdf"
}

This is the right tool when your file is not publicly hosted — private storage, a generated PDF, a scan coming off a device. Note the practical ceiling: the payload is capped (roughly 5 MB of base64 content at time of writing, which is a smaller original file than you think, since base64 adds about a third). For big files, host them somewhere reachable and use the URL method, or store a link rather than a copy.

Forms and drag-and-drop. A form with an attachment question is still the cheapest intake mechanism ever built, and it handles large files without you writing anything. Use it unless you need programmatic control.

Whichever route you take, your token needs the data.records:write scope and access to the base. If tokens are new to you, start with our Airtable API quickstart on PATs and OAuth.

Step 3: stop paying for duplicates

Attachment storage is pooled per workspace and counted per stored copy. Some things that quietly double or triple your usage:

  • Duplicating a base. Every attachment in the copy is a new stored file. Three "sandbox" copies of a 20 GB base is 80 GB.
  • Copy-pasting attachment cells between tables or bases.
  • Automations that re-attach on every run because a condition is not tight enough. We have seen a nightly job attach the same 4 MB report 400 times.
  • Deleted records. Removing a record does not instantly reclaim space; snapshots and trash hold the files for their retention window. See backup and disaster recovery for how that retention interacts with restores.

A storage audit you can run in ten minutes

  1. In the admin or billing area, note the workspace's total attachment usage and the plan allowance.
  2. In each large base, add a formula field next to every attachment field: SUM(ARRAYCOMPACT(...)) is not available for sizes directly, so use a script instead — iterate records, sum size from the cell value, and log per-table totals.
  3. Rank tables by total bytes. In most bases, 80% of storage sits in one or two fields — usually a raw photo upload or a generated PDF archive.
  4. For each offender, decide: keep in Airtable, rehost and store a link, or archive older files out to cheap object storage and clear the field.
  5. Write the decision into your schema documentation so the next developer does not undo it. Our change management and schema documentation tutorial covers where that lives.

A quick sizing script for step 2:

let table = base.getTable('Documents');
let query = await table.selectRecordsAsync({ fields: ['File'] });
let total = 0;
for (let record of query.records) {
    for (let file of record.getCellValue('File') || []) total += file.size;
}
output.text(`Total: ${(total / 1024 / 1024).toFixed(1)} MB across ${query.records.length} records`);

For bases with tens of thousands of records, run this in batches — the same batching discipline described in our scripting guide.

Step 4: migrations and exports

When you migrate files out of Airtable — to another platform, to a client, to an archive — the expiring-URL rule is what determines your script design:

  • Read a page of records, download every attachment in that page immediately, then move to the next page. Do not build a list of 10,000 URLs and then start downloading.
  • Store the Airtable id of each attachment alongside the downloaded file so the run is resumable and you can detect duplicates by identity rather than filename. Filenames collide constantly — scan.pdf will appear 300 times.
  • Preserve type; some downstream systems refuse files whose extension and MIME type disagree.
  • Verify byte counts against the size field before declaring a migration complete.

The same principles apply inbound, when you are moving a document library into Airtable. If you are planning that kind of move, our data migration to Airtable service page describes how we scope it.

Step 5: decide what belongs in Airtable at all

The honest architectural answer is that Airtable is an excellent database and an average file store. Attachments are perfect for small, record-scoped artefacts: a receipt, a headshot, a signed order form, a screenshot attached to a bug. They are a poor fit for video libraries, design source files, large media archives, or anything a public website needs to serve.

For those, keep the file in object storage and keep the metadata in Airtable: a permanent URL, a checksum, a size, an owner, a retention date. You get search, permissions, and reporting from Airtable, and you get cheap, durable, addressable storage from the storage system. Handling personal data in those files brings its own obligations — see our notes on Airtable security and compliance.

Checklist

  • No attachment URL is stored anywhere outside Airtable as a permanent address.
  • Every external consumer either renders inside Airtable or reads a rehosted Public URL.
  • Programmatic uploads use the Upload Attachment endpoint for private files and the URL method for public ones, with failures logged rather than silently swallowed.
  • Workspace storage usage is measured, attributed to specific fields, and reviewed quarterly.
  • Base duplication policy accounts for the storage it creates.
  • Migration scripts download within the URL lifetime and are resumable.

If your base is already over its storage allowance, or a downstream integration is showing broken images it used to render fine, that is a solvable afternoon rather than a rebuild. BaseBrainers untangles attachment architecture as part of our Airtable API integration work — get in touch with the symptoms and we will tell you which of the patterns above applies.