Pack Building Roadmap - Maker Stories Webinar
Share
Explore

icon picker
Getting Started with Packs

... a Coda pack - not a wolf pack, but these guys are so cute!

Basic Concepts from Coda

What is a Pack?

Coda Packs allow you to extend Coda by building new building blocks that can operate directly on Coda docs’ canvas. You can write these extensions in JavaScript/TypeScript, using them to create functions that let you re-use a formula’s complex logic across documents or even communicate with third-party APIs, with or without user authentication.
Packs are a combination of formulas, sync tables, and column formats.
A formula (including a button) is a JavaScript function that will be exposed as a Coda formula, that you can use anywhere in a Coda doc that you can use any normal formula. Formulas take basic Coda types as input, like strings, numbers, dates, booleans, and arrays of these types, and return any of these types or objects whose properties are any of these types. Buttons are just a flavor of a formula with the flag isAction activated.
A sync table is how to bring structured data from a third-party into Coda. A sync table is a table that you can add to a Coda doc that gets its rows from a third-party data source, that can be refreshed regularly to pull in new or updated data. A sync table is powered by a formula that takes parameters that represent sync options and returns an array of objects representing row data. A sync table also includes a schema describing the structure of the returned objects.
A column format is a custom column type that you apply to any column in any Coda table. A column format tells Coda to interpret the value in a cell by executing a formula using that value, typically looking up data related to that value from a third-party API. For example, the Weather pack has a column format Current Weather; when applied to a column, if you type a city or address into a cell in that column, that location will be used an input to a formula that fetches the current weather at that location, and the resulting object with weather info will be shown in the cell.
Your formulas, sync tables, and column formats are bundled together with metadata describing your pack, into a PackDefinition object which forms the complete specification of your pack.

How do Packs work?

When you build a Pack, you’re ultimately outputting a bundle of compiled code that Coda then runs on your behalf when the Pack is used in a doc. This is different from our API, where we allow you to retrieve/edit data from docs but developers are responsible for running their code.
Here’s how Packs work:
Create a Pack
1. Pack Makers create and build the Pack via the Online Pack Editor or CLI tools.
2. Build sent to Coda’s Backend, either via a command or automatically
Use the Pack in a Doc
3. Doc Makers/Editors install the Pack in their doc
4. They invoke formulas from the doc: When any formulaic element (e.g. formula, sync table, column format, button) is invoked, the request is sent to the Coda backend
5. Coda runs the formula: we retrieve your Pack build and run it for you in the Packs Secure Execution Environment. The formulas are run only with the inputs provided to it; each call to a formula is independent as formulas are state-less. Thus, Packs cannot rely on data that changes and persists between individual calls to a formula.
6. If applicable, external API called by the Execution Environment. All API calls will originate from our servers to protect authentication credentials, which we store on our servers and never send to clients.
7. Return data to doc: the results (or errors) from the execution are sent back to the doc.

image.png

Core Concepts from Coda

Fetching Remote Data

One of the main reason packs are useful is that they can fetch data from a remote server or API and return it Coda. The Coda Packs API permits you to make http requests to third-parties via the Fetcher objects. This is the only way to make http requests in a pack, direct use of a a different http client is not permitted.
All pack formulas including sync formulas have a Context object as their 2nd parameter. This context contains a property called fetcher that is a Fetcher object, exposing a single method, fetch. This method allows you to specify common information for an http request, including the http method, url, headers, and body:
The response object contains the http status code, response body, and response headers, as you’d expect.
The fetcher will apply authentication information to each request before it is sent, if you have configured authentication for your pack. You do not, should not, and likely cannot apply authentication headers or url parameters directly to a fetcher request; Coda will do this for you.
The fetcher may also apply rate limits, to ensure that a given pack is not sending too many requests in a short period of time, which may affect the service you are fetching from.

Authentication

Packs that use third-party APIs and particularly those that fetch user-specific data will almost always require authentication. The SDK supports many kinds of authentication, you simply declare what kind of authentication your pack uses and some configuration data in your pack definition, and Coda will facilitate getting authentication information from the user when they use your pack, and the necessary authentication data will be applied to each fetcher request that you make from your pack code.

User (Default) Authentication vs System Authentication

The SDK broadly divides authentication into two categories: authentication that is tied to the user of the pack vs authentication that is managed by the system, aka the pack author. In the pack definition the former is known as userAuthentication and the latter systemAuthentication. You will typically specify one or the other in your pack definition, or neither if your pack does not make http requests or those requests do not require authentication.
Default authentication is the most common. Specify this if each user of your pack should log in with OAuth, or have their own API key, or whatever user-specific token is necessary for the pack to be able to retrieve data that is specific to that user.
Use system authentication if you as the pack author will provide the necessary tokens to successfully make http requests within your pack. An example would be if your pack returns weather forecasts and the API involved requires an API key, but individual users need not provide their own API key. You as the pack author will register an API key and provide it to Coda, and Coda will apply it to all pack requests regardless of the user.

