A modern Go package to access the Airtable API with enhanced features including context support, intelligent retry logic, and improved error handling.
- Context Support: All methods now support
context.Contextfor cancellation and timeouts - Intelligent Retry Logic: Exponential backoff retry with configurable options
- Enhanced Error Handling: Custom error types with detailed error information
- Configurable HTTP Client: Customizable timeout, retry settings, and HTTP client
- Input Validation: Comprehensive parameter validation
- Updated Go Version: Requires Go 1.21+
- Improved Documentation: All comments and documentation in English
go get github.com/Squirrel-Entreprise/airtableAirtable uses simple token-based authentication. To generate or manage your API key, visit your account page.
Initialize client with default options
a := airtable.New("your-api-key", "your-base-id", false)Create client with custom options
options := airtable.ClientOptions{
Timeout: 30 * time.Second,
MaxRetries: 3,
RetryDelay: time.Second,
MaxRetryDelay: 30 * time.Second,
UserAgent: "my-app/1.0",
Debug: true,
}
a := airtable.NewWithOptions("your-api-key", "your-base-id", options)All methods support context for cancellation and timeouts
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var result airtable.AirtableList
err := a.ListWithContext(ctx, params, &result)productsParameters := airtable.Parameters{
Name: "Products", // Name of the table
MaxRecords: "100", // Max records to return
PageSize: "10",
View: "Grid view", // View name
FilterByFormula: fmt.Sprintf(`Name="%s"`, "Apple"), // Filter by formula
Fields: []string{ // Fields to return
"Name",
"Category",
},
Sort: []airtable.Sort{
{
Field: "Category",
Direction: airtable.Descending,
},
},
}
var products airtable.AirtableList
if err := a.List(productsParameters, &products); err != nil {
fmt.Println(err)
}
for _, p := range products.Records {
fmt.Println(p.ID, p.Fields["Name"], p.Fields["Category"])
}If you make a request that needs to return more records than fits in a page (determined by Parameters.PageSize), the response AirtableList's Offset field will be non-empty.
For the small 5-record table below:
| Name |
| ----- |
| R-001 |
| R-002 |
| R-003 |
| R-004 |
| R-005 |
Getting a list with a page-size of 2:
var (
params = airtable.Parameters{
Name: "Products",
PageSize: "2",
Fields: []string{"Name"},
}
resList airtable.AirtableList
)
a.List(params, &resList)resList will have its Offset field set, in addition to its Records field:
fmt.Println("offset:", resList.Offset)
for _, p := range resList.Records {
fmt.Println(" name:", p.Fields["Name"])
}
// offset: itrUBoYIqbyPWClt7/rec15x8Y8iIFy0zLD
// name: R-001
// name: R-002To get the next two products (R-003, R-004), add that offset into params before making the List call again:
params.Offset = resList.Offset
resList.Offset = ""
a.List(params, &resList)Printing resList like before:
// offset: itr9dgLqRfeSslQ2G/rec1YiWuByyHHe2cM
// name: R-003
// name: R-004Repeat to get the last product:
params.Offset = resList.Offset
resList.Offset = ""
a.List(params, &resList)
// offset:
// name: R-005Note that offset is blank in the printout. When the Airtable API has sent all the records it can, there will be no more pages, it omits the "offset" key in the JSON. We need to be careful to always clear/reset resList.Offset to an empty string before making the call to List. If we don't manually clear resList.Offset, it will keep the old offset and we won't get the signal from the API that there will be no more pages.
To make getting all records regardless of record count and page size, this client provides a ListPager. Call your pagers Next method to get records back, at most page-size records at a time. When all pages have been listed (no more records), Next returns an ErrEOL ("end of list").
Fetching the same five products two-at-a-time, like above, now looks like:
pgr := NewListPager(a, params)
for {
products, err := pgr.Next()
if err != nil {
if err == ErrEOL {
break
}
log.Fatal(err)
}
fmt.Println("offset:", pgr.Offset())
for _, p := range products {
fmt.Println(" name:", p.Name)
}
}ListPager manages the offset logic, making sure you get all the records you expect with no fuss.
product := airtable.AirtableItem{}
table := airtable.Parameters{Name: "Products"}
if err := a.Get(table, "recj2fwn8nSQhR9Gg", &product); err != nil {
fmt.Println(err)
}
fmt.Println(product.ID, product.Fields["Name"], product.Fields["Category"])type porductPayload struct {
Fields struct {
Name string `json:"Name"`
Category string `json:"Category"`
Price float64 `json:"Price"`
} `json:"fields"`
}
newProduct := porductPayload{}
newProduct.Fields.Name = "Framboise"
newProduct.Fields.Category = "Fruit"
newProduct.Fields.Price = 10.0
payload, err := json.Marshal(newProduct)
if err != nil {
fmt.Println(err)
}
product := airtable.AirtableItem{}
table := airtable.Parameters{Name: "Products"}
if err := a.Create(table, payload, &product); err != nil {
fmt.Println(err)
}
fmt.Println(product.ID, product.Fields["Name"], product.Fields["Price"])type porductPayload struct {
Fields struct {
Price float64 `json:"Price"`
} `json:"fields"`
}
updateProduct := porductPayload{}
updateProduct.Fields.Price = 11.0
payload, err := json.Marshal(updateProduct)
if err != nil {
fmt.Println(err)
}
product := airtable.AirtableItem{}
table := airtable.Parameters{Name: "Products"}
if err := a.Update(table, "recgnmCzr7u3jCB5w", payload, &product); err != nil {
fmt.Println(err)
}
fmt.Println(product.ID, product.Fields["Name"], product.Fields["Price"])table := airtable.Parameters{Name: "Products"}
if err := a.Delete(table, "recgnmCzr7u3jCB5w"); err != nil {
fmt.Println(err)
}