Skip to main content

Field names

Requirements

npm i airtable @airwalker/airtable

Using airtable.js

Here's an example of us creating a record in a base with a Name field, can you spot the error?

Hint: tsc and your editor won't know about it

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",
});
} catch (e) {
console.log(e);
}
})();

Hint: here's the response from Airtable

AirtableError {
error: 'UNKNOWN_FIELD_NAME',
message: 'Unknown field name: "name"',
}

Answer: The example incorrectly uses name (with a lowercase n) and rather than the correct field Name

Using @airwalker/airtable

Let's make a couple of tiny tweaks and see how @airwalker/airtable deals with this.

All that's different is that we:

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

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

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

(async () => {
try {
await table.create({
name: "test",
});
} catch (e) {
console.log(e);
}
})();

Now in our editor or when compiling this via tsc we get the following error:

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; }' 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: { ...; }; }>'.
Overload 2 of 6, '(recordData: string | Partial<{ "Due date"?: string | undefined; "Campaigns (from \uD83D\uDCC8 Campaigns table)"?: string[] | undefined; Creator?: CollaboratorParams | undefined; Headline?: string | undefined; ... 4 more ...; Image?: { ...; }[] | undefined; }>, optionalParameters?: OptionalParameters | undefined): Promise<...>', gave the following error.
Argument of type '{ name: string; }' is not assignable to parameter of type 'string | Partial<{ "Due date"?: string | undefined; "Campaigns (from \uD83D\uDCC8 Campaigns table)"?: string[] | undefined; Creator?: CollaboratorParams | undefined; Headline?: string | undefined; ... 4 more ...; Image?: { ...; }[] | undefined; }>'.
Object literal may only specify known properties, but 'name' does not exist in type 'Partial<{ "Due date"?: string | undefined; "Campaigns (from \uD83D\uDCC8 Campaigns table)"?: string[] | undefined; Creator?: CollaboratorParams | undefined; Headline?: string | undefined; ... 4 more ...; Image?: { ...; }[] | undefined; }>'. Did you mean to write 'Name'?

The part we're interested in is right at the end

...Did you mean to write 'Name'?

Our mistake has been caught by type checking, and we did indeed mean to write Name.

Here's the correction we need to make:

await table.create({
Name: "test",
});

We've resolved the error.

Let's also make this code resilient to field renames. This means that when somebody renames this field in the future this integration will continue to work.

In case you weren't aware, fields in AIrtable ID's which can also be used in the place of field names.

If you were already aware that you can use field ID's you may be getting ready to jump into Airtable to grab the field ID, but we can just import and use it.

tip

For more info about Airtable ID's refer to the Airtable docs

Our code now looks like this:

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

const ccPipelineIDs =
AirtableIDs["Content Calendar"].tables["📚 Content pipeline"].fields;

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

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

(async () => {
try {
await table.create({
[ccPipelineIDs.Name]: "test",
});
} catch (e) {
console.log(e);
}
})();

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",
});
} catch (e) {
console.log(e);
}
})();

@airwalker/airtable

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

const ccPipelineIDs =
AirtableIDs["Content Calendar"].tables["📚 Content pipeline"].fields;

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

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

(async () => {
try {
await table.create({
[ccPipelineIDs.Name]: "test",
});
} catch (e) {
console.log(e);
}
})();
  • Type checking avoids field name typos (Name vs name)
  • Type checking ensures Name is a string
  • ID's introduce resilience to field renames