Security

The SDK applies authentication info to fetcher requests automatically so you as the pack author don’t need to worry about doing so and about how to do so, making authoring packs easier. But critically the SDK does this in a way so that packs code never has access to any authentication information at all, so that users and pack authors are protected from inadvertent or intentional mishandling of credentials.
Your packs code will invoke context.fetcher.fetch() without any authentication info; in the process of executing the fetch request, Coda will automatically append credentials to the headers or url of the request, as appropriate, and then fulfill the request. Packs code will never have access to the request after authentication credentials have been applied.

Authentication Types

OAuth2: The user will be prompted to go through an OAuth2 authentication flow in the browser with a third-party service. The access token that the third-party service provides to Coda at the end of this flow will be applied to each fetcher request in the Authorization header, in the form Authorization: Bearer <access-token>. A custom token prefix other than Bearer may be provided by the pack author if necessary. The pack author provides OAuth2 configuration information including the third-party urls to use for the token creation flow and token exchange, the scopes needed by the pack. The pack author must also provide a client id and client secret for the third-party service, which are typically obtained when registering a new application in the third-party service’s developer portal.
During your registration with that third-party, you will need to provide an authorization callback URL:
See the “Progressive OAuth” section below for additional advanced capabilities when using OAuth2.
HeaderBearerToken: The user (or pack author, if using system authentication) provides an API token which is applied to the Authorization header of each fetcher request, in the form Authorization: Bearer <token>.
CustomHeaderToken: The user (or pack author, if using system authentication) provides an API token, which is applied to a custom API header as specified by the pack author, in the form <custom-header>: <custom-prefix> <token>.
QueryParamToken: The user (or pack author, if using system authentication) provides an API token, which is applied to the url of each fetcher request in a url parameter specified by the pack author. Using url params for authentication is not recommended if there are other alternatives.
MultiQueryParamToken: The user (or pack author, if using system authentication) provides multiple tokens, which are applied to the url of each fetcher request in url parameters specified by the pack author. This is not common. Using url params for authentication is not recommended if there are other alternatives.
WebBasic: The user (or pack author, if using system authentication) provides a username and typically a password, which are base64-encoded and applied to the Authorization header of each fetcher request, in the form Authorization: Basic <base64encode(username:password)>, as outlined in . Web basic authentication sends passwords in clear text so is not recommended if there are alternatives.
CodaApiHeaderBearerToken: Internally the same as HeaderBearerToken but for cases where a pack is going to make requests to Coda’s own API. The UI will assist the user in creating and configuring an API token without the user needing to do this manually. This is mostly for use by Coda-internal packs.
Various: Indicates that the pack has various different authentication methods. When this is indicated, the user will be able to specify what type of authentication to use when they create a connection. This is mostly for use by Coda-internal packs.
OAuth2, CodaApiHeaderBearerToken, and Various are not available for system authentication.

Progressive OAuth

Sometimes the capabilities of a pack change over time, or the underlying API changes, or you wish to offer optional functionality in your pack that may not be available to all users. When using OAuth2 authentication, you will have the ability to change the OAuth scopes that are used across versions of your pack, and to set formula-specific scopes if desired.
When a user installs your pack, if it uses OAuth, the user is prompted to set up an account and is redirected to the third-party service to go through the OAuth flow and ultimately get an access token. The scopes that are requested during that flow are whatever the pack definition declares, for the pack version that is live at that time.
What if you’re adding new formulas to your pack that require an additional scope, or the API you’re working with has changed and has necessitated a new scope? You can simply add the new scope(s) to your pack definition and publish a new version of the pack. If a user of your pack tries to invoke a formula and it fails, Coda will try to determine if the failure was due to the user not having sufficient OAuth scopes, likely because they set up their account on an older version of your pack. After receiving the error, the user will be prompted to re-authorize their account, this time with the new scopes.
Alternatively, you may want to offer functionality in your pack that may not be available to all users. For example, the Slack API has endpoints both for regular users of a Slack workspace as well as endpoints that are only accessible by administrators of that workspace. Those latter endpoints require OAuth scopes that can only be granted to administrators. If you wish to have a Slack pack with formulas or syncs for both regular users and administrators, you can either split your pack into two, one targeted at regular users and one targeted at administrators, or you can declare that specific formulas or syncs require additional scopes.
Use the extraOAuthScopes property of a formula or sync formula definition to declare additional scopes that are only needed for that formula. As above, if a user tries to execute such a formula and it fails, Coda will try to determine if it failed due to insufficient scopes, and prompt the user to re-auth but this time with the additional scopes.

