# Accept input with parameters

The primary mechanism for passing data from the user or document into your Pack is via parameters. You define the parameters in your code and the user fills them with values when they use your Pack. The same parameter mechanism is used by formulas, actions, and sync tables.

[View Sample Code](../../../samples/topic/parameter/)

## Using parameters

In the formula editor parameters are entered as comma-separated values, while in the action dialog or sync table side panel they presented as input boxes.

## Defining parameters

The [`parameters`](../../../reference/sdk/core/interfaces/PackFormulaDef/#parameters) property of a formula contains the array of parameter definitions, each one containing information about the parameter. The helper function [`makeParameter()`](../../../reference/sdk/core/functions/makeParameter/) is used to create these definitions, and a `type`, `name`, and `description` are required.

```
sdk.makeParameter({
  type: sdk.ParameterType.String,
  name: "type",
  description: "The type of cookie.",
});
```

See [`ParamDef`](../../../reference/sdk/core/interfaces/ParamDef/) for the full set of properties you can define for a parameter.

## Accessing parameter values

At runtime, the values set by the user are passed to the formula's `execute` function as the first argument, bundled up as an array.

```
pack.addFormula({
  // ...
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.String,
      name: "type",
      description: "The type of cookie.",
    }),
    sdk.makeParameter({
      type: sdk.ParameterType.Number,
      name: "num",
      description: "How many cookies.",
    }),
  ],
  // ...
  execute: async function ([type, num], context) {
    // ...
  },
});
```

The order that you define the parameters determines the order they are passed into the `execute` function. The names of the parameters don't need to match the variable names you use for them in the `execute` function, but it's usually more readable to keep them the same.

Array destructuring

In the code above, and across our other samples, we typically use [array destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) to pull values out of the parameter array and assign them to variables. You could alternatively do that within the body of the `execute` function:

```
execute: async function (parameters, context) {
  let word = parameters[0];
  let count = parameters[1];
},
```

## Parameter types

When defining a parameter you must specify what type of data the parameter will accept. The enum [`ParameterType`](../../../reference/sdk/core/enumerations/ParameterType/) lists all of the allowed parameter types.

### Plain text

Use the `String` parameter to pass a plain text value to your formula. Coda will automatically apply the [`ToText()`](https://coda.io/formulas#ToText) formula to the input and pass it to the `execute` function as a [JavaScript String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String).

String parameters are compatible with almost every column type in Coda, as most have a text representation. At times a string parameter may be better than a more semantically accurate type, as it allows you to access the value as shown to the user.

Example: Hello world formula

```
import * as sdk from "@codahq/packs-sdk";
export const pack = sdk.newPack();

// Say Hello to the given name.
pack.addFormula({
  name: "Hello",
  description: "A Hello World example.",
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.String,
      name: "name",
      description: "The person's name you would like to say hello to.",
    }),
  ],
  resultType: sdk.ValueType.String,
  execute: async function ([name]) {
    return "Hello " + name + "!";
  },
});
```

### Rich text

Use the `Html` or `Markdown` parameter type to pass text values with formatting included. Coda will convert the formatting to an equivalent block of HTML or Markdown markup, and pass it to the `execute` function as a [JavaScript String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String).

These parameter types don't provide perfect fidelity, and the converted HTML or Markdown may be quite different than how the value displays in Coda. Often it is closer to what you'd get if you pasted that value into another rich text editor.

HTML markup may change

The generated HTML for a given value is not a stable API surface that you should rely on. We may change it at any time without warning, so we don't recommend that you parse it to extract information. Use it for display purposes only.

### Numbers

Use the `Number` parameter type to pass a number to your formula. Coda will automatically apply the [`ToNumber()`](https://coda.io/formulas#ToNumber) formula to the input and pass it to the `execute` function as a [JavaScript Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number).

The number equivalent for some column types may not be obvious. Specifically:

- **Percent** values will be converted into the equivalent fraction. For example, "75%" will be passed as `0.75`.
- **Date** and **Date and time** values will be converted into the number of days since 1899-12-30[1](#fn:1). For example, "1955-11-12" will be passed as `20405`.
- **Time** and **Duration** values will be converted into a number of days. For example, "12 hrs" will be passed as `0.5`.

Example: Pizza eaten formula

```
import * as sdk from "@codahq/packs-sdk";
export const pack = sdk.newPack();

// Formula that converts slices of a pizza into a percentage eaten.
pack.addFormula({
  name: "PizzaEaten",
  description: "Calculates what percentage of a pizza was eaten.",
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.Number,
      name: "slices",
      description: "How many slices were eaten.",
    }),
  ],
  resultType: sdk.ValueType.Number,
  codaType: sdk.ValueHintType.Percent,
  execute: async function ([slices], context) {
    return slices / 8;
  },
});
```

### Booleans

Use the `Boolean` parameter type to pass a boolean (true/false) to your formula. Coda will pass the value to the `execute` function as a [JavaScript Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean).

### Dates

Use the `Date` parameter type to pass a date value to your formula. Coda will automatically apply the [`ToDateTime()`](https://coda.io/formulas#ToDateTime) formula to the input and pass it to the `execute` function as a [JavaScript Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date).

JavaScript Date objects can only represent a specific moment in time. This means that they can't easily represent less specific concepts like a day (regardless of time), a time (regardless of day), or duration. Coda handles those column types using the following logic:

- **Date** values will be converted into a datetime representing midnight on that day in the document's timezone.
- **Time** and **Duration** values will be converted a datetime that is that much time past midnight on 1899-12-30[1](#fn:1), in the document's timezone. For example, the duration "12 hours" in a document set to "America/New York" will be passed as `Sat Dec 30 1899 12:00:00 GMT-0500 (Eastern Standard Time)`.

Timezone shifting

Because of how timezones work in Coda and JavaScript, the date passed into the parameter may appear different in your Pack code. See the [Timezones guide](../../advanced/timezones/) for more information.

Example: Good New Years Eve glasses formula

```
import * as sdk from "@codahq/packs-sdk";
export const pack = sdk.newPack();

pack.addFormula({
  name: "GoodNYEGlasses",
  description: "Determines if a date is good for New Years Eve glasses " +
    "(the year contains two zeros).",
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.Date,
      name: "date",
      description: "The input date.",
    }),
  ],
  resultType: sdk.ValueType.Boolean,
  execute: async function ([date], context) {
    // Format the JavaScript Date into a four-digit year.
    let formatted = date.toLocaleDateString("en", {
      timeZone: context.timezone, // Use the timezone of the doc (important!).
      year: "numeric",
    });
    // Extract all of the zeros from the year.
    let zeros = formatted.match(/0/g);
    return zeros?.length >= 2;
  },
});
```

### Images and files

Use the `Image` parameter type to pass an image to your formula, and the `File` type for files. The value passed to the `execute` function will be the URL of that image or file.

Images and files that the user uploaded to the doc will be hosted on the `codahosted.io` domain and don't require authentication to access. These URLs are temporary, and you should not rely on them being accessible after the Pack execution has completed.

If you need access to the binary content of the image or file you'll need to use the [fetcher](../fetcher/#binary-response) to retrieve it. The fetcher is automatically allowed access to the `codahosted.io` domain, so no need to declare it as a [network domain](../fetcher/#network-domains). It's not possible to access the binary content of images coming from an **Image URL** column, since they can come from any domain.

Example: Image file size formula

```
import * as sdk from "@codahq/packs-sdk";
export const pack = sdk.newPack();

// Regular expression that matches Coda-hosted images.
const HostedImageUrlRegex = new RegExp("^https://(?:[^/]*\.)?codahosted.io/.*");

// Formula that calculates the file size of an image.
pack.addFormula({
  name: "FileSize",
  description: "Gets the file size of an image, in bytes.",
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.Image,
      name: "image",
      description:
        "The image to operate on. Not compatible with Image URL columns.",
    }),
  ],
  resultType: sdk.ValueType.Number,
  execute: async function ([imageUrl], context) {
    // Throw an error if the image isn't Coda-hosted. Image URL columns can
    // contain images on any domain, but by default Packs can only access image
    // attachments hosted on codahosted.io.
    if (!imageUrl.match(HostedImageUrlRegex)) {
      throw new sdk.UserVisibleError("Not compatible with Image URL columns.");
    }
    // Fetch the image content.
    let response = await context.fetcher.fetch({
      method: "GET",
      url: imageUrl,
      isBinaryResponse: true, // Required when fetching binary content.
    });
    // The binary content of the response is returned as a Node.js Buffer.
    // See: https://nodejs.org/api/buffer.html
    let buffer = response.body as Buffer;
    // Return the length, in bytes.
    return buffer.length;
  },
});
```

Get the original filename

The original filename of an uploaded image or file can be obtained from the `Content-Disposition` header returned when fetching its `codahosted.io` URL.

```
Content-Disposition: attachment; filename="cat1.png"
```

This filename is not populated in all cases, for instance when an image was pasted into a table from the clipboard. You can see an example of how to parse this header, and fallback in cases where the filename isn't present, in the [Box sample Pack](https://github.com/coda/packs-examples/blob/c565981293f14a4ca82bc2ddcf385ea9c7bbbad6/examples/box/helpers.ts#L31-L52).

### Lists

Each of the parameter types described above has an array variant that allows you to pass a list of values of that type. For example, `StringArray` and `NumberArray`. All of the values in the list must be of the same type and not blank.

Example: Longest string formula

```
import * as sdk from "@codahq/packs-sdk";
export const pack = sdk.newPack();

pack.addFormula({
  name: "Longest",
  description: "Given a list of strings, returns the longest one.",
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.StringArray,
      name: "strings",
      description: "The input strings.",
    }),
  ],
  resultType: sdk.ValueType.String,
  execute: async function ([strings], context) {
    if (strings.length === 0) {
      throw new sdk.UserVisibleError("No options provided.");
    }
    let result;
    for (let str of strings) {
      if (!result || str.length > result.length) {
        result = str;
      }
    }
    return result;
  },
});
```

### Table column

Passing a table column into an array parameter can be error prone, because if the column contains blank cells the formula will fail to run. To accept a list that may include blank values use the sparse variant of the array parameter (`SparseStringArray`, `SparseNumberArray`, etc). Blank cells will be represented as `null`, and you'll need to make sure your code can handle those values.

Example: Total cost formula

```
import * as sdk from "@codahq/packs-sdk";
export const pack = sdk.newPack();

pack.addFormula({
  name: "TotalCost",
  description: "Calculates the total cost for an order.",
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.SparseNumberArray,
      name: "prices",
      description: "The prices for each item.",
    }),
    sdk.makeParameter({
      type: sdk.ParameterType.SparseNumberArray,
      name: "quantities",
      description: "The quantities of each item. Default: 1.",
      optional: true,
    }),
    sdk.makeParameter({
      type: sdk.ParameterType.SparseNumberArray,
      name: "taxRates",
      description: "The tax rates for each item. Default: 0.",
      optional: true,
    }),
  ],
  resultType: sdk.ValueType.Number,
  codaType: sdk.ValueHintType.Currency,
  execute: async function ([prices, quantities=[], taxRates=[]], context) {
    if ((quantities.length > 0 && quantities.length !== prices.length) ||
        (taxRates.length > 0 && taxRates.length !== prices.length)) {
      throw new sdk.UserVisibleError("All lists must be the same length.");
    }
    let result = 0;
    for (let i = 0; i < prices.length; i++) {
      let price = prices[i];
      let quantity = quantities[i];
      let taxRate = taxRates[i];
      if (price == null) {
        // If the price is blank, continue on to the next row.
        continue;
      }
      if (quantity != null) {
        price *= quantity;
      }
      if (taxRate != null) {
        price += price * taxRate;
      }
      result += price;
    }
    return result;
  },
});
```

### Pages

Coda documents can contain many pages, and it's possible to pass the contents of a page to a Coda formula using the `Html` parameter type. Users will be presented with the pages they can select from in the autocomplete options, and once selected the formula will receive an HTML version of that page's content. Some features of the page may not be included in the HTML markup, and it should not be considered a complete or stable API surface.

Formulas not recalculated when page content changes

Unlike with other data sources, when passing a page as a parameter the formula will not be automatically recalculated when the content of the page changes. For this reason we recommend only passing pages for action formulas, which are calculated on each button press or automation run.

### Objects

Pack formulas can return structured data as [Objects](../data-types/#objects), but it's not possible to pass them as parameters. Users can't construct objects in the Coda formula language, so in general they don't make for a great input type.

If your Pack returns an object in one formula that you'd like to use an input to another formula, instead of passing the entire object you can just pass its unique ID. For example, the [Todoist Pack](../../../samples/full/todoist/) contains a `Tasks` sync table which returns `Task` objects. The `MarkAsComplete()` formula only takes the task's ID as input instead of the entire object.

## Optional parameters

By default all parameters you define are required. To make a parameter optional simply add `optional: true` to your parameter definition. Optional parameters are shown to the user but not required in order for the formula to execute. Optional parameters must be defined after all of the required parameters, and like required parameters their order is reflected in the Coda formula editor and the array of values passed to the `execute` function.

```
pack.addFormula({
  name: "TotalCost",
  // ...
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.Number,
      name: "cost",
      description: "The cost of the item.",
    }),
    sdk.makeParameter({
      type: sdk.ParameterType.Number,
      name: "quantity",
      description: "How many items.",
      optional: true,
    }),
    sdk.makeParameter({
      type: sdk.ParameterType.Number,
      name: "taxRate",
      description: "The tax rate for the item.",
      optional: true,
    }),
  ],
  // ...
  execute: async function ([cost, quantity, taxRate], context) {
    // ...
  },
});
```

Optional parameters that have not been set by the user will default to the JavaScript value `undefined` in your `execute` function. When you initialize your parameter variables in the `execute` function you can assign a default value that will get used when the parameter has not been explicitly set by the user.

```
pack.addFormula({
  // ...
  execute: async function ([cost, quantity = 1, taxRate = 0], context) {
    // ...
  },
});
```

When using a formula with optional parameters, the user may choose to set those parameters by name, instead of by position. This can be useful when they want to skip over some optional parameters that appear earlier in the list.

```
TotalCost(10, taxRate: 0.15)
```

In this case the `cost` and `taxRate` parameters would be set, but the `quantity` parameter would be undefined, and therefore use its default value of `1`.

Example: Scream text formula

```
import * as sdk from "@codahq/packs-sdk";
export const pack = sdk.newPack();

// Formats text to look like screaming. For example, "Hello" => "HELLO!!!".
pack.addFormula({
  name: "Scream",
  description: "Make text uppercase and add exclamation points.",
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.String,
      name: "text",
      description: "The text to scream.",
    }),
    sdk.makeParameter({
      type: sdk.ParameterType.Number,
      name: "volume",
      description: "The number of exclamation points to add.",
      optional: true,
    }),
    sdk.makeParameter({
      type: sdk.ParameterType.String,
      name: "character",
      description: "The character to repeat.",
      optional: true,
    }),
  ],
  resultType: sdk.ValueType.String,
  examples: [
    { params: ["Hello"], result: "HELLO!!!" },
    { params: ["Hello", 5], result: "HELLO!!!!!" },
    { params: ["Hello", undefined, "?"], result: "HELLO???" },
    { params: ["Hello", 5, "?"], result: "HELLO?????" },
  ],
  execute: async function ([text, volume = 3, character = "!"], context) {
    return text.toUpperCase() + character.repeat(volume);
  },
});
```

## Suggested values

As a convenience to users of your Pack, you can provide a suggested value for a parameter. When they use your formula the default will be pre-populated in the formula editor, action dialog, etc. The user is then free to edit or replace it this value.

To add a suggested value to a parameter set the field `suggestedValue` to the value you'd like to use. The suggested value must be of the same type as the parameter, for example a number parameter must have a number as its suggested default value.

```
sdk.makeParameter({
  type: sdk.ParameterType.Number,
  name: "days",
  description: "How many days of data to fetch.",
  suggestedValue: 30,
});
```

Currently suggested values are only used for required parameters, and setting them for optional parameters has no effect.

Example: Random dice roll action

```
import * as sdk from "@codahq/packs-sdk";
export const pack = sdk.newPack();

// Rolls virtual dice and returns the resulting numbers. Use it with a button in
// table and store the results in another column.
pack.addFormula({
  name: "RollDice",
  description: "Roll some virtual dice.",
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.Number,
      name: "quantity",
      description: "How many dice to roll.",
      suggestedValue: 1,
    }),
    sdk.makeParameter({
      type: sdk.ParameterType.Number,
      name: "sides",
      description: "How many sides the dice have.",
      suggestedValue: 6,
    }),
  ],
  resultType: sdk.ValueType.Array,
  items: sdk.makeSchema({
    type: sdk.ValueType.Number,
  }),
  isAction: true,
  execute: async function ([quantity, sides], context) {
    let results = [];
    for (let i = 0; i < quantity; i++) {
      let roll = Math.ceil(Math.random() * sides);
      results.push(roll);
    }
    return results;
  },
});
```

## Accepting multiple values

For some formulas you may want to allow the user to enter multiple values for a parameter. You could use an array parameter for this case but a more user-friendly approach may be to use variable argument (vararg) parameters. These are parameters that you allow the user to repeat as many times as needed.

```
Foo(List("A", "B", "C"))  # A string array parameter.
Foo("A", "B", "C")        # A string variable argument parameter.
```

They are defined using the `varargParameters` property and accept the same parameter objects. The values set by the user are passed in to the `execute` just like normal parameters, only there is an unknown number of them. The easiest way to access them is by using [JavaScript's "rest" syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#assigning_the_rest_of_an_array_to_a_variable), which captures the remaining values into an array.

```
pack.addFormula({
  // ...
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.String,
      name: "name",
      description: "The person's name.",
    }),
  ],
  varargParameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.String,
      name: "nickname",
      description: "A nickname for the person.",
    }),
  ],
  // ...
  execute: async function ([name, ...nicknames], context) {
    // ...
  },
});
```

There are some important differences between vararg parameters and standard parameters:

- They appear at the end of the formula, after all standard parameters.
- Unlike standard parameters they are optional by default, and cannot by made required.
- You can't provide a default value, since the user must always enter an explicit value.
- You can have more than one, but if so the user is required to enter complete sets of values. For example, if you have two vararg parameters `a` and `b`, the user can't provide a value for `a` without also providing a value for `b`. These pairs of parameters can then be repeated multiple times: `Foo("a1", "b1", "a2", "b2")`.

Partially supported in actions builder or sync table settings

While vararg parameters always work in the formula editor, they are only partially supported in the builder UIs. A single vararg parameter will be shown as if it was a single array parameter, and a pair of vararg parameters will be shown with a nice UI similar to that used by built-in actions. Three or more vararg parameters won't show up in the builder UIs at all, and the user will need to visit the formula editor to set their values.

Example: Step diagram formula

```
import * as sdk from "@codahq/packs-sdk";
export const pack = sdk.newPack();

// Takes an unknown number of steps and labels and outputs a simple diagram.
// Example: Steps("Idea", "Experiment", "Prototype", "Refine", "Product")
// Result: Idea --Experiment--> Prototype --Refine--> Product
pack.addFormula({
  name: "Steps",
  description: "Draws a simple step diagram using text.",
  parameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.String,
      name: "start",
      description: "The starting step.",
    }),
  ],
  varargParameters: [
    sdk.makeParameter({
      type: sdk.ParameterType.String,
      name: "label",
      description: "The label for the arrow.",
    }),
    sdk.makeParameter({
      type: sdk.ParameterType.String,
      name: "step",
      description: "The next step.",
    }),
  ],
  resultType: sdk.ValueType.String,
  execute: async function ([start, ...varargs], context) {
    let result = start;
    while (varargs.length > 0) {
      let label; let step;
      // Pull the first set of varargs off the list, and leave the rest.
      [label, step, ...varargs] = varargs;
      result += ` --${label}--> ${step}`;
    }
    return result;
  },
});
```

## Autocomplete

If you have a parameter that accepts a limited set of values it's usually best to provide those options using autocomplete. See the [Autocomplete guide](autocomplete/) for more information.

## Validation

Often parameters have a limited set of values that they support, such as a range of numbers or a set of strings. If you are passing these values to an API it may detect these invalid values and throw an error, but the resulting error message may not be clear to the user.

You can provide a better user experience by validating the parameters before you use them. A common way to do that is to check the values at the start of the `execute` function and throw a user-visible error if a problem is found.

```
pack.addSyncTable({
  name: "Orders",
  description: "List the open orders.",
  identityName: "Order",
  schema: OrderSchema,
  formula: {
    name: "SyncOrders",
    description: "Syncs the data.",
    parameters: [
      sdk.makeParameter({
        type: sdk.ParameterType.String,
        name: "sku",
        description: "Filters orders by the product SKU.",
      }),
      sdk.makeParameter({
        type: sdk.ParameterType.Number,
        name: "quantity",
        description: "Filters orders by a minimum quantity.",
      }),
    ],
    execute: async function (args, context) {
      let [sku, quantity] = args;
      if (sku.length != 10) {
        throw new sdk.UserVisibleError("Use the 10 digit public SKU.")
      }
      if (quantity < 1) {
        throw new sdk.UserVisibleError("The quantity must be 1 or greater.")
      }
      // Do the sync.
    },
  },
});
```

This approach works well in a doc, where the user can quickly determine if there is an error and adjust accordingly. However, when a sync table is used by an agent to [index data](../../../agents/indexing/), the sync takes places out of view and the user won't get that feedback.

For sync tables only, you can instead use the [`validateParameters`](../../../reference/sdk/core/interfaces/CommonPackFormulaDef/#validateparameters) function. This function is run before the `execute` function begins, and the agent setup process won't complete until it passes. In it you can validate all of the parameter values and return an object containing the full set of errors to show to the user.

```
pack.addSyncTable({
  // ...
  formula: {
    // ...
    validateParameters: async function (context, _, args) {
      let { sku, quantity } = args;
      let errors = [];
      if (sku.length != 10) {
        errors.push({
          message: "Use the 10 digit public SKU.",
          parameterName: "sku",
        });
      }
      if (quantity < 1) {
        errors.push({
          message: "Must be 1 or greater.",
          parameterName: "quantity",
        });
      }
      if (errors.length > 0) {
        return {
          isValid: false,
          message: "Please fix the errors.",
          errors: errors,
        };
      }
      return {
        isValid: true,
      };
    },
    execute: async function (args, context) {
      let [sku, quantity] = args;
      // Do the sync ...
    },
  },
});
```

## Reusing parameters

It's often the case that many formulas in a Pack use the same parameter. For example, the [Google Calendar Pack](https://coda.io/packs/google-calendar-1003/documentation) has many formulas have a parameter for the calendar to operate on. Rather than redefine the same parameter for each formula, it can be more efficient to define the shared parameter once outside of a formula and then reuse it multiple times.

```
const ProjectParam = sdk.makeParameter({
  type: sdk.ParameterType.String,
  name: "projectId",
  description: "The ID of the project.",
});

pack.addFormula({
  name: "Project",
  description: "Get a project.",
  parameters: [
    ProjectParam,
  ],
  // ...
});

pack.addFormula({
  name: "Task",
  description: "Get a task within a project.",
  parameters: [
    ProjectParam,
    sdk.makeParameter({
      type: sdk.ParameterType.String,
      name: "taskId",
      description: "The ID of the task.",
    }),
  ],
  // ...
});
```

Example: Math formulas

```
import * as sdk from "@codahq/packs-sdk";
export const pack = sdk.newPack();

// A "numbers" parameter shared by both formulas.
const NumbersParameter = sdk.makeParameter({
  type: sdk.ParameterType.NumberArray,
  name: "numbers",
  description: "The numbers to perform the calculation on.",
});

pack.addFormula({
  name: "GCD",
  description: "Returns the greatest common divisor for a list of numbers.",
  parameters: [
    // Use the shared parameter created above.
    NumbersParameter,
  ],
  resultType: sdk.ValueType.Number,
  execute: async function ([numbers]) {
    // Handle the error case where the list is empty.
    if (numbers.length === 0) {
      throw new sdk.UserVisibleError("The list cannot be empty.");
    }

    // Handle the error case where all the numbers are zeros.
    if (numbers.every(number => number === 0)) {
      throw new sdk.UserVisibleError(
        "The list must contain a non-zero number.");
    }

    let result = numbers[0];
    for (let i = 1; i < numbers.length; i++) {
      let number = numbers[i];
      result = gcd(number, result);
    }
    return result;
  },
});

pack.addFormula({
  name: "LCM",
  description: "Returns the least common multiple for a list of numbers.",
  parameters: [
    // Use the shared parameter created above.
    NumbersParameter,
  ],
  resultType: sdk.ValueType.Number,
  execute: async function ([numbers]) {
    // Handle the error case where the list is empty.
    if (numbers.length === 0) {
      throw new sdk.UserVisibleError("The list cannot be empty.");
    }

    // Handle the error case where the list contains a zero.
    if (numbers.some(number => number === 0)) {
      throw new sdk.UserVisibleError("The list must not contain a zero.");
    }

    let result = numbers[0];
    for (let i = 1; i < numbers.length; i++) {
      let number = numbers[i];
      result = Math.abs(number * result) / gcd(number, result);
    }
    return result;
  },
});

// Helper function that calculates the greatest common divisor of two
// numbers.
function gcd(a, b) {
  if (a === 0) {
    return b;
  }
  return gcd(b % a, a);
}
```

## Date range parameters

Parameters of the type `DateArray` are often used for date ranges, with the first date representing the start of the range and the second date representing the end. When a `DateArray` parameter is used in an action or sync table, the input box displays a date range picker to make it easier for the user to select a range.

These parameters also support a special set of [suggested values](#suggested) that represent date ranges relative to the current date. These are available in the [`PrecannedDateRange`](../../../reference/sdk/core/enumerations/PrecannedDateRange/) enumeration.

```
sdk.makeParameter({
  type: sdk.ParameterType.DateArray,
  name: "dateRange",
  description: "The date range over which data should be fetched.",
  suggestedValue: sdk.PrecannedDateRange.Last30Days,
});
```

## Recommended parameter types

The table below shows the recommended parameter type to use with various types of Coda columns and values.

| Type | Supported | Recommended | Notes | | --- | --- | --- | --- | | Text | ✅ Yes | `String` | Use `Html` or `Markdown` if the formatting is important. | | Link | ✅ Yes | `String` | | | Canvas | ✅ Yes | `Html` | Use `String` to discard formatting. | | Select list | ✅ Yes | `StringArray` | Works for both single and multi-value select lists. | | Number | ✅ Yes | `Number` | | | Percent | ✅ Yes | `Number` | Passed as a fraction. | | Currency | ✅ Yes | `Number` | Use `String` to get currency symbol. | | Slider | ✅ Yes | `Number` | | | Scale | ✅ Yes | `Number` | | | Date | ✅ Yes | `Date` | | | Time | ✅ Yes | `Date` | | | Date and time | ✅ Yes | `Date` | | | Duration | ✅ Yes | `Number` | | | Checkbox | ✅ Yes | `Boolean` | | | People | ❌ No | | Use `String` to get the person's name. | | Email | ✅ Yes | `String` | | | Reaction | ❌ No | | Use `StringArray` to get the names of the people that reacted. | | Button | ❌ No | | | | Image | ✅ Yes | `ImageArray` | Image column can contain multiple images. | | Image URL | ✅ Yes | `Image` | | | File | ✅ Yes | `FileArray` | File columns can contain multiple files. | | Relation | ❌ No | | Use `StringArray` to get the display name of the row(s). | | Table | ❌ No | | You can't pass an entire table, pass individual columns instead. | | Page | ✅ Yes | `Html` | |

______________________________________________________________________

1. The representation is known as ["serial number"](http://www.cpearson.com/excel/datetime.htm) and is common to all major spreadsheet applications. [↩](#fnref:1 "Jump back to footnote 1 in the text")[↩](#fnref2:1 "Jump back to footnote 1 in the text")
