Skip to main content

Field types

Requirements

npm i airtable @airwalker/airtable

Using airtable.js

Here is the relevant fields and their types for this example:

  • Name - Single line text
  • Status - Single select
    • Live
    • In progress
    • On hold
    • Reviews
    • Planning
  • Image - Attachment

Here's an example that creates a record with those fields:

import Airtable from "airtable";

const base = new Airtable({
apiKey: "YOUR_SECRET_API_TOKEN",
}).base("appXXXXXXXXXXXXXX");

const table = base.table("tblXXXXXXXXXXXXXX");

(async () => {
try {
await table.create({
Name: "test",
Status: 'Preparing',
Image: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
});
} catch (e) {
console.log(e);
}
})();

There are errors waiting to be discovered. Can you spot any?

Here's the response we get from Airtable when we run this:

AirtableError {
error: 'INVALID_ATTACHMENT_OBJECT',
message: 'Invalid attachment for field Image: parameters must be objects, not strings',
statusCode: 422
}

It seems we've made a mistake with our Image field.

When uploading an image we need to pass a value that matches the shape {url: string}[].

The updated code looks like this:

import Airtable from "airtable";

const base = new Airtable({
apiKey: "YOUR_SECRET_API_TOKEN",
}).base("appXXXXXXXXXXXXXX");

const table = base.table("tblXXXXXXXXXXXXXX");

(async () => {
try {
await table.create({
Name: "test",
Status: "Preparing",
Image: [
{
url: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
},
],
});
} catch (e) {
console.log(e);
}
})();

With this change you'll see tsc or your editor displaying an error. Let's move on despite the error, we'll see that it isn't accurate.

No overload matches this call.
Overload 1 of 6, '(recordsData: CreateRecords<FieldSet>, optionalParameters?: OptionalParameters | undefined): Promise<Records<FieldSet>>', gave the following error.
Argument of type '{ Name: string; Status: string; Image: { url: string; }[]; }' is not assignable to parameter of type 'CreateRecords<FieldSet>'.
Object literal may only specify known properties, and 'Name' does not exist in type 'CreateRecords<FieldSet>'.
Overload 2 of 6, '(recordData: string | Partial<FieldSet>, optionalParameters?: OptionalParameters | undefined): Promise<Record<FieldSet>>', gave the following error.
Type '{ url: string; }' is missing the following properties from type 'Attachment': id, filename, size, type

Ignoring the error and running the code anyway we get a different response:

AirtableError {
error: 'INVALID_MULTIPLE_CHOICE_OPTIONS',
message: 'Insufficient permissions to create new select option ""Preparing""',
statusCode: 422
}

The error in the response is no longer about the Image field, despite what the airtable.js types might lead us to believe.

Let's address the Status field error. Based on the error we've discovered Status as a select field, and we've provided an invalid option. Having checked our options we decide that we probably wanted to use the Planning option.

Here's how the working code looks:

import Airtable from "airtable";

const base = new Airtable({
apiKey:
"YOUR_SECRET_API_TOKEN",
}).base("appXXXXXXXXXXXXXX");
const table = base.table("tblXXXXXXXXXXXXXX");
(async () => {
try {
await table.create({
Name: "test",
Status: "Planning",
Image: [
{
url: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
},
],
});
} catch (e) {
console.log(e);
}
})();

Running that code results successfully creates our record, that is despite the airtable.js types still incorrectly warning us that Image is incorrect.

We can work around the error by asserting the value of Image as any.

await table.create({
Name: "test",
Status: "Planning",
Image: [
{
url: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
},
] as any,
});

While it gets rid of the error this is not a good idea, consider this example which doesn't error:

await table.create({
Name: "test",
Status: "Planning",
Image: [
{
beepboop: 11093910313,
},
] as any,
});

A solution is to change the assertion to being as {url: string}[] and then as any:

await table.create({
Name: "test",
Status: "Planning",
Image: [
{
url: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
},
] as { url: string }[] as any,
});

Using @airwalker/airtable

Let's do this again with @airwalker/airtable.

import Airwalker from "@airwalker/airtable";
import { ContentCalendar } from "./airwalker-generated-types";

const base = new Airwalker({
apiKey: "YOUR_SECRET_API_TOKEN",
}).base("appXXXXXXXXXXXXXX");

const table =
base.table<ContentCalendar["📚 Content pipeline"]>("tblXXXXXXXXXXXXXX");

(async () => {
try {
await table.create({
Name: "test",
Status: "Preparing",
Image: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
});
} catch (e) {
console.log(e);
}
})();

Here's an error explaining that no overload matches the call

