{"id":36372544,"url":"https://github.com/eugenezadorin/airtable-php","last_synced_at":"2026-01-11T14:02:09.711Z","repository":{"id":40577595,"uuid":"350026070","full_name":"eugenezadorin/airtable-php","owner":"eugenezadorin","description":"Simple PHP client for Airtable API","archived":false,"fork":false,"pushed_at":"2024-05-24T11:06:56.000Z","size":106,"stargazers_count":27,"open_issues_count":4,"forks_count":7,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-11-27T16:48:52.672Z","etag":null,"topics":["airtable","api-client","php"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eugenezadorin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-21T14:41:32.000Z","updated_at":"2025-09-02T18:59:03.000Z","dependencies_parsed_at":"2024-06-21T15:45:08.938Z","dependency_job_id":"693364ca-a9b9-47f0-8526-53b732ffb986","html_url":"https://github.com/eugenezadorin/airtable-php","commit_stats":{"total_commits":42,"total_committers":3,"mean_commits":14.0,"dds":0.4285714285714286,"last_synced_commit":"be4935ae788a041e966cd25688d68ebc7b23fbb9"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/eugenezadorin/airtable-php","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eugenezadorin%2Fairtable-php","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eugenezadorin%2Fairtable-php/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eugenezadorin%2Fairtable-php/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eugenezadorin%2Fairtable-php/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eugenezadorin","download_url":"https://codeload.github.com/eugenezadorin/airtable-php/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eugenezadorin%2Fairtable-php/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28306985,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T11:18:18.743Z","status":"ssl_error","status_checked_at":"2026-01-11T11:07:56.842Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["airtable","api-client","php"],"created_at":"2026-01-11T14:02:09.641Z","updated_at":"2026-01-11T14:02:09.703Z","avatar_url":"https://github.com/eugenezadorin.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Airtable PHP client\n\n## Installation\n\n    composer require zadorin/airtable-php\n\nPlease note that library requires PHP 8.2 since `v1.0.0` release.\n\nIf you have lower PHP version, use `v0.*.*` releases:\n\n    composer require zadorin/airtable-php:^0\n\n## Usage\n\n### Basic setup\n\n```php\n$apiKey = 'key***********';\n$database = 'app***********';\n$tableName = 'my-table';\n\n$client = new \\Zadorin\\Airtable\\Client($apiKey, $database);\n```\n\nYou can find API key in your [account settings](https://airtable.com/account) and database name in [API Docs](https://airtable.com/api).\n\n### Insert some rows\n\n```php\n$client-\u003etable($tableName)\n    -\u003einsert([\n        ['name' =\u003e 'Ivan', 'email' =\u003e 'ivan@test.tld'],\n        ['name' =\u003e 'Peter', 'email' =\u003e 'peter@test.tld']\n    ])\n    -\u003eexecute();\n```\n\n### Fetch multiple rows\n\n```php\n$recordset = $client-\u003etable($tableName)\n    -\u003eselect('id', 'name', 'email') // you can use shortcut select('*') to fetch all columns\n    -\u003ewhere(['name' =\u003e 'Ivan', 'email' =\u003e 'ivan@test.tld'])\n    -\u003eorderBy(['id' =\u003e 'desc'])\n    -\u003elimit(10)\n    -\u003eexecute();\n\nvar_dump($recordset-\u003efetchAll()); // returns set of Record objects\nvar_dump($recordset-\u003easArray()); // returns array of arrays\n```\n\n### Fetch specific rows by record id\n\n```php\n$recordset = $client-\u003etable($tableName)\n    -\u003efind('rec1*******', 'rec2*******')\n    -\u003eexecute();\n```\n\n### Iterate and update records\n\n```php\nwhile ($record = $recordset-\u003efetch()) {\n    var_dump($record-\u003egetId()); // rec**********\n    var_dump($record-\u003egetFields()); // [id =\u003e 1, name =\u003e Ivan, email =\u003e ivan@test.tld]\n\n    $record-\u003esetFields(['name' =\u003e 'Ivan the 1st']);\n    $client-\u003etable($tableName)-\u003eupdate($record);\n}\n```\n\n### Pagination\n\n```php\n$query = $client-\u003etable($tableName)\n    -\u003eselect('*')\n    -\u003eorderBy(['id' =\u003e 'desc'])\n    -\u003epaginate(50); // limit(50) works the same. Default (and maximal) page size is 100\n\nwhile ($recordset = $query-\u003enextPage()) {\n    var_dump($recordset-\u003efetchAll());\n}\n```\n\n### Remove rows\n\n```php\n$records = $client-\u003etable($tableName)\n    -\u003eselect('id', 'email')\n    -\u003ewhere(['email' =\u003e 'peter@test.tld'])\n    -\u003eexecute()\n    -\u003efetchAll();\n\n$client-\u003edelete(...$records)-\u003eexecute();\n```\n\n## Complex filters\n\nYou can build complex formulas to filter records, but be careful, because formula applies to each record and can slow down your query.\n\nAssume we prepared following query object:\n\n```php\n$query = $client-\u003etable('my-table')-\u003eselect('*');\n```\n\n### Query builder\n\nThe following lines give the same results:\n\n```php\n$query-\u003ewhere(['email' =\u003e 'ivan@test.tld']);\n$query-\u003ewhere('email', 'ivan@test.tld');\n$query-\u003ewhere('email', '=', 'ivan@test.tld');\n```\n\nYou can use different logical operators:\n\n```php\n$query-\u003ewhere('email', '!=', 'ivan@test.tld');\n$query-\u003ewhere('code', '\u003e', 100);\n```\n\nIt's possible to concat multiple where statements:\n\n```php\n$query-\u003ewhere([\n    ['code', '\u003e', 100],\n    ['code', '\u003c', 200],\n]);\n```\n\nOr chain methods to achieve the same result:\n\n```php\n$query-\u003ewhere('code', '\u003e', 100)-\u003eandWhere('code', '\u003c', 200);\n```\n\n### OR-logic\n\n```php\n$query-\u003ewhere('name', 'Ivan')-\u003eorWhere('id', 5);\n```\n\nMethods `where()`, `andWhere()`, `orWhere()` use the same signature, so you can combine them:\n\n```php\n$query-\u003ewhere('code', '\u003e', 100)\n    -\u003eandWhere('code', '\u003c', 500)\n    -\u003eorWhere([\n        ['code', '\u003c', 100],\n        ['id', '=', 5]\n    ]);\n```\n\n### Regex filtering\n\nBesides logical operators, you can use keywords `like` and `match` in your where-statements.\n\nKeyword `match` allows you to apply `REGEXP_MATCH()` function to your filter formula. \nAirtable's REGEX functions are implemented using the [RE2 regular expression library](https://github.com/google/re2/wiki/Syntax),\nso be sure that syntax of your regular expression is correct:\n\n```php\n// look for emails, matching @gmail.com in case-insensitive way\n$query-\u003ewhere('email', 'match', '(?i)^(.+)@gmail.com$');\n```\n\nKeyword `like` also uses `REGEXP_MATCH()` under the hood, but provides more SQL-like syntax:\n\n```php\n// look for emails, which ends with @gmail.com\n$query-\u003ewhere('email', 'like', '%@gmail.com');\n\n// look for names, which starts with Ivan\n$query-\u003ewhere('name', 'like', 'Ivan%');\n\n// look for urls, which contains substring (both variants below works the same):\n$query-\u003ewhere('url', 'like', '%github%');\n$query-\u003ewhere('url', 'like', 'github');\n```\n\nPlease note, that `like` is case-sensitive, so if you want to ignore case, you'd better use `match` with `i`-flag.\n\n### Date filtering\n\nLibrary provides few methods to filter records by date and time:\n\n```php\n$query-\u003ewhereDate('birthdate', new \\DateTimeImmutable('2022-03-08'));\n$query-\u003ewhereDateTime('meeting_start', '2022-04-01 11:00:00');\n```\n\nFirst parameter is your column name.\n\nYou can pass `DateTimeImmutable` object or datetime string, which will be cast into `DateTimeImmutable` automatically.\n\nYou can filter records by date range instead of strict equality:\n\n```php\n$query\n    -\u003ewhereDate('birthdate', '\u003e=', new \\DateTimeImmutable('2022-03-01'))\n    -\u003eandWhereDate('birthdate', '\u003c', new \\DateTimeImmutable('2022-04-01')); \n```\n\nThere are shortcuts for that purpose:\n\n```php\n$query-\u003ewhereDateBetween('birthdate', '2022-03-01', '2022-03-31'); // left and right borders included!\n$query-\u003ewhereDateTimeBetween('meeting_start', '2022-04-01 11:00:00', '2022-04-01 15:00:00');\n```\n\nWhen searching by date (not datetime), library applies range filter under the hood.\nFor example, `$query-\u003ewhereDate('meeting', '2022-03-08')` will actually search records between `2022-03-08 00:00:00` and `2022-03-08 23:59:59`, \nincluding left and right borders.\n\nPlease note that the library does not perform any timezone conversions, so most reliable solution is to specify GMT timezone in your `DateTimeImmutable` objects, \nand set flag `Use the same time zone (GMT) for all collaborators` in your datetime column settings.\n\n### Raw formula\n\nYou can see what exact formula was built:\n\n```php\n$query-\u003ewhere([\n    ['Code', '\u003e', 100],\n    ['Code', '\u003c', 300]\n])\n-\u003eorWhere('Name', 'Qux');\n    \n$query-\u003egetFormula(); // OR(AND({Code}\u003e'100', {Code}\u003c'300'), {Name}='Qux')\n```\n\nAlso, you can filter records by raw formula:\n\n```php\n$query-\u003ewhereRaw(\"OR( AND({Code}\u003e'100', {Code}\u003c'300'), {Name}='Qux' )\");\n```\n\nAll query builder methods are used to make raw formula under the hood.\nIt means that if the functionality of query builder is not enough, you can always use raw formula instead.\n\nNote that library don't validate raw formulas so you can get exception from Airtable API.\n\n### View\n\nSometimes it is more convenient to create a specific table view with predefined sorting and filters, \ninstead of building a complex query in the source code.\n\nAssuming you have `tasks` table and `active tasks` view containing only active tasks ordered by priority:\n\n```php\n$records = $client-\u003etable('tasks')\n    -\u003eselect('*')\n    -\u003ewhereView('active tasks')\n    -\u003eexecute();\n```\n\nYou can combine view and additional filters, specify subset of selected fields and override order just like normal select query:\n\n```php\n$records = $client-\u003etable('tasks')\n    -\u003eselect('Name', 'Priority')\n    -\u003ewhereView('active tasks')\n    -\u003eandWhere('Status', 'todo')\n    -\u003eorderBy(['Id' =\u003e 'desc'])\n    -\u003eexecute();\n```\n\nYou can use alias `andWhereView()` but method `orWhereView()` will throw `LogicError`. \nThis is because view is not actually part of the filter formula, it always works like \"view AND formula\", \nso you can't use `OR` operator here.\n\nAlso note that if view not exists `RequestError` exception will be thrown.\n\n### Macros\n\nYou can extend query builder methods with your own using macros:\n\n```php\n\\Zadorin\\Airtable\\Client::macro('whereCanDriveCar', function() {\n    $this-\u003ewhere('age', '\u003e=', 21);\n});\n\n$query-\u003ewhere('state', 'Florida')-\u003eandWhereCanDriveCar();\n```\n\nMacro name must not start with `or`/`and`. These logic prefixes are reserved and handles automatically.\n\nContext `$this` inside macro callback references to query builder instance. It allows you to use other query builder methods or even other macros:\n\n```php\nClient::macro('whereStateIsFlorida', function () {\n    $this-\u003ewhere('state', 'Florida');\n});\n\nClient::macro('canDriveCar', function() {\n    $this-\u003ewhere('age', '\u003e=', 21);\n});\n\nClient::macro('whereFloridaDriver', function() {\n    $this-\u003ewhereStateIsFlorida()-\u003eandCanDriveCar();\n});\n```\n\nYou can pass variables into macro callback:\n\n```php\nClient::macro('whereName', function ($name) {\n    $this-\u003ewhere('Name', '=', $name);\n});\n\n$query-\u003ewhereName('Ivan')-\u003eorWhereName('John');\n```\n\nAnd of course you can use raw formula to build something more complex:\n\n```php\nClient::macro('whereBornInMay', function($year) {\n    $this-\u003ewhereRaw(\"AND(IS_AFTER(birthdate, '$year-04-30 23:59:59'), IS_BEFORE(birthdate, '$year-06-01 00:00:00'))\");\n});\n```\n\nBut remember that raw formula overrides other query builder setup.\n\n## Typecast\n\nAirtable supports linked fields, which references other rows from current or another table. \nAssume you have `users` table where `contacts` field is a link to row in another table.\n\nBy default, you have to specify concrete row ID while inserting or updating such fields:\n\n```php\n$client\n    -\u003etable('users')\n    -\u003einsert(['name' =\u003e 'Ivan', 'contacts' =\u003e 'recSPVbdx5vXwyLoH'])\n    -\u003eexecute();\n```\n\nIt's not very handy, so Airtable API supports `typecast` parameter, which enables automatic data conversion from string values.\n\nAutomatic conversion is disabled by default to ensure data integrity, but sometimes it may be helpful.\n\nThis is how you can enable that feature:\n\n```php\n$client\n    -\u003etable('users')\n    -\u003einsert(['name' =\u003e 'Ivan', 'contacts' =\u003e 'ivan@test.tld'])\n    -\u003etypecast(true) // true is default value and can be skipped\n    -\u003eexecute();\n```\n\nUpdate queries works the same.\n\n## Throttling\n\nAirtable API is limited to 5 requests per second per base. Client uses simple throttling library to keep this limit.\n\nYou can disable this behavior:\n\n```php\n$client = new \\Zadorin\\Airtable\\Client($apiKey, $database);\n$client-\u003ethrottling(false);\n```\n\n## Debug\n\nClient keeps last request object so you can use this for debugging purposes.\n\n**Be careful with debug information because it contains all HTTP headers including authorization token**\n\n```php\n$recordset = $client-\u003etable($tableName)-\u003eselect('*')-\u003eexecute();\n$request = $client-\u003egetLastRequest();\n\n$request-\u003egetResponseCode(); // http code (int)\n$request-\u003egetPlainResponse(); // response body (string)\n$request-\u003egetResponseInfo(); // array provided by curl_getinfo()\n```\n\n## Exceptions\n\nAll package exceptions inherits from common `Zadorin\\Airtable\\Errors\\AirtableError` class.\n\nAlso you may be interested in `Zadorin\\Airtable\\Errors\\RequestError` which contains last request instance:\n\n```php\ntry {\n    $inserted = $client-\u003etable($tableName)-\u003einsert()-\u003eexecute();\n} catch (RequestError $e) {\n    \n    // catch Airtable responses here\n    var_dump($e-\u003egetMessage());\n    var_dump($e-\u003egetLastRequest()-\u003egetResponseInfo());\n\n} catch (AirtableError $e) {\n\n    // catch package errors. In that case it will be \"No records specified for insert\"\n\n}\n```\n\n## Known problems\n\nClient uses `ext-curl` to make requests and `ext-json` to encode/decode results. Make sure this php extensions installed and properly configured.\n\nIf you see `SSL certificate problem: unable to get local issuer certificate` you probably have to configure option `curl.cainfo` in your `php.ini`. [Source](https://stackoverflow.com/questions/28858351/php-ssl-certificate-error-unable-to-get-local-issuer-certificate)\n\n## License and contributing\n\nMIT License. Any feedback is highly appreciated — welcome to [issues](https://github.com/eugenezadorin/airtable-php/issues). \n\nIf you want to send pull request make sure all tests are pass.\n\n## Tests\n\nCopy this [readonly test database](https://airtable.com/shrs2bB37sScbDuLX) into your Airtable account, then fill env variables specified in `phpunit.xml.dist`. \n\nAnd finally run test suite:\n\n    ./vendor/bin/pest\n\nIt's also recommended to use static analysis tool to avoid errors:\n\n    ./vendor/bin/psalm\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feugenezadorin%2Fairtable-php","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feugenezadorin%2Fairtable-php","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feugenezadorin%2Fairtable-php/lists"}