> For the complete documentation index, see [llms.txt](https://help.felt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.felt.com/upload-anything/urls/airtable.md).

# Airtable

Map an Airtable view or base in Felt by pasting its shared link, or sync a private base with Python and the Felt REST API.

Add an Airtable view to a Felt map by pasting its shared link. Felt places the records the same way it does for an uploaded spreadsheet. See [Spreadsheets and geocoding](/upload-anything/files/spreadsheets-and-geocoding.md). For a base that can't have a public link, use the Python script.

## Add an Airtable view to a map

Felt reads only Airtable shared links, not links copied from the address bar.

1. In Airtable, open the view you want to map. Hide the fields and filter out the records you don't want on the map.
2. Click **Share and sync** > **Create link to view**, then click **Copy link**.
3. In the Felt toolbar, click **Upload Anything** ![](https://217108486-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmRfGitkyjOEMvVsEyGWN%2Fuploads%2FnwKMjNT6E1km4CtnHkqf%2F24.svg?alt=media\&token=9e98d9c4-35a3-416a-8802-b4e366c722cb) > **From URL**, and paste the link.
4. Optional: Turn on **Live** to pick up changes on a schedule. See [Refresh data](/upload-anything/refresh-data.md).
5. Click **Add to map**. The layer appears in the legend with the view's name.

A shared base link (**Share** > **Share to web**) also works, but adds only the first view of the table the link opens on. To map several tables, add a view link for each.

Felt can't read a shared link restricted by a password or email domain. Edits you make in Felt don't sync back to Airtable.

## Sync a private Airtable base with Python

{% hint style="success" %}
Available on the Business and Enterprise plans. [Compare plans](https://felt.com/pricing#compare)
{% endhint %}

This script reads a table with the [Airtable Web API](https://airtable.com/developers/web/api/list-records) and uploads it to Felt with [felt-python](https://github.com/felt/felt-python). You need:

* An Airtable [personal access token](https://airtable.com/create/tokens) with the `data.records:read` scope and access to the base, in the `AIRTABLE_TOKEN` environment variable.
* A Felt [API token](/developers/rest-api.md#create-an-api-token) in the `FELT_API_TOKEN` environment variable.
* The base ID (starts with `app`) from the Airtable URL, and the Felt [map ID](/developers/rest-api.md#find-map-layer-and-folder-ids).

Install the libraries with `pip install felt-python requests`, then run:

```python
import csv
import os

import requests
from felt_python import refresh_file_layer, upload_file

AIRTABLE_TOKEN = os.environ["AIRTABLE_TOKEN"]

BASE_ID = "appXXXXXXXXXXXXXX"
TABLE = "Sites"         # Table name or ID
VIEW = "Grid view"      # Only this view's records, in its order
MAP_ID = "your-map-id"
LAYER_ID = None         # After the first run, set this to the new layer's ID


def fetch_records():
    """Read every record, following Airtable's pagination"""
    url = f"https://api.airtable.com/v0/{BASE_ID}/{TABLE}"
    headers = {"Authorization": f"Bearer {AIRTABLE_TOKEN}"}
    params = {"view": VIEW, "pageSize": 100}
    records = []
    while True:
        response = requests.get(url, headers=headers, params=params, timeout=60)
        response.raise_for_status()
        data = response.json()
        records += [record["fields"] for record in data["records"]]
        if "offset" not in data:
            return records
        params["offset"] = data["offset"]


def cell_text(value):
    """Flatten multiple selects, collaborators, and attachments to text"""
    if isinstance(value, list):
        return ", ".join(str(cell_text(item)) for item in value)
    if isinstance(value, dict):
        return value.get("name") or value.get("filename") or value.get("url") or ""
    return value


records = fetch_records()
columns = sorted({name for record in records for name in record})

with open("airtable.csv", "w", newline="") as file:
    writer = csv.DictWriter(file, fieldnames=columns)
    writer.writeheader()
    for record in records:
        writer.writerow({name: cell_text(value) for name, value in record.items()})

if LAYER_ID:
    refresh_file_layer(MAP_ID, LAYER_ID, "airtable.csv")
else:
    print(upload_file(MAP_ID, "airtable.csv", "Airtable sites"))
```

The first run adds a layer. Copy its layer ID into `LAYER_ID`, and later runs replace the layer's data and keep its styling. Run the script on a schedule to keep the layer current.

For a view with a public shared link, skip Airtable's API: `upload_url(MAP_ID, "<shared link>", "Airtable sites")` adds it, and `refresh_url_layer(MAP_ID, LAYER_ID)` refreshes it.

## Troubleshooting Airtable links

| Message                                                           | Cause and fix                                                                                                                                                                         |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| *This Airtable link only works for people signed in to the base.* | The link was copied from the address bar. Create a shared link in Airtable and paste that instead.                                                                                    |
| *This Airtable link isn't shared publicly.*                       | The shared link was turned off or replaced. Copy the current shared link from Airtable and add it again.                                                                              |
| *Couldn't access data at remote URL*                              | The shared link needs a password or an email domain. In the link settings in Airtable, remove the restriction, or [use the Python script](#sync-a-private-airtable-base-with-python). |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://help.felt.com/upload-anything/urls/airtable.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