No overload matches this call.
Overload 1 of 6, '(recordsData: CreateRecords<{ Read: { "Due date"?: string | undefined; "Campaigns (from \uD83D\uDCC8 Campaigns table)"?: string[] | undefined; Creator?: Collaborator | undefined; Headline?: string | undefined; ... 4 more ...; Image?: Attachment[] | undefined; }; Write: { ...; }; }>, optionalParameters?: OptionalParameters | undefined): Promise<...>', gave the following error.
Argument of type '{ Name: string; Status: string; Image: string; }' is not assignable to parameter of type 'CreateRecords<{ Read: { "Due date"?: string | undefined; "Campaigns (from \uD83D\uDCC8 Campaigns table)"?: string[] | undefined; Creator?: Collaborator | undefined; Headline?: string | undefined; ... 4 more ...; Image?: Attachment[] | undefined; }; Write: { ...; }; }>'.
Object literal may only specify known properties, and 'Name' does not exist in type 'CreateRecords<{ Read: { "Due date"?: string | undefined; "Campaigns (from \uD83D\uDCC8 Campaigns table)"?: string[] | undefined; Creator?: Collaborator | undefined; Headline?: string | undefined; ... 4 more ...; Image?: Attachment[] | undefined; }; Write: { ...; }; }>'.

Let's comment the fields out, we'll add them back one by one and resolve any errors that appear.

With the Name field only, there are no errors:

await table.create({
Name: "test",
// Status: "Preparing",
// Image: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
});

Let's add Status back:

await table.create({
Name: "test",
Status: "Preparing",
// Image: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
});

Here's the error for the relevant overload:

No overload matches this call.
...
Overload 2 of 6, '(recordData: string | Partial<{ "Due date"?: string | undefined; Status?: "selxOmkoJe4j0sXOU" | "selVhw0N1YQOLlzPO" | "selogbVjjHetPIPLx" | "selHZWkeKziqIW7rB" | "selfoqE6oHhBl4naf" | "Live" | ... 4 more ... | undefined; ... 6 more ...; Headline?: string | undefined; }>, optionalParameters?: OptionalParameters | undefined): Promise<...>', gave the following error.
Type '"Preparing"' is not assignable to type '"selxOmkoJe4j0sXOU" | "selVhw0N1YQOLlzPO" | "selogbVjjHetPIPLx" | "selHZWkeKziqIW7rB" | "selfoqE6oHhBl4naf" | "Live" | "In progress" | "On hold" | "Reviews" | "Planning" | undefined'.

We now know Planning is not a valid option for Status. We can also see the allowed option names and IDs.

By importing and using the ID's from our autogenerated types we can get the both of both worlds, the stability of an ID and the readability of a name.

Here's the update to Status, there are no errors:

const contentPipelineOptions =
AirtableOptions["Content Calendar"]["📚 Content pipeline"];

await table.create({
Name: "test",
Status: contentPipelineOptions.Status.Planning,
Image: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
});

Let's add Image back:

const contentPipelineOptions =
AirtableOptions["Content Calendar"]["📚 Content pipeline"];

await table.create({
Name: "test",
Status: contentPipelineOptions.Status.Planning,
Image: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
});

Here's the error for the relevant overload:

No overload matches this call.
...
Overload 2 of 6, '(recordData: string | Partial<{ "Due date"?: string | undefined; Status?: "selxOmkoJe4j0sXOU" | "selVhw0N1YQOLlzPO" | "selogbVjjHetPIPLx" | "selHZWkeKziqIW7rB" | "selfoqE6oHhBl4naf" | "Live" | ... 4 more ... | undefined; ... 6 more ...; Headline?: string | undefined; }>, optionalParameters?: OptionalParameters | undefined): Promise<...>', gave the following error.
Type 'string' is not assignable to type '{ url: string; }[]'.

We've found that Image needs to be a { url: string; }[]

Here's the update to Image:

await table.create({
Name: "test",
Status: contentPipelineOptions.Status.Planning,
Image: [
{
url: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
},
],
});

We've resolved all the errors.

Summary

airtable.js

import Airtable from "airtable";

const base = new Airtable({
apiKey: "YOUR_SECRET_API_TOKEN",
}).base("appXXXXXXXXXXXXXX");

const table = base.table("tblXXXXXXXXXXXXXX");

(async () => {
try {
await table.create({
Name: "test",
Status: "Planning",
Image: [
{
url: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
},
] as { url: string }[] as any,
});
} catch (e) {
console.log(e);
}
})();

@airwalker/airtable

import Airwalker from "@airwalker/airtable";
import { ContentCalendar } from "./airwalker-generated-types";

const base = new Airwalker({
apiKey: "YOUR_SECRET_API_TOKEN",
}).base("appXXXXXXXXXXXXXX");

const table =
base.table<ContentCalendar["📚 Content pipeline"]>("tblXXXXXXXXXXXXXX");

(async () => {
try {
await table.create({
Name: "test",
Status: contentPipelineOptions.Status.Planning,
Image: [
{
url: "https://www.airtable.com/images/newsroom/newsroom_image-1_1x.png",
},
],
});
} catch (e) {
console.log(e);
}
})();
  • Type checking validates the value of Status against allowed options
  • ID's introduce resilience to Status option renames
  • Type checking validates Attachment object signature
  • Type checking doesn't incorrectly report valid Attachments signature as an error