Changelog
Source:NEWS.md
airtable2 0.1.2.9000
Breaking changes
air_write()and other upload functions now clear values from fields when the corresponding column containsNAorNULL. Previously, these values were ignored, leaving the existing field value unchanged.air_write(),air_upsert(), andair_sync()now error by default whendatacontains complex list-columns (nested lists or data frames that Airtable cannot store directly). Previously these columns were silently problematic. Setcomplex_fields = "warn"to drop them orcomplex_fields = "json"to serialize them as JSON text.add_fields = "yes"inair_write(),air_upsert(), andair_sync()now infers field types from column class instead of always creatingsingleLineText. Mapping:numeric→number,logical→checkbox,Date→date, complex/JSON columns →multilineText, everything else →singleLineText. This changes which Airtable field type is created when a new column is first written.
New features
air_write()gains acreate_tableargument. WhenTRUE, the table is created in the base if it does not already exist; field types are inferred from the data using the same rules asadd_fields = "yes".air_infer_fields(data)maps R column types to Airtable field specification lists suitable forat_create_table()/at_create_base(). Factors becomesingleSelectfields with choices fromlevels().air_infer_table(data, name)wrapsair_infer_fields()to produce a complete table specification in one call.air_left_join_upload()gainstypecast,complex_fields, andprogressarguments, bringing its API in line withair_upsert().When
add_fields = "yes"creates multiple new fields, a progress bar is shown instead of one message per field.
Bug fixes
-
air_sync()no longer reports unchanged rows as changed (#13). Change detection now reduces each field to a canonical, type-aware representation before hashing, so a local column compares equal to the value Airtable actually stores and returns. Previously the hash relied onformat()happening to agree across representations, which produced spurious updates on every sync for several field types:- dateTime — compared the wrong moment (the hash used the absolute UTC instant, but Airtable round-trips datetimes by wall-clock).
-
checkbox — unchecked boxes read back as
NA(Airtable omits them) and never matched a localFALSE. -
multipleSelects / linked records and other list-columns — were padded by
format()so a list never matched the identical list read back. -
number — integer-vs-double and scientific-notation drift (e.g.
1e+05vs100000). These are covered by live tests exercising every writable field type.
-
air_restore()now correctly handles tables containing multiselect, linked-record, attachment, or other list-type columns. Previouslyair_write()would abort with a “complex columns” error becauseair_dump()stores those columns as plain lists (without theair_*class); they are now re-wrapped before writing.
airtable2 0.0.0.9000
Initial development release — complete rewrite of airtabler using modern R packages.
DBI improvements (Stage 8A)
- Added
dbGetRowsAffected()forAirtableResult— returnsNA_integer_(read-only results satisfy the DBI contract). - Added
dbFetch()dispatch for missingnsodbFetch(res)works without specifyingn. - Extended DBI test coverage:
dbGetInfo,dbWriteTablemodes,dbExistsTableFALSE case,dbListFieldserror path,dbi_parse_statementedge cases, multi-basedbListTables,dbSendQueryformula forwarding. - Added
tests/testthat/test-dbitest.Ras DBItest conformance placeholder. - Added DBI vignette
vignettes/dbi.Rmd(Stage 8C).
Breaking changes
- Normalized function signatures so the piped/main object comes first and the container hierarchy (table → base → workspace) comes last, letting a session default base be used positionally:
-
air_read(table, base_id = NULL, ...)(was base-first) -
air_write(data, table, base_id = NULL, ...),air_upsert(data, table, merge_on, base_id = NULL, ...),air_sync(data, table, key, base_id = NULL, ...) -
air_delete(record_ids, table, base_id = NULL, ...)(now resolves the session default base) -
air_left_join()/air_inner_join()/air_full_join()are now(x, table, base_id = NULL, by = NULL, ...) at_create_base(name, tables, workspace_id = NULL, ...)
-
Bug fixes
- Attachment uploads now work.
at_upload_attachment()(and theair_write_attachments()/air_sync_attachments()/air_demo()paths that call it) previously POSTed toapi.airtable.comand always got a 404, because Airtable serves theuploadAttachmentendpoint from a separate host. Uploads now go tocontent.airtable.com.air_req()gained ahostargument to select the host. - API errors are now informative:
air_perform()surfaces Airtable’s errortypeandmessageplus actionable hints (e.g. a wrong table name / missing permission, an unknown field name, or an invalidfilterByFormula). This also fixes a dead error handler — the old code caught the non-existenthttr2_http_errorcondition class, so Airtable’s explanation was silently dropped and users only saw the bare HTTP status. -
air_api_usage()now counts parallel attachment downloads (which previously bypassed the counter viareq_perform_parallel()), and its print method shows the workspace ID as a clickable link. - Airtable IDs in
air_browse()messages are now clickable cli hyperlinks. -
air_browse()now honors itsbase_id/table_idarguments (they previously leaked intoutils::browseURL()), so browsing a table/view by ID works. -
.onAttachnow warns (once, viapackageStartupMessage()) when bothairtablerandairtable2are loaded and their function names would mask. - Fixed the pkgdown reference index so the site builds (documented the
AirtableDrivertopic; indexedair_req()/air_token()and the package doc).
New features
- Full httr2-based Airtable REST client replacing legacy
airtabler(httr-based) - Type-aware
air_read()/air_write()with S3 list-columns for multiselect, linked records, attachments, collaborators, and barcodes - Diff-based
air_sync()andair_upsert()that exclude computed/read-only fields automatically using the table schema - DBI S4 interface (
AirtableDriver,AirtableConnection) for standard database workflows and RStudio/Positron connection pane integration - Join helpers:
air_left_join(),air_inner_join(),air_full_join() - Parallel attachment downloads/uploads (
air_read_attachments(),air_write_attachments()) - Base metadata management:
air_meta(),air_meta_push(),air_meta_sync() - Schema inspection:
air_schema()returns a tidy tibble of tables/fields/types - Backup/restore:
air_dump()(JSON/CSV + optional attachments) andair_restore() - Type helpers:
air_flatten_multiselect(),air_flatten_links(),air_flatten_attachments(),air_flatten_collaborator(),air_expand_multiselect(),air_expand_collaborator(),air_simplify() - Field/table template builders:
air_field_template(),air_table_template() - Project-level defaults:
air_set_token(),air_set_base()with env var fallback (AIRTABLE_API_KEY,AIRTABLE_BASE_ID) - API call tracking:
air_api_usage(), respects free-tier ~1000 calls/month limit - Situation report:
at_sitrep()for debugging credentials and accessible bases - Browser navigation:
air_browse()to open bases, tables, views, and records - Low-level
at_*wrappers covering all Airtable REST endpoints (records, tables, bases, fields, views) - Convenience wrappers
air_connect()/air_pane()for DBI connection pane integration -
air_delete()high-level record deletion with messaging - The default delimiter for flattening/expanding multiselect, links, and attachment fields is now
"; "(was", "), overridable with theairtable2.delimiteroption orAIRTABLE2_DELIMITERenv var; expansion tolerates spaces around the separator so flatten/expand round-trips -
air_browse()accepts base/table NAMES (not just IDs), opens the session default base when called with no argument, and resolvesapp/wsp/tbl/viwprefixes -
air_attachment_preview_url()plus documentation of Airtable attachment URL behavior (the APIurlexpires ~2h; stable viewer links are obtained in the web app) -
air_flatten()is now an S3 generic dispatching on theair_multiselect,air_links,air_attachments, andair_collaboratorcolumn classes (the per-typeair_flatten_*()functions remain as exported wrappers) - Write/upsert/sync now auto-expand flat character columns to the structure Airtable expects, inferred from the table schema (e.g. a
"; "-joined string into a multiselect or linked-record array, an email into a collaborator object) — manualair_expand_*()is no longer required -
air_left_join_upload()(wasair_upload_join()) joins a local data frame to matched Airtable records and upserts only new or changed field values; reads the remote table minimally (key + existing target fields) and skips unmatched rows without creating records — the upload complement toair_left_join() -
air_meta_sync()now accepts a local data frame, a CSV/JSON file path, or a table name (defaults to"_metadata") and performs a pull-then-patch: it fetches the current remote metadata, computes a diff, and pushes only changed fields -
air_meta_init()seeds a_metadatatable in a base from a schema data frame or an existing schema object, creating the table and populating it in one call - Added
vignette("dbi", package = "airtable2")— “Using airtable2 with DBI”: covers connecting viaair_connect()/DBI::dbConnect(), exploring connections, reading (full table and formula-filtered queries), writing (append and overwrite/sync), multi-base mode, and DBI limitations