Syncs

A sync is a specific kind of formula, with the goal of populating a Coda table with data from a third-party data source. A sync formula returns an array of objects, with each object representing a row of data in the resulting table.
Syncs assume that pagination will be necessary in most cases. One invocation of a sync formula is not meant to return all of the applicable results, but only one page of results of a reasonable size. For example, if you are fetching items from an API that returns 100 items per API request, it would be very reasonable for your sync formula to return 100 results per invocation.
The 2nd parameter to a sync formula is a SyncContext, which has all the fields of the Context object used with regular formulas, but also includes a property called sync, which is a Sync object containing a property called continuation. A Continuation is a simple representation that you define of where you are in the overall sync process. Conceptually, the continuation is just a page number, but it can represent more if you need it.
The first time your sync formula is invoked, the continuation will be undefined. Your formula then returns its array of results for that invocation, and optionally a continuation to use the next time the sync formula is invoked. If a continuation is not returned, the sync terminates. If a continuation is returned, that same continuation is passed as an input in context.sync.continuation the next time the formula is invoked. The formula is invoked repeatedly with each subsequent continuation until no continuation is returned.
A Continuation is just a JavaScript object mapping one or more arbitrary key names to a string or number. It’s up to you what you want to include to help you keep track of where you are in the overall sync. Typically, if you’re calling an API, the continuation closely matches what the API uses for pagination.
Suppose the API you’re using allows you to pass a page number in a url parameter to filter results just to that page. You could return {page: 2} as your continuation. You could then look at context.sync.continuation.page in your sync formula to determine which page of results you should request in that particular invocation.
If the API you’re using instead returns a complete, opaque url of the next page of results in response metadata, you might structure your continuation like {nextUrl: 'https://myapi.com/results?pageToken=asdf123'}.

Dynamic Sync Tables

Most sync tables have schemas that can be statically defined. For example, if you’re writing a sync of a user’s Google Calendar events, the structure of an Event from the Google Calendar API is well-known and you can write a schema for what your table should contain.
In certain cases, you may want to sync data whose structure is not known in advance and may depend on the user doing the sync. For example, Coda’s Jira pack allows users to sync data from their Jira instance, but Jira lets users create arbitrary custom fields for their Issue objects. So the schema of the Issues sync table is not known in advance; it depends on the Jira account that the user is syncing from.
Coda supports “dynamic” sync tables for cases like these. Instead of including a static schema in your sync table definition, you include a formula that returns a schema. This formula can use the fetcher to make authenticated http requests to your pack’s API so that you may retrieve any necessary info from that third-party service needed to construct an appropriate schema.
To define a dynamic schema, use the makeDynamicSyncTable() wrapper function. You will provide a getSchema formula that returns a schema definition. You’ll also provide some supporting formulas like getName, to return a name in the UI for the table, in case even the name of the entities being synced is dynamic.
There are two subtle variants of dynamic sync tables. A sync table can be dynamic simply because the shape of the entities being synced vary based on who the current user is. For example, in the Jira example, Jira Issues are synced by hitting the same static Jira API url for Issues, but the schema of the issues returned will be different depending on the configuration of the Jira instance of the calling user.
Alternatively, a sync table can be dynamic because the data source is specific to each instance of the table. If you were building a sync table to sync data from a Google Sheet, the data source would be the API url of a specific sheet. In this case, the sync table will be bound to a dynamicUrl that defines the data source. This url will be available to all of the formulas to implement the sync table in the sync context, as context.sync.dynamicUrl. To create a sync table that uses dynamic urls, you must implement the listDynamicUrls metadata formula in your dynamic sync table definition, as described below.

Type Hints

The SDK intentionally provides only a few value types for object properties to keep things simple, but the Coda application is able to understand many more types of values. The SDK allows you to optionally provide a codaType when declaring a property in an object schema which tells Coda how to interpret a value that is otherwise one of the basic value types.
For example, suppose the API you’re using provides a created-at timestamp as a numeric Unix time value. You can simply declare your field as {type: ValueType.Number, codaType: ValueHintType.DateTime} and Coda will parse the timestamp into a proper DateTime value on your behalf.
Or perhaps you want to return an object that has an image associated with it, and your pack and the API it integrates with specify that image as a url. Your schema can declare that property as {type: ValueType.String, codaType: ValueHintType.Image} and that url will be downloaded to be hosted on Coda and presented as an image in the Coda UI.
A full list of ValueHintTypes can be found in the .

Schemas

Normalization

