Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Airtable Laravel Package

tapp/laravel-airtable

A Laravel package for integrating Airtable into your app. Query tables, create and update records, and map Airtable data to Eloquent-style models with a simple, fluent API for syncing and managing data.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and configure (under 5 minutes):

composer require tapp/laravel-airtable
php artisan vendor:publish --tag=airtable   # if the tag differs, check the provider

Add credentials to .env:

AIRTABLE_KEY=pat_xxxxxxxxxxxxxxxxxxxxx   # personal access token, not legacy apiKey
AIRTABLE_BASE=appXXXXXXXXXXXXXX          # the base ID from the Airtable URL

The service provider and Airtable facade are auto-discovered. First sanity check:

use Tapp\Airtable\Facades\Airtable;

$records = Airtable::table('Leads')->get();   // Collection of rows
$one     = Airtable::table('Leads')->find('recXXXXXXXXXXXXXX');

Where to look first:

  1. config/airtable.php — confirm key, base, and any api_version/timeout keys match what you put in .env.
  2. src/ — the package is small; AirtableManager (or similarly named class) is the real entry point. Read it before trusting IDE autocompletion, because the public surface is thin and undocumented methods rarely exist.
  3. The README — it is the primary spec. There is no API docs site and only ~112 stars, so treat the source as authoritative.

First real use case: pulling a reference table (e.g. "Pricing Tiers" or "Countries") into a read-only cache on boot, and pushing form submissions into a "Leads" table. Both are single-table CRUD, which is exactly what this wrapper is built for. If your first task involves complex filterByFormula, linked-record traversal, or attachments, validate early that the wrapper exposes what you need — you may end up falling back to raw HTTP.

Implementation Patterns

Repository / service layer, not controllers. Wrap the facade so table names and field mappings live in one place:

class LeadRepository
{
    public function all(): Collection
    {
        return Airtable::table('Leads')->get();
    }

    public function create(array $data): Collection
    {
        return Airtable::table('Leads')->create([
            'Name'   => $data['name'],
            'Email'  => $data['email'],
            'Status' => 'New',
        ]);
    }
}

CRUD round-trips. The typical set is get() / find($id) for reads and create() / update($id, $data) / delete($id) for writes. Record IDs are the Airtable rec… strings — store them in your own database as a mapping column so you can round-trip later.

Filtering. Use the wrapper's where/filter helpers rather than pulling all rows and filtering in PHP (Airtable caps page size at 100 records by default, so client-side filtering is both slow and wrong):

$active = Airtable::table('Leads')->where('Status', 'Active')->get();

For anything the helper cannot express, drop to a filterByFormula string and remember Airtable uses {Field Name} syntax with the braces:

Airtable::table('Leads')->where('Status', 'Active')->get();
// or, if exposed:
Airtable::table('Leads')->filterByFormula("{Status} = 'Active'")->get();

Pagination. Distinguish "one page" from "everything." get() is typically a single page (100 rows); loop with offset/limit or explicitly call the fetch-all path. For large tables, write a queued job that pages through and upserts into your own DB — do not hydrate thousands of Airtable rows in a web request.

Caching read-heavy tables. Airtable has a hard rate limit (~5 requests/second per base), so wrap stable reference data:

$tiers = Cache::remember('airtable.tiers', now()->addHour(), fn () =>
    Airtable::table('Pricing Tiers')->get()
);

Bust the cache on write via a model event or a small observer around your repository.

Queue writes. Any non-interactive write (form submission, webhook handler) should be dispatched as a job with RateLimited middleware or Bus::batch so a burst of traffic does not hit Airtable's 5 rps ceiling and start returning 429s.

Testing. Use Laravel's Http::fake() to stub Airtable's api.airtable.com responses, or bind a fake into the container that returns fixture Collections. Do not hit the live base in the test suite — the rate limit will bite you in CI.

Integration into Eloquent workflows. A common pattern is a local leads table as the source of truth plus an airtable_id column, and a queued listener that mirrors changes. This gives you query flexibility and Airtable as a reporting surface, rather than making Airtable the primary datastore.

Gotchas and Tips

  • Rate limits are the number-one failure. Airtable returns 429 with a Retry-After header around 5 rps per base. The wrapper does not include retry/backoff; add ->retry(3, 1000) on the HTTP client or a RateLimited job middleware. Watch for it especially in tests and seeders that loop over records.
  • Field names are exact and case-sensitive. where('status', 'Active') silently returns an empty collection if the field is Status. This is the most common "the package is broken" report — it usually isn't.
  • get() vs all() semantics. Older versions of the package fetch a single page (max 100) with get() and may or may not auto-page on all(). Read the source or the README before assuming you receive every row; a truncation bug at 100 records is easy to ship and hard to notice.
  • Formulas and escaping. Quotes and apostrophes in values (e.g. O'Brien) break filterByFormula unless escaped. Build formulas through the package's helpers where possible, or escape single quotes as \' yourself.
  • Linked records and attachments are not plain strings. Linked-record fields return arrays of record IDs; attachments return an array of objects with url and filename. Do not assume scalars — the wrapper usually returns the raw JSON structure inside the Collection, so map/transform before rendering.
  • update vs replace. Airtable has both PATCH (merge) and PUT (replace) semantics. If the package exposes update() as a PATCH, sending {Status: null} may not clear the field the way you expect; check whether clearing is supported and if you need an explicit empty value.
  • Config quirks. The config historically distinguished api_key (legacy, deprecated) from personal access tokens. Modern Airtable requires PATs with per-base scopes. A 401 almost always means the token lacks the base scope or the base ID is wrong (base IDs start with app). A 404 often means the table name is misspelled rather than the base being unreachable.
  • Error handling. Wrap calls in try/catch around Illuminate\Http\Client\RequestException (or the package's exception if it defines one). Distinguish 4xx (developer error — bad field, bad base) from 5xx/429 (retryable) before scheduling retries.
  • Timeouts and large payloads. Attachments and wide tables can be slow; set an explicit timeout in the config's HTTP options rather than trusting the default, and cap limit/fields to only the columns you need to speed up responses.
  • Extension points. Because the package is thin, the two places to hook in are (a) the config's HTTP client options, for retries and timeouts, and (b) a facade binding or Airtable::macro-style extension on the manager class for reusable filters. You can also bind your own repository as a singleton and inject it, keeping the facade out of application code.
  • Package maturity. With ~112 stars, zero dependents, and infrequent releases, treat this as a convenience wrapper, not a supported SDK. Pin the version in composer.json, add integration tests against a sandbox base, and keep a thin adapter class so you can swap to the official Airtable PHP client or raw Http::withToken() calls without touching call sites.
  • When to skip the wrapper. If you need webhooks, batch requests to the metadata API, or the newer POST /v0/{base}/{table}/listRecords paging with offset tokens, verify support first — a 20-line Http::withToken(config('airtable.key')) call is often clearer than fighting an abstraction that does not expose the primitive.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect