Skip to content

Preview content with rich cards

Pack formulas can return structured data as objects, allowing a single call to return a variety of related information. By default these objects are presented as "mentions", shown as chips in the document that you can hover over to get the full set of information.

Schema shown as a mention

Cards are an alternative, more visual display format for objects that allow the user to easily consume key information. Additionally URLs pasted into a document can be automatically shown as cards, providing an easy way for users to discover and use your Pack.

Schema shown as a card

View Sample Code

Using cards

Cards are shown as distinct type of building block, but any formula returning a compatible object can be displayed as a card. It will also be shown as an option for matching links.

Cards shown in the Pack side panel

Changing a mention to show as a card in the display as menu

Changing a mention to show as a card in the display as menu

Changing a mention to show as a card in the display as menu

Creating cards

Although they appear as a separate type of building block, cards are really just an alternative display format for formulas that return structured data. Your formula will appear as a card if it returns an object schema that fulfills all of the following:

  • Defines one of: displayProperty or titleProperty
  • Defines one of: linkProperty, snippetProperty, or subtitleProperties

See the sections below for how to configure specific attributes of the card.

Example: Weather card
import * as coda from "@codahq/packs-sdk";
export const pack = coda.newPack();

// Define the schema that will be used to render the card.
const WeatherSchema = coda.makeObjectSchema({
  properties: {
    summary: { type: coda.ValueType.String, fromKey: "shortForecast" },
    forecast: { type: coda.ValueType.String, fromKey: "detailedForecast" },
    temperature: { type: coda.ValueType.String },
    wind: { type: coda.ValueType.String, fromKey: "windSpeed" },
    icon: {
      type: coda.ValueType.String,
      codaType: coda.ValueHintType.ImageReference,
    },
    link: {
      type: coda.ValueType.String,
      codaType: coda.ValueHintType.Url,
    },
  },
  displayProperty: "summary",
  subtitleProperties: [
    // Only show the value for the temperature property.
    { property: "temperature", label: "" },
    "wind",
  ],
  snippetProperty: "forecast",
  imageProperty: "icon",
  linkProperty: "link",
});

// Add a formula that fetches the weather and returns it as a card.
pack.addFormula({
  name: "CurrentWeather",
  description: "Get the current weather at a specific location (US only).",
  parameters: [
    coda.makeParameter({
      type: coda.ParameterType.Number,
      name: "latitude",
      description: "The latitude of the location.",
    }),
    coda.makeParameter({
      type: coda.ParameterType.Number,
      name: "longitude",
      description: "The longitude of the location.",
    }),
    coda.makeParameter({
      type: coda.ParameterType.Boolean,
      name: "isMetric",
      description: "Whether to use metric units. Default: false.",
      optional: true,
    }),
  ],
  resultType: coda.ValueType.Object,
  schema: WeatherSchema,
  execute: async function ([latitude, longitude, isMetric], context) {
    let url = await getForecastUrl(latitude, longitude, context);
    if (isMetric) {
      url = coda.withQueryParams(url, { units: "si" });
    }
    let response = await context.fetcher.fetch({
      method: "GET",
      url: url,
    });
    let data = response.body;
    let weather = data.properties.periods[0];
    // Add the unit onto the temperature.
    weather.temperature = `${weather.temperature}°${weather.temperatureUnit}`;
    weather.link =
      coda.withQueryParams("https://forecast.weather.gov/MapClick.php", {
        lat: latitude,
        lon: longitude,
      });
    return weather;
  },
});

// A helper function that gets the forecast URL for a given location.
async function getForecastUrl(latitude: number, longitude: number,
  context: coda.ExecutionContext): Promise<string> {
  try {
    let response = await context.fetcher.fetch({
      method: "GET",
      url: `https://api.weather.gov/points/${latitude},${longitude}`,
    });
    let data = response.body;
    return data.properties.forecast;
  } catch (error) {
    // Check if the error is due to the location being outside the US.
    if (error.statusCode === 404) {
      let statusError = error as coda.StatusCodeError;
      let message = statusError.body?.detail;
      if (message) {
        throw new coda.UserVisibleError(message);
      }
    }
    throw error;
  }
}

pack.addNetworkDomain("weather.gov");

Title

The card's title appears at the top of the card, and is required.

The card's title

Many object schemas already define a display value (via displayProperty) which determines which property value is shown in the mention chip for the object. The same display value will be shown as the title of the card, but can be overridden by defining a titleProperty. This is useful if you want to use a different property specifically for cards, for example that is longer.

const ProductSchema = coda.makeObjectSchema({
  properties: {
    sku: { type: coda.ValueType.String },
    name: { type: coda.ValueType.String },
    // ...
  },
  // Use the SKU in mention chips, to save space.
  displayProperty: "sku",
  // Display the full product name in cards.
  titleProperty: "name",
  // ...
});

Subtitles

The card can include a subtitle that highlights key properties, which appears under the title.

The card's subtitle

The properties displayed in the subtitle are determined via the subtitleProperties field of the schema, which lists the subset of properties to show and the order to show them.

const ProductSchema = coda.makeObjectSchema({
  properties: {
    // ...
    quantity: { type: coda.ValueType.Number },
    price: {
      type: coda.ValueType.Number,
      codaType: coda.ValueHintType.Currency,
    },
  },
  // ...
  subtitleProperties: ["quantity", "price"],
});

If subtitleProperties are not defined the card will fall back to using the featuredProperties configured on the schema, if any. Featured properties are also used to determine the initial columns to show in a sync table. If you want a schema to include featuredProperties but not display a subtitle, set subtitleProperties to an empty array.

The subtitle shows a label for each property, which defaults to {Name}: {Value}. You can customize the label for a property, such as removing the property name, in the property definition. See the Schemas guide for more information.

Body

The card body can include a snippet of content, which appears under the title (and subtitle if defined).

The card's body

The snippet is meant to contain a limited amount of text, although there is no size limit enforced. Which property's content to use for the snippet is defined by the field snippetProperty, and it can only refer to properties of type String or Array of String. These properties can contain rich text, such as Markdown and HTML.

const ProductSchema = coda.makeObjectSchema({
  properties: {
    // ...
    description: { type: coda.ValueType.String },
  },
  // ...
  snippetProperty: "description",
});

Image

The card can include an image, which appears to the left of the other content.

The card's image

Which property's content to use for the image is defined by the field imageProperty, and it can only refer to properties of type String with a hint of ImageReference.

const ProductSchema = coda.makeObjectSchema({
  properties: {
    // ...
    photo: {
      type: coda.ValueType.String,
      codaType: coda.ValueHintType.ImageReference,
    },
  },
  // ...
  imageProperty: "photo",
});

Creating custom images

Images can make cards much more appealing, but sometimes the API you are calling does not include a relevant image for the resource. In those cases you can add a custom image to the object, for example a generic icon. SVG data URIs can be a simple way to serve those images without needing a separate server to host them.

The card can include a link, which will be opened when the card is clicked. The domain of the link is also shown at the bottom of the card.

The card's link

Which property's content to use for the link is defined by the field linkProperty, and it can only refer to properties of type String with a hint of Url.

const ProductSchema = coda.makeObjectSchema({
  properties: {
    // ...
    websiteLink: {
      type: coda.ValueType.String,
      codaType: coda.ValueHintType.Url,
    },
  },
  // ...
  linkProperty: "websiteLink",
});

Display a link as a card

One of the most common uses for cards is to display information about an item from an external application, usually identified by a URL. For example, the Slack Pack's contains a Message card takes in a message URL and displays the text, author, etc.

To make it easier to discover these cards, when a user pastes a link into a doc Coda will show a list of compatible Packs. Clicking one of these will install the Pack and display the link as a card.

Dialog showing cards that can be used to display a link

Built-in card option

The link "Display as" menu may include an option for "Card", which displays a fix set of metadata for public URLs. This is distinct from Pack cards, which are shown as additional options below that.

Coda will automatically display a link as a card if it matches a Pack already installed in the doc, or for certain Coda-made Packs.