Syncs and formulas that return objects get normalized, which simply means that their property names get rewritten to a standardized format. So long as you use the wrapper methods like makeObjectFormula() or makeSyncTable(), which we require as a best practice, this will happen automatically on your behalf so you don’t need to worry about implementing normalization. However, it will affect the return values of object formulas and syncs so it’s good to be aware of what’s happening in order to understand the output when testing your formulas and avoid being surprised.
There are a few reasons that object property names are normalized. One is to remove any punctuation from property names that would affect the Coda formula builder and cause a user to be unable to access a field in an object due to a bad name. Another reason is to standardize all property names across all the different packs which have different authors, so that working with pack values is consistent for Coda users.
Normalization simply removes punctuation and rewrites strings into Pascal case. That is, property names like fooBar, foo_bar, and foo bar will all be normalized to FooBar.
The SDK will normalize both the schema as well as the return values from object formulas. So for example, if you’re working with an API that returns a User object that looks like {id: '...', name: '...'}, you can define the schema for the object as
And your formula can return objects you get from the API that look like {id: '...', name: '...'} as-is. Coda will normalize the schema so that it looks like
And it will convert your return value into {Id: '...', Name: '...'} to match the schema.
When testing your code using coda execute or the testing helpers provided by the SDK like executeFormulaFromPackDef, you’ll notice that the return values are normalized as above.

Object Schema Identities

An object schema can declare an identity property. In most cases this is optional, but it is required for the top-level schema of a sync table; however, an identity will be created on your behalf using the identityName attribute when defining a sync table.
Declaring an identity means that you’re giving your object a name that can be used to reference these objects from elsewhere. Objects with identities also render as “chips” in the Coda UI with the pack logo.
By having a name to reference, you can do things like have sync tables in your pack that reference other sync tables. You can have a child object in your sync table’s schema that has codaType: ValueHintType.Reference, and if the identity of the object matches the identity of another sync table, Coda will render those values as reference objects pointing the corresponding row in the other table.
With a reference, you need not populate a complete object value, you only need to provide the object id and a display name, and if the referenced object already exists in the doc, Coda will render the entire referenced object. The easiest way to create a reference schema is to use the helper . If you factor out your sync table’s schema into a variable, you can pass the schema to this helper and it will generate a reference schema.
See “Updating Synced Columns With Button Actions” for another use case for schema identities.
Identities must be unique across a pack. Normally you need only declare the name of an identity object: identity: {name:'MyObject'}. A complete identity object is the combination of a name, a pack id, and optionally a dynamicUrl. The pack id will be populated with the id of your pack when you upload it. If you’re returning a schema for a dynamic sync table, you must include the dynamicUrl of the sync entity in your identity definition.

Key Mapping and Extraneous Properties

The SDK assumes that it will be common to write packs that mostly fetch and return data from third-party API, and that massaging that data to conform to an SDK schema might be tedious, so the SDK supports ways to pass through third-party data as-is or with minimal massaging.
The SDK will automatically remove properties that are not declared within the schema. So if your API returns properties not useful to the end user, you can just leave them out of the object schema and they will be automatically elided.
To make parsing an API object and massaging it to match your schema easier, you can use the fromKey property of a schema property definition. This instructs the SDK to convert properties with whatever name you specified in the fromKey property to the name that you gave to that property in the schema.
So suppose that the API you’re working with returns a User object that looks like {userId: '...', userName: '...'} but you want your pack to return a value that has the friendlier property names id and name. You can define your schema as
You can then return the user object from the API as-is, and the userId and userName fields will be remapped to id and name (and then those fields will be normalized, too).
With the use of the schema declaration (including fromKey), you generally can avoid writing any custom code to remove or remap fields to make an API object conform to your desired schema.

Execution Environment

Coda packs execute on the server side in a special isolated node environment. They do not execute in the browser and don’t have access to any browser resources or user information other than what the packs infrastructure explicitly provides.
The Packs environment is currently built on top of Node 14, and has access to ES2020 features.

Logging

You can log messages using the standard console logger and they will appear in your Pack’s development console. This can be useful for debugging during development as well as in production.
The format of the message parameter to the Logger method is .

A few key concepts as you get started

You can build Packs in plain, vanilla Javascript (JS). That means the JS you know is fully applicable here, and all the resources you’d Google for to get help on JS can help you here. (Advanced users: you can optionally also use Typescript (TS), as desired).
Sync Tables, Column Formats, and Buttons are all driven by Formulas: They just take inputs and show outputs in different ways. In particular:
Sync Tables: displays data as a Coda table with many rows vs as an individual value or object
Column Formats: Takes the value the user provides as an input to a formula, returning an object or specific value
Buttons: Formulas but that have a special flag (isAction) marked


Share
 
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.