To enable this feature for your Pack, add a column format pointing to a formula that accepts a URL and returns a card. Then add matchers (regular expressions) to the column format that determine which URLs the the prompt should appear on.

pack.addFormula({
  name: "Product",
  description: "...",
  parameters: [
    coda.makeParameter({
      type: coda.ParameterType.String,
      name: "url",
      description: "...",
    }),
  ],
  resultType: coda.ValueType.Object,
  schema: ProductSchema,
  // ...
});

pack.addColumnFormat({
  name: "Product",
  formulaName: "Product",
  matchers: [
    new RegExp("^https://example.com/products/[A-Z0-9]+$"),
  ],
});

Table behavior

Formulas and column formats that return a card can be used in a table, but currently the resulting object can only be shown as a mention.

Example: Todoist task card
import * as coda from "@codahq/packs-sdk";
export const pack = coda.newPack();

// A schema defining the card, including all of metadata what specifically to
// highlight in the card.
const TaskSchema = coda.makeObjectSchema({
  properties: {
    name: {
      description: "The name of the task.",
      type: coda.ValueType.String,
      required: true,
    },
    description: {
      description: "A detailed description of the task.",
      type: coda.ValueType.String,
    },
    url: {
      description: "A link to the task in the Todoist app.",
      type: coda.ValueType.String,
      codaType: coda.ValueHintType.Url,
    },
    priority: {
      description: "The priority of the task.",
      type: coda.ValueType.String,
    },
    id: {
      description: "The ID of the task.",
      type: coda.ValueType.String,
      required: true,
    },
  },
  // Which property's content to show in the title of the card.
  displayProperty: "name",
  // Which property contains the link to open when the card is clicked.
  linkProperty: "url",
  // Which property's content to show in the body of the card.
  snippetProperty: "description",
  // Which properties' content to show in the subtitle of the card.
  subtitleProperties: ["priority"],
});

// Formula that renders a card for a task given it's URL. This will be shown a
// "Card" in the Pack's list of building blocks, but is also a regular formula
// that can be used elsewhere.
pack.addFormula({
  name: "Task",
  description: "Gets a Todoist task by URL",
  parameters: [
    coda.makeParameter({
      type: coda.ParameterType.String,
      name: "url",
      description: "The URL of the task",
    }),
  ],
  resultType: coda.ValueType.Object,
  schema: TaskSchema,

  execute: async function ([url], context) {
    let taskId = extractTaskId(url);
    let response = await context.fetcher.fetch({
      url: "https://api.todoist.com/rest/v2/tasks/" + taskId,
      method: "GET",
    });
    let task = response.body;
    return {
      name: task.content,
      description: task.description,
      url: task.url,
      priority: task.priority,
      id: task.id,
    };
  },
});

// Regular expressions that match Todoist task URLs. Used to match and parse
// relevant URLs.
const TaskUrlPatterns: RegExp[] = [
  new RegExp("^https://todoist.com/app/task/([0-9]+)$"),
  new RegExp("^https://todoist.com/app/project/[0-9]+/task/([0-9]+)$"),
  new RegExp("^https://todoist.com/showTask\\?id=([0-9]+)"),
];

// Add a column format for the Task formula, to define which URLs it should
// trigger for. This also makes it easier to use the formula in a table column.
pack.addColumnFormat({
  // How the option will show in the link and column type dialogs.
  name: "Task",
  // The formula that generates the card.
  formulaName: "Task",
  // The set of regular expressions that match Todoist task URLs.
  matchers: TaskUrlPatterns,
});

// Helper function to extract the Task ID from the URL.
function extractTaskId(taskUrl: string) {
  for (let pattern of TaskUrlPatterns) {
    let matches = taskUrl.match(pattern);
    if (matches && matches[1]) {
      return matches[1];
    }
  }
  throw new coda.UserVisibleError("Invalid task URL: " + taskUrl);
}

// Allow the pack to make requests to Todoist.
pack.addNetworkDomain("todoist.com");

// Setup authentication using a Todoist API token.
pack.setUserAuthentication({
  type: coda.AuthenticationType.HeaderBearerToken,
  instructionsUrl: "https://todoist.com/app/settings/integrations",
});