# Introduction to Formsort

## What is Formsort?&#x20;

[Formsort](https://formsort.com/) is a form builder with the flexibility of code—built for teams who don’t want to wait weeks for small changes, who want the power to make bold updates that drive real business results, and who want full ownership over their outcomes.

### Where it sits <a href="#where-it-sits" id="where-it-sits"></a>

**Formsort is a fully-managed platform for building and hosting forms.**

Your team creates and manages form content using our online Studio—a purpose-built content management system (CMS) designed specifically for forms. From styling to data storage, everything is handled in one place.

We host your fully-branded forms on your subdomain or embedded directly within your site.

Formsort forms are production-ready and designed to integrate seamlessly into your stack—supporting the full lifecycle from rapid prototyping to high-traffic registration flows.

### Who Formsort Is For

* **Startups** looking to launch form flows quickly on infrastructure that scales with them.
* **Growing businesses** aiming to optimize form performance to reduce acquisition costs and increase conversion rates.
* **Enterprises** that want to build form flows for new business lines—without requiring dedicated teams.
* **Teams managing complex internal workflows** that rely on structured data collection.
* **Companies in regulated industries** that need to collect user data securely, without spreading it across multiple platforms.
* **Ambitious product managers and designers** ready to go beyond prototypes and launch form flows to real users.

Once set up, a single non-technical product or business owner can ship sophisticated form flows—without needing help from engineering or design teams.


# Formsort quickstart guides

**Formsort gives you tons of flexibility to build a personalized experience for your responders—and it’s easy to experiment along the way.** Whether you’re adding dynamic content, tailoring questions, or tweaking logic, there are lots of tools at your fingertips.

Explore the guides below to get started:

* [Adding content and collecting answers ](/core-concepts/formsort-quickstart-guides/add-content-and-collect-answers)
* [Capturing demographic data ](/core-concepts/formsort-quickstart-guides/capture-demographic-data)
* [Adding informational content](/core-concepts/formsort-quickstart-guides/add-informational-content)&#x20;
* [Templating your variables](/core-concepts/formsort-quickstart-guides/template-your-variables)
* [Adding conditional logic](/core-concepts/formsort-quickstart-guides/add-conditional-logic)&#x20;
* [Using conditional logic with Calculated and API variables](/core-concepts/formsort-quickstart-guides/using-conditional-logic-with-calculated-and-api-variables)
* [Ending your flows](/core-concepts/formsort-quickstart-guides/end-the-flow)&#x20;
* [Reviewing the variable schema ](/core-concepts/formsort-quickstart-guides/review-your-variable-schema)
* [Setting up Integrations ](/core-concepts/formsort-quickstart-guides/set-up-integrations)

{% hint style="info" %}
💡 Want even more inspiration? Check out our guides on signup flow best practices and client intake form best practices for tips on crafting a polished, high-converting experience.
{% endhint %}


# Add content and collect answers

Add questions, informational text, pictures, and videos to your flow.

### Flow **Content Tab**

![Formsort studio](/files/gblpVjuHUtkkRvOhd7S5)

* The content tab is where you will add **groups**, **steps**, and **questions** to your flow.
* There are lots of different question types you can choose from. Some question types require answers, while some are purely informational, like pictures or text. See our [component reference](/adding-questions-and-content/question-reference) section for descriptions of different question types.
* When you click on the group, step, or question, you will see the editor on the side. This is where you can manage the **logic** and **settings** of the element you are editing.
* You can use basic **markdown** within the question text itself to format the text style - like assigning header styles that you have created in your Typography menu, bolding or italicizing certain words, or adding links.
* Hit the **Live Preview** to see the flow update with your edits in real time.&#x20;

<details>

<summary>Markdown menu</summary>

#### Headers

```
# This is a big header
```

## This is a big header <a href="#this-is-a-big-header" id="this-is-a-big-header"></a>

```
## This is a medium header
```

### This is a medium header <a href="#this-is-a-medium-header" id="this-is-a-medium-header"></a>

```
###### This is the smallest header
```

**This is the smallest header**

#### Emphasis

```
*This text will be italic*
```

*This text will be italic*

```
_This will also be italic_
```

*This will also be italic*

```
**This text will be bold**
```

**This text will be bold**

```
__This will also be bold__
```

**This will also be bold**

```
_These **can** be combined_
```

*These **can** be combined*

#### Lists

```
* Item 1
* Item 2
  * Item 2a
  * Item 2b
```

* Item 1
* Item 2
  * Item 2a
  * Item 2b

```
1. Item 1
1. Item 2
  1. Item 2a
  1. Item 2b
```

1. Item 1
2. Item 2
   1. Item 2a
   2. Item 2b

#### Links

```
[Formsort](http://formsort.com/)
```

[Formsort](http://formsort.com/)

#### Blockquotes

```
As Dieter Rams said:

> Less, but better
```

As Dieter Rams said:

Less, but better

</details>

### Common question types

* [**Select question**](https://docs.formsort.com/building-flows/content-types/select)**:** responders choose from a selection of choices (you can [style your choices](https://docs.formsort.com/building-flows/content-types/select#styling-choices) by adding images or changing the presentation between buttons, dropdown, etc.)

![select question](/files/z4zWiKB3QRcsaIvLRC3K)

* [**Text question**](https://docs.formsort.com/building-flows/content-types/text): responders enter free text

![text question](/files/eYIUJYwKSnhoefIspmRg)


# Capture demographic data

Learn how to capture the most common types of demographic data from your responder.

## Capturing demographic data

It’s likely that you’ll want to capture some demographic information from your users, such as first and last name, contact information, and any other identifying information that may be relevant to your business. Let’s build a couple steps that will capture basic information from your users.

### Add a step

To start, we’ll want to add a [step](https://docs.formsort.com/building-flows/adding-content/steps). A step can be thought of as a container for questions, and will render as a single “page” that the responder will see.

![Click "add step"](/files/LtdyI7dH61LELFJjG9XK)

### Capture name

Now that we have the question container, let’s add some questions to it. The first thing we’ll want to know about a user is their name. [Text components](https://docs.formsort.com/building-flows/content-types/text) are the perfect tool for this since they allow users to enter text freely.

![](/files/b1fyKkkLm7BTaENteYFk)

{% hint style="info" %}
Check out [some examples](https://fineflows.com/elements/first-namelast-name) of how others capture first and last name in their flows.
{% endhint %}

The next two steps will apply to every question you create:

* Add a title to the question so the responder understands what the question is asking. As you can see in the gif below, this will automatically update the “Question Label” and the “Variable” sections in the question Settings.

![Add a question in the question title](/files/xnh5buIm8skpj2wdHW3X)

* Be sure to provide a useful variable name in the Variable section. This will be the name of the data value when an answer payload is sent. As a default measure, this box will automatically copy whatever you’ve put as the question title -- it’s probably necessary to rewrite this.

![](/files/UjJxF9TWmqYVmmctYiWt)

In the text component’s **settings**, you have the option to add **placeholder** **text** for the input box, to provide more clarity and direction for your users.

![Add a placeholder for the input box](/files/KW5qx2BwZB6ShQQdueLO)

### Capture phone and email

Next, we may want to capture their phone number and/or email. There are two components we can use for this: [phone](https://docs.formsort.com/building-flows/content-types/phone) and [email](https://docs.formsort.com/building-flows/content-types/email). Pretty simple!

#### Phone

Add a phone component and, same as the text component, be sure to title the question and rename the variable.

![Create a phone component](/files/NhSubkzeBGjVSsk5J8T5)

Since the user is entering information into an input box, placeholder text can also be used here. Input mask will be on by default, which will automatically format the user’s response as (123)456-7890 . This setting can be turned off.

#### Email

Now, add an email.

![Add an email component. Be sure to rewrite the variable](/files/oDFdDTiHIm2m10mapTcx)

“Warn about common domain typos” is a setting that will be on by default, and will provide a warning for users if it detects an error in the email domain (e.g. gnail.com vs gmail.com).

{% hint style="info" %}
It is generally good practice to give context as to why contact information is being collected, and what you plan to do with this information. A [tooltip](https://docs.formsort.com/building-flows/adding-content/adding-content#info-text-and-tooltips) is a great way to add this additional context to a question, without adding more components to a page. Simply toggle "**Has info text?**" on, and add the text required to inform your users. &#x20;
{% endhint %}

### Capture date of birth

Oftentimes we may need to capture a user's date of birth - either to follow up for a birthday, or to tailor an experience [based on the user's age](https://docs.formsort.com/building-flows/schemas/calculated-answers#get-age-from-dob) using a [calculated variable](https://docs.formsort.com/building-flows/schemas/calculated-answers). The [date component](https://docs.formsort.com/building-flows/content-types/date) is the tool of choice here, which can be used for capturing more than just dates of births!&#x20;

It's up to you where in the flow you'd want to put this question: you can add it to the list of demographic questions on the first step, or - if you'd like the flow to feel more conversational - you can add it to it's own step.&#x20;

![Adding a Date component to a new step](/files/xpQWi3jt7DmFvCtynZUS)

There are a number of options to be toggled with this component, and the list may be worth experimenting with if a date component is going to be used for any other reason. For the purposes of capturing someone's date of birth, it is suggested to at least toggle on "**Only allow dates in the past?**".

### Capture address

Since we've already captured some of the basic contact information for our responder, we might want to capture their address. The [address component](https://docs.formsort.com/building-flows/content-types/address) can be used for this, and will automatically provide the fields required for a complete address.

First, we’ll probably want to create a new step for this, since the address component may take up some real estate if "**Use single field?**" is not enabled. It also helps the user *feel* the progression in the flow, and helps to break up the sense of monotony that comes with filling out a long list. &#x20;

![Add a Step, and an Address component](/files/4JqGB6uYMnEnSUFhK1NF)

There are several options for an address component, like formatting for [International addresses](https://docs.formsort.com/building-flows/content-types/address), or [Validating US addresses](https://docs.formsort.com/building-flows/content-types/address) using a USPS integration. It is worth reviewing the detailed [address docs page](https://docs.formsort.com/building-flows/content-types/address) to get a better understanding of what you can do here.


# Add informational content

Add text, photos, and videos to your flow.

Not all of the content you add to your flow needs to be questions you are asking the responder! You also may want to add text or pictures to your flow that are **purely for informational purposes**. Sometimes this is to educate the responder about what you offer, or to provide more information about why they are disqualified from your service.

See the types of informational content you can choose from when you click "**Add Question**" and scroll below "**Content**".

![](/files/vaituXyoy7WA5Cn5AcaH)

## Common types of informational content:

### Add text and pictures to your steps

You can add add text and pictures before, between, or after the questions you are asking.&#x20;

<figure><img src="/files/lJuNApRMaJYZqQ9dT9m7" alt=""><figcaption></figcaption></figure>

### Welcome page

![](/files/gcmKbCrmNYZMFMtJzMYY)

![](/files/X98j4GSHrbi5piiqLMyA)

### **Educational steps**

Share facts or statistics that are relevant to your product or service to encourage the responder to continue the flow.

![](/files/feAbXu6R4ea2C9lLTw6a)

### Transition steps

Tell a responder in the middle of the flow what's about to happen next.

![](/files/jUgWJNYfqFGLXNUKRqXP)

![](/files/w7gzAB2PO5yKfiTRbCvM)


# Template your variables

Refer back to answers already given in your flow.

Sometimes you want to refer back to answers already collected in your flow and display those answers to a responder. A common use case of this is calling the responder by their first name, or reminding them of a choice they had selected in a previous question. This is called **templating variables**.

{% embed url="<https://www.loom.com/share/b476fa30487142e796c27874ae5d5918>" %}

### **How to do it in the Studio**

* In this flow, I asked a **Text question** to ask the pet’s name, and I named the variable `pets_name`

![](/files/rWUmGRm7g3ASA5eZrCvu)

* In my next two steps, I insert the answer variable `pets_name` from that question in double brackets.

![](/files/Jawcu0Z5VCZUZP7t6IMn)

You can always see a list of the variables you have available to you in the side bar under variables:

![](/files/qjoDgEE1ziO6o8Vh4awC)

### How it looks to a responder

![](/files/LqWF1kJwsILPr1Vn2dTr)

### Other resources

If you need to reformat the variables you collected, please refer to [Templating formatting functions.](/variable-templating#template-formatting-functions)

Use templated variables in [redirect URLs](/variable-templating#redirect-urls) and [image URLs](/variable-templating#inserting-answer-values-into-content).


# Add conditional logic

As a responder is going through your flow, you’ll likely learn information about them that will prompt you to show them a different experience. You can take responders through different paths of your flow by applying **conditional logic** at the question level, the step level, or the group level.

{% embed url="<https://www.loom.com/share/a99281beebb548d99a5d9d53efc09980>" %}

### How to do it

In Formsort, you add conditional logic to the question, step, or group that you want to be displayed to the responder who matches that criteria.

Take this design below. I want cat and dog owners to be shown the green step because they are eligible for my services, but other pet types to be shown a disqualification screen.

![](/files/Ck8lQYdAMl3tMURrovlN)

***

### **Disqualifying Responders (Example)**

Sometimes, you’ll need to determine whether a responder meets your eligibility requirements—and if they don’t, stop them from proceeding through the rest of your flow. This is a common use case for conditional logic, and it allows you to tailor the experience for ineligible responders while collecting only the information you need.

Before building your disqualification logic, consider the following:

* What are the qualification criteria for my service?
* What information do I still want to collect from disqualified responders who may qualify in the future?
* Where should disqualified responders go next—should they be redirected, emailed, or shown a custom message?

#### How to Disqualify Responders

1. **Determine your qualification criteria.**\
   Common criteria include age, location, or specific answers to questions like medical history or income range.
2. **Collect the right data.**\
   Add the necessary steps and questions to your flow so you can evaluate each responder. For example:
   * Use a **select question** with a **choice library** to list all 50 U.S. states.
   * Ask a **yes/no question** to confirm a qualifying condition (e.g. “Are you 18 or older?”).
   * For more accuracy, calculate age from the responder’s date of birth using a calculated variable. Learn how to calculate age from date of birth →
3. **Add a disqualification step or group.**\
   Create the step (or group of steps) that disqualified responders will see. This might be a simple message ending the flow, or a series of steps to collect follow-up info or offer alternative next steps.
4. **Apply conditional logic.**\
   Add logic to the disqualification step or group so it only shows for responders who **do not meet your criteria**. Place this logic at the point in the flow where disqualification should occur.

   > Tip: If you're redirecting responders at this point, make sure they won’t hit later steps unintentionally.
5. **Configure the end of the flow.**\
   Once responders reach the disqualification step, consider what you want to happen next:
   * Redirect to a help center or FAQ
   * Show a friendly message explaining why they aren’t eligible
   * Offer an email sign-up to notify them when they become eligible\
     Learn more about finalizing flows →

***

#### Advanced Methods for Disqualification

If your eligibility logic is complex or subject to change, you can streamline it with more advanced tools:

* **Use a** [**calculated variable**](/response-data-collection-and-management/variable-schema/calculated-answers)\
  Write a function that determines eligibility in one place, referencing multiple values (e.g. check if the responder’s state is in a list of eligible states).
* **Call your backend with an** [**API variable**](/response-data-collection-and-management/variable-schema/api-answers)\
  Send responder data to your server and receive a response indicating whether the user is eligible. This allows you to centralize and update logic outside of the Formsort editor.


# Using conditional logic with Calculated and API variables

Conditionally render questions with advanced calculations

There are plenty of reasons to conditionally render questions in your flow - that is, either present or hide questions because of specific conditions. Some factors may be based on the information your users provide in their answers, like rendering certain questions based on age, or it could be based on factors external to that, like a marketing team hiding an offer that is out of season.

Fortunately, the synergy between Formsort’s conditional logic and API and Calculated variables allows us to automate when questions should be available to the users filling out our forms. But, like anything, this takes a little bit of configuring.

## Using calculated variables to hide questions

The power of calculated variables lies in the flexibility they provide by using Javascript functions (in Typescript format). This requires a little bit of JS/TypeScript familiarity, but it’s a powerful tool!

Below, we’ll go over a couple examples of using Calculated Variables for creating logic.

### Ex 1:

In this example function, we’ll return a Boolean true/false value based on whether or not the user in inside or outside the hours of operation, which are 9am - 5pm, M-F. This will require no direct input from the user, but instead will draw the local time from their browser.

In the variant we’re going to create this logic in, let’s head to our **Calculated Inline** tab under **Variables**, and then into the Calculated variable editor.

<figure><img src="/files/kCtFErULNJd5dVguQ3SU" alt=""><figcaption><p>Add the calculated variable</p></figcaption></figure>

<figure><img src="/files/NzP9yM6HtOQXscVjPFKl" alt=""><figcaption><p>The Calculated variable editor</p></figcaption></figure>

Let’s give the variable a name - we’ll call this one `dayparting_offers`, change the Variable Type to `Boolean` so Formsort knows to expect a T/F return, and drop the code into the **Getter Function Body**.

<figure><img src="/files/7D61vkpuYVDSEKQlGb6W" alt=""><figcaption><p>Setting up the variable</p></figcaption></figure>

{% hint style="info" %}
You can check the function using the “Test API variable” section at the bottom of the editor.
{% endhint %}

Here is a copy of the code we’re using:

<pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">function myFunction(): boolean { // readonly line
<strong>  var now = new Date();
</strong>  var hours_in_GMT = now.getUTCHours();
  var days_in_GMT = now.getDay();
  var days_in_desired_timezone = days_in_GMT;

  //time difference to GMT
  //In this case EST is 5 hours behind GMT
  var time_difference_to_desired_timezone = -5;

  var hours_in_desired_timezone = hours_in_GMT + time_difference_to_desired_timezone;

  //if its past midnight in GMT, make sure to adjust
  if (hours_in_desired_timezone &#x3C; 0 ) { days_in_desired_timezone = (((days_in_desired_timezone - 1) +7) % 7);
 hours_in_desired_timezone = hours_in_desired_timezone + 24;
<strong>  }
</strong><strong>  
</strong>//set the parameters for open hours using military time where Sunday is 0
<strong>  var start_time;
</strong>  var close_time;

  if ([1,2,3,4,5].indexOf(days_in_desired_timezone) > -1) {
  start_time = 9;
  close_time = 17;
  }

  else {
  start_time = 0;
  close_time = 0;
  }

  if (hours_in_desired_timezone &#x3C; close_time &#x26;&#x26; hours_in_desired_timezone >=   start_time) {
  return true;
  }

<strong>  else {
</strong>    return false;
  }
}    
</code></pre>

Now, lets use this variable to create logic on steps we only want available inside the hours of operation.

* In **Content**, head to the step we’re putting the logic on. Here, it is going to be Step 1.
* Click **Logic** → **Show conditionally** → **Add condition…**

<figure><img src="/files/BXuM547bGweHCJuBGuxQ" alt=""><figcaption><p>Navigate to Step logic menu</p></figcaption></figure>

* Select our variable `dayparting_offers` in the variable logic editor, and set it equal to **true**. **Save condition**.

<figure><img src="/files/0QDvglPH0hNxlW2uBkpp" alt=""><figcaption><p>Adding the step logic</p></figcaption></figure>

* You can repeat this on Step 2 as well, but instead set the variable value to **false**.

<figure><img src="/files/2jNDcv6qVKYTQUVe0FQl" alt=""><figcaption></figcaption></figure>

* You should see that the step logic will be visible on the steps we’ve set it on.

Now, depending on your local time, you will see either Step 1 or Step 2. Try it out in Live Preview!

### Ex 2:

This next example will hide a step based on our user’s age. This will require us to take the user’s `date of birth` response as an input to our calculated variable, run a function that will return an `age`, and set our step logic based on that age.

{% hint style="info" %}
Setting logic against a [date object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) can be tricky and unreliable. If you opt to filter users based on their age, it’s recommended to use this method, which sets logic against a [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number).
{% endhint %}

First, let’s create a question to capture the user’s dob.

* At the desired step, choose “Add question” and select the [Date](https://docs.formsort.com/building-flows/content-types/date) component. Set the **Variable** name (we’re using `dob`).

<figure><img src="/files/2OFRm8j0xZvNprQvkrk7" alt=""><figcaption><p>Naming our variable</p></figcaption></figure>

Now, we can take this input and dynamically create an age, depending on the user’s answer.

Head to **Calculated Inline** in our **Variables** section, and “Add calculated variable”.

* Give the function a name (we’ll call this one `age`) and set the **Variable Type** to **number** so Formsort knows to expect an integer.
* Toggle “**Uses other variables**”, and select `dob` from your variable list.
* Drop in the function

<figure><img src="/files/4vhvsLTzmSLVuv7KuYz8" alt=""><figcaption><p>Set the dependent variable</p></figcaption></figure>

{% hint style="info" %}
You can check the function using the “Test API variable” section at the bottom of the editor.
{% endhint %}

Here is a copy of the code we’re using:

{% code overflow="wrap" %}

```javascript
// dob is taken from our "Uses other variables" input
function myFunction(patient_dob: string): number { // readonly line
  const ageDifMs = Date.now() - (new Date(dob)).getTime();
  const ageDate = new Date(ageDifMs);
  return Math.abs(ageDate.getUTCFullYear() - 1970);
}
```

{% endcode %}

Now, lets use this variable to create logic on steps for users *at or above a certain age*.

* In **Content**, head to the step we’re putting the logic on. Here, it is going to be Step 3.
* Click **Logic** → **Show conditionally** → **Add condition…**

<figure><img src="/files/JYahFmczsQ4vNParRKpG" alt=""><figcaption><p>Adding Step logic</p></figcaption></figure>

* Select our variable `age` in the variable logic editor, and set it to **greater than 17**. **Save condition**.

<figure><img src="/files/pClUJ2rWUdWGHh8hysWJ" alt=""><figcaption><p>Creating the Step logic</p></figcaption></figure>

<figure><img src="/files/i9C9VjXJZc5RObgk9xLl" alt=""><figcaption><p>The inline Step logic indicator</p></figcaption></figure>

Now, if our user’s `dob` puts them at an age that is younger than 16, they will skip Step 3 and move right on to Step 4. Try it out in Live Preview!

For more information on Calculated Variables and the options in our Calculated Variable editor, see our [docs](https://docs.formsort.com/building-flows/schemas/calculated-answers)!

## Using API variables to hide questions

[API variables](https://docs.formsort.com/building-flows/schemas/api-answers) make it possible to fetch data from a server that returns JSON responses. This server can be one you own, or it can be a 3rd party server that has information you need. As long as it returns a JSON response, and *you* can provide authentication into it (if necessary), we can ping it.

Once the API call returns the information we need, we can use the response to create logic on the steps we want to conditionally render.

### Example

In the example below, we will [**template**](https://docs.formsort.com/building-flows/adding-content/using-answers-in-templates?q=templat) a user’s response into our call to the [World Time API](http://worldtimeapi.org/) to determine their timezone, and thus determine which questions to hide.

First, let’s set up a question to find out where the user lives.

* Head into your variant and create the question. We’re using a [select question](https://docs.formsort.com/building-flows/content-types/select) here to provide a list of possible responses for our user.

<figure><img src="/files/09Lk7yGEUrwerkiAsYP3" alt=""><figcaption><p>Creating our Select question</p></figcaption></figure>

* Give the variable name something recognizable. This question will have the variable name `user_city`.

<figure><img src="/files/fCdQD8sG6q1DeXQl27yG" alt=""><figcaption><p>Naming the variable</p></figcaption></figure>

Now, let’s set up the API variable to return the time zone, based on the user’s answer.

* Head into **Variables** → **API lookups** and then “**+Add api variable**”.

<figure><img src="/files/DuqbUwqWaduwGjI9tIly" alt=""><figcaption><p>Adding the API variable</p></figcaption></figure>

* Add a variable name, and set the [**variable type**](https://docs.formsort.com/building-flows/schemas/api-answers#variable-type). Variable type is the expected data type we’ll be receiving from the API response (i.e. string, number, boolean, object, array).
* If we call this particular API with the `area` and `region` specified in the URL path, we will get an object return with the timezone information about the region.

<figure><img src="/files/xqDckL1Px8lMcB1PZcba" alt=""><figcaption><p>Example call with area: America and region: Denver</p></figcaption></figure>

* Now that we know what kind of return to expect, we can add the variable value of `user_city` to our URL path, to make the API call dynamic.
  * Click “**Variables…**”, select `user_city`, and paste the variable into the URL path.

<figure><img src="/files/438PfukSFc1V04qtnXyS" alt=""><figcaption><p>Open the available variables menu</p></figcaption></figure>

<figure><img src="/files/5GBA1Hlt6PxQREA4C5Rq" alt=""><figcaption><p>Grab the variable we want</p></figcaption></figure>

<figure><img src="/files/xaUZjPfCuSsh6IxMcHPQ" alt=""><figcaption><p>Paste the variable into URL path</p></figcaption></figure>

* Now, we can double check the API call using the **Test API variable** section to make sure we’ll get the response we want. Select a value from the **USER\_CITY** dropdown, then hit “Send test request”.

<figure><img src="/files/LmNUXATnByCbb0shD0WX" alt=""><figcaption></figcaption></figure>

* Looks like we have a matching result! All we want from this call is the value for the `abbreviation` field in our return. Let’s use the **JSON Accessor** in **Result processing**.

<figure><img src="/files/Osjfv9CAKbWQYDkbgI8f" alt=""><figcaption><p>Error? </p></figcaption></figure>

* We returned an error! The API editor is still expecting an object, but the JSON accessor is returning string value. Let’s adjust our expected **variable type** and re-test.

<figure><img src="/files/UWrRPWYARdZywLy29Sqv" alt=""><figcaption><p>Success! Denver is in MST</p></figcaption></figure>

We have the question we need to grab our user’s city, and we have confirmed that our API call will work with that information. All that’s left to do is create logic on the questions we want to hide, depending on the user’s time zone.

* Let’s create a Step that will not be available to users in “MST”. On that step, head to **Logic**, toggle **Show conditionally** on, and create the logic required. **Save condition**.

<figure><img src="/files/WpT9WBvB03tpzlIIjtmZ" alt=""><figcaption></figcaption></figure>

* Now, when a user answers “Denver” to question 1, they will not see question 2, and will instead skip to question 3. Try it out in Live Preview!

This is one simple API variable use case, but there are a lot of different ways the API variable editor can be configured, like using a mapping function to parse API responses, adding a URL fallback in case the API is unavailable, or making the API fetch conditional on some other conditions.

Make sure to see our [API Variable documentation](/response-data-collection-and-management/variable-schema/api-answers), or, if you’re like us, play around with it and see what you can do!


# Add a scheduling option

If your signup flow includes a scheduling element, there are a few different ways you can direct your responders to select a time.

### Method 1: Add a redirect

Redirect out of the flow to a calendar app (like Calendly or Acuity) using a[ Redirect](/redirects-and-endings#redirects).​

How to do it:

* When the responder hits a certain step, they can be automatically redirected, or they can click a Next/Submit button to be redirected to a new URL
* You can have conditional redirects — so some responders can go to different URLs depending on specific criteria
* You can add[ query params](/variable-templating#redirect-urls) to the redirect URL so you can pass data to the scheduling tool
* Note that a redirect finalizes the flow, so you’ll have to keep that in mind if you are analyzing flow analytics and/or want them to be redirected back into the flow to finish additional steps. In the case where you want to have the responder continue the flow, you will need to create a different second flow to redirect them into.

Some scheduling applications have their own redirects after an appointment or meeting is scheduled!

This makes it possible to redirect the responder back into a new Formsort flow after they've booked something. You can use [external variables](/response-data-collection-and-management/variable-schema/external-answers) to bring in any information about the appointment the responder booked.

{% hint style="info" %}
Formsort does not currently support redirecting users back in to the same flow that the user was redirected out of. This is because redirecting a user out of a flow will finalize the flow at the step the user is redirected, and users cannot return to finalized flows.&#x20;

For this reason, we suggest using redirects when it makes sense to finalize one flow, and then redirect the user back into a **new** flow if they need to continue.&#x20;
{% endhint %}

### Method 2: Use a custom question

You can also choose to embed a scheduling app directly into your flow as a[ custom question](/adding-questions-and-content/question-reference/iframe-question). A custom question is hosted by you in an iFrame.

The benefits to this is that you can keep the responder journey contained to the flow, so it can feel like a more cohesive experience.

How to do it:

* Add a[ custom question](/adding-questions-and-content/question-reference/iframe-question) to render a calendar app in an iFrame
* You can create multiple conditional custom questions, or every responder can see the same one.
* Use the editor on the right sidebar to customize the question further.
* Use the [custom question API](/adding-questions-and-content/question-reference/iframe-question) to collect answers

{% hint style="warning" %}
Note that you'll need to use the[ custom question API](/adding-questions-and-content/question-reference/iframe-question) in order to allow a responder to proceed through the flow and collect their answers. Otherwise, Formsort has no way of knowing that the question has been completed.

If this doesn't work for your use case, mark the question as **optional**.
{% endhint %}

{% hint style="info" %}
Looking to embed Calendly via a custom question? Check out [this example](https://github.com/formsort/custom-question-examples).
{% endhint %}

### ​Method 3: Return answers via API choices on a select question

If you have an API of “available times”, you can dynamically generate those choices in a select question.

1. Add a[ Select](/adding-questions-and-content/question-reference/select) question
2. Navigate to Choices > Load choices dynamically
3. Choose External API that displays the choices (see more about [providing choices via API](/adding-questions-and-content/question-reference/select#dynamic-choice-loading))


# End the flow

Manage how your responders exit your flow.

Responders might end the flow at different points, depending on the experience you have built for them.&#x20;

There are a few different ways to end the flow for a responder. Remember that you can choose the behavior for responders that partially complete a flow and then return to it in [returning responder behavior.](/form-behavior-settings/variant-settings/returning-responder-behavior)

### If the responder exits the flow early:

If you have conditional logic, you may want to end their flow earlier. A common use case for this is when the responder has given you information that disqualifies them for your product or service.

#### Here's how to set up a disqualifying step:

1. Create a step that tells the responder that they are not eligible.

![](/files/rlZEMxAZbNpkyM8Lkonf)

2. Create **conditional logic** on the step so that only the disqualified responders see it. Make this step **finalizing** for them.

![](/files/Qh3uNTeoT62J2miVbkGh)

{% hint style="info" %}
**Pro tip**: you can use [markdown](/styling/customizing-appearance/typography#markdown-styling-support) to add a link to your homepage so users have a place to go after finalizing the form!&#x20;
{% endhint %}

### If the responder completes the entire flow:

* Show them a thank you screen that indicates that the flow is completed. You can configure your confirmation text in **Settings > Strings > Confirmation Text**

![](/files/0tgramMrlp3wC3ar9Rsg)

{% hint style="info" %}
The Settings tab also lets you set behavior for returning responders so you have more control of what happens when responders return back to a partially completed or completed flow.

Learn more about returning responder behavior [here](/form-behavior-settings/variant-settings/returning-responder-behavior).
{% endhint %}

* Show them a finalizing step<br>

![](/files/tybDZhImc7y9cCGImDcK)

* Redirect them to a different page at the end, such as your FAQ or a testimonials page. See more about redirects [here](https://docs.formsort.com/building-flows/redirects).

![](/files/h9PBL2U0le8zNedqHAA7)

{% hint style="success" %}
To make the redirect experience less abrupt, you can also add in a gif or animation to the last step the responder sees. Check out some [examples of loading images](https://fineflows.com/elements/loading-indicator).&#x20;
{% endhint %}


# Review your variable schema

Manage the data you are collecting from responders and sending to your integrations.

As responders go through your flow, you will build up a **schema** of answer variables from them — either through answers they have given directly, or other methods. You can view and edit all of your variables in the Schema tab.

<figure><img src="/files/cG548xc181QU2nL59DJy" alt=""><figcaption></figcaption></figure>

### Types of variables:

* [Variables from questions:](/response-data-collection-and-management/variable-schema/variables-from-questions) Variables you collect from the responder directly when they answer a question. Examples: `first_name`, `dog_breed, pet_type`
* [Externally provided](/response-data-collection-and-management/variable-schema/external-answers): Variables you can pass through the URL of the flow. This can be helpful to add in information about the responder that they might not be able to answer directly, like their unique ID, or Common examples: `UTM source`, \`
* [API lookups:](/response-data-collection-and-management/variable-schema/api-answers) you can fetch a new answer from a server that returns JSON responses. This lookup can even use other variables collected with the flow as inputs or parameters. Examples are calling your own healthcare provider database, finding the nearest store location, or looking up current pricing.
* [Calculated inline](/response-data-collection-and-management/variable-schema/calculated-answers): Allow you to define new answers based on other answers present within the flow, by writing simple javascript functions that are evaluated within Formsort. A common example is calculating age from a responder’s inputted date of birth (see [here](/response-data-collection-and-management/variable-schema/calculated-answers#get-age-from-dob))
* [System library](/response-data-collection-and-management/variable-schema/system-library-answers): Variables you can collect about the responder's experience with your flow. Examples include: `current_step_id, is_mobile, is_returning_responder`.


# Set up integrations

Send your flow data to analytics or data storage tools.

From the **Integrations** tab, you can configure where you were will send data, the shape of submission data, and the frequency with which data is sent. &#x20;

{% hint style="info" %}
For any changes in the Integrations tab to be reflected in your flow/variant, you must republish after changes are made.
{% endhint %}

## Sending analytics events

[Amplitude](/integrations/integration-reference/amplitude), [Google Analytics](/integrations/integration-reference/google-analytics), [Google Tag Manager](/integrations/integration-reference/google-tag-manager), and [Segment](/integrations/integration-reference/segment) can be configured to receive [analytics events](/integrations/getting-data-out/analytics).

The analytics destinations can also receive answer payloads. You must opt-in to this when configuring them in the integrations editor by toggling "Send answers" on.

## Sending answers

[BigQuery](/integrations/integration-reference/bigquery), [Redshift](/integrations/integration-reference/redshift), and [Webhooks](/integrations/integration-reference/webhooks) can be configured to receive answer payloads. At the [submission frequency](/integrations/getting-data-out/submission-frequencies) that you define, the entire answer payload will be sent to the integration.

Due to the distributed nature of our system, there are a couple of factors you should consider when setting up [BigQuery](/integrations/integration-reference/bigquery), [Redshift](/integrations/integration-reference/redshift), and [Webhooks](/integrations/integration-reference/webhooks) integrations:\
\
1\. Ordering of answer submissions is not guaranteed. This can be particularly noticeable when you use a submission frequency of “Every Step”. For example, if steps are completed in quick succession, ordering of your submissions may not exactly match completion order. \
\
2\. While we guarantee at-least-once delivery of all answer submissions, regardless of frequency, **we cannot guarantee only-once delivery**. All integration targets should be designed with idempotency in mind. Receiving a duplicate answer submission should not adversely affect processing.

{% hint style="info" %}
**When does passed in data get sent?**

Answers passed in via URL parameter or POST body will be included in all answer payloads regardless of the step order.
{% endhint %}

{% hint style="info" %}
**Sending data to a Google Spreadsheet**

Spreadsheets are not databases, and have a slew of problems when you treat them as such, however, we recognize their usefulness, particularly when getting a new project up to speed.

Formsort has a first-party integration with Google Sheets. See more in our [Google Sheets documentation.](/integrations/integration-reference/google-sheets)
{% endhint %}

## Viewing and testing webhook payloads

When you set up a webhook, you can view a sample answer payload in the Webhooks tab once you select a submission frequency. More about submission frequencies [here](/integrations/getting-data-out/submission-frequencies).

{% embed url="<https://www.loom.com/share/91b67af476504342935d938d7e6014b7>" %}

## Receiving uploads

[Amazon S3](/integrations/integration-reference/s3) and [Google Cloud Storage](/integrations/integration-reference/google-cloud-storage) can be configured to receive uploads, such as from [file upload](/adding-questions-and-content/question-reference/file-upload), [image upload](/adding-questions-and-content/question-reference/image-upload), or [signature](/adding-questions-and-content/question-reference/signature) questions. The answer itself will contain the S3/GCS URL of the uploaded asset.


# How data works in Formsort

Formsort is designed to help you **collect responder data** and reliably **send it to your own databases, tools, and workflows**. While we offer secure, built-in response storage, our goal is always to give you full control over your data.

### We store responses by default

As soon as someone begins interacting with your form, their answers—including partial ones—are automatically saved and visible in the **Responses** tab of the Formsort studio. These responses remain accessible unless archived.

* All answers are **encrypted** and **HIPAA-compliant**.
* **Enterprise teams** can opt out of storage entirely, relying solely on external integrations.

### Send answers to your own systems

In addition to storing data, Formsort allows you to **forward responses to third-party destinations** via:

* Our native integration ecosystem
* External API lookups
* Dynamic URL parameters and cookies

This makes it easy to fit Formsort into your broader data ecosystem.

### Incomplete sessions are stored separately

To support session continuity, we also store partial form-fills in a separate, temporary system—so responders can return and finish later.

* These sessions are saved in an **encrypted S3 bucket** and retained for **21 days** from the time of last submission.
* Each time a responder revisits a form and submits new data, the 21-day clock resets.
* This mechanism is separate from your saved responses and can be used even if response storage is disabled.

***

### HIPAA-compliant answer storage

All stored responses are **encrypted and comply with HIPAA standards**. Formsort does not delete responses, but users can manage response access and visibility through archiving.

{% hint style="info" %}
Enterprise users can disable response storage - Formsort will not store your form responses on the platform. Instead, responses are held in a secure, encrypted S3 bucket for 21 days only, after which they are permanently deleted. Use external integrations to manage your long-term data storage.
{% endhint %}


# Responder UUIDs

Identifying individual responders using a unique ID.

A **responder UUID** (Universally Unique Identifier) is a unique value used to identify a user's browser session. This UUID is automatically generated by Formsort when a user loads a form for the first time and is stored in their browser’s `localStorage`.

By default, the UUID persists across visits, allowing Formsort to:

* Recognize returning users,
* Restore saved answers,
* And tie together session-specific data like events and submissions.

### When Is a Responder UUID Generated?

You don’t need to provide a UUID manually. Formsort automatically generates a `responderUuid` during a responder’s first session.

If you’d like to generate a **new UUID** every time someone visits your form (even in the same browser), enable **Start each session as a new responder** in your variant settings.

### Manually Setting a Responder UUID

While auto-generation works for most use cases, you can also provide your own responder UUID. This is useful if:

* You already have a unique identifier for a user (e.g. a user ID or session ID),
* You want to force-load a specific session tied to that identifier.

#### Two ways to set the responder UUID manually:

1. **Via the URL parameter** `responderUuid`**:**

```
https://<FLOW-URL>?responderUuid=123e4567-e89b-12d3-a456-426614174000
```

2. **Via HTML POST body:**

```
<input name="responderUuid" value="123e4567-e89b-12d3-a456-426614174000" />
```

{% hint style="info" %}
:bulb:You can also use URL parameters to set initial answers in the form—see our full guide for more.
{% endhint %}

#### Important notes:

* `responderUuid` must be a valid UUID string in the format:\
  `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`\
  (e.g., `123e4567-e89b-12d3-a456-426614174000`)
* Force-loading a session with `responderUuid` **will not work** if:
  * **Start each session as a new responder** is enabled, or
  * The user’s previous session has been finalized.

### Making the responder UUID available as an answer

You can make the `responderUuid` available as an **answer variable** in your flow by adding it from the **System Library** into your form schema.

<div align="left"><figure><img src="/files/euRFQ1e4F0ZO0z0zdxyM" alt="" width="563"><figcaption><p>The System Library menu</p></figcaption></figure></div>

Once added:

* It can be templated into redirects or API calls, or anywhere templating is allowed.
* You can reference it like any other variable in your flow.

{% hint style="info" %}
Learn more about [variable templating](/variable-templating) for dynamic usage of answer data in your form logic.
{% endhint %}


# Understanding flows

### Key Concepts

Formsort uses a few foundational concepts that work together to help you build, manage, and optimize form flows. Here’s how they connect:

#### Flow

A **flow** is a full form experience, like a signup, onboarding, or lead capture journey. Think of it as the container for everything the user sees and interacts with. Each flow lives in the Studio and serves a distinct purpose.

**Example:**

* `signup-flow`
* `onboarding-flow`

<div align="left"><figure><img src="/files/owaPV59V2cceWoVmR92M" alt=""><figcaption></figcaption></figure></div>

#### Variant

A **variant** is a version of a flow. Every flow begins with a **Main** variant, but you can duplicate and modify variants to test different user experiences.

Variants all aim to achieve the same goal but may include different steps, logic, or copy.

<div align="left"><figure><img src="/files/hc1z4uO2klhXD8MaUo1i" alt=""><figcaption></figcaption></figure></div>

#### Group

**Groups** are optional, but useful for organizing complex flows. A group is a collection of **steps** within a variant, helping you logically separate sections of a form.

{% hint style="info" %}
Groups are not visible to responders—they’re for internal organization only.
{% endhint %}

<div align="left"><figure><img src="/files/AgrFNxVMZzB7uFiGnyEV" alt=""><figcaption></figcaption></figure></div>

#### Step

A **step** is a single "page" of your flow. It organizes **questions** into digestible sections, guiding responders through the form experience.

Steps live inside groups (or directly inside a variant if no groups are used).

<div align="left"><figure><img src="/files/3oMZIa7tDPGNdhrlGbst" alt=""><figcaption><p>A step with a question</p></figcaption></figure></div>

#### Question

**Questions** are the form fields that collect user input and stores them into **variables** —like name, email, or file uploads. You can drag-and-drop questions to reorder them, move them across steps, or display them side-by-side.

{% hint style="info" %}
Example: collect first name and last name in a single row.
{% endhint %}

#### Variable

A **variable** is a reference to the **responder's** answer for a specific **question** in your form, used to store and retrieve their responses within Formsort and your external systems. In addition to question variables, other types of variables exist, such as those provided externally, calculated inline, or retrieved through API lookups and system libraries — all of which are automatically saved as part of your form responses, and sent to your data integrations.

{% hint style="info" %}
Variables have a wide range of configuration options—check out the section [**Understanding variables**](/adding-questions-and-content#understanding-variables) below to explore them all in detail.
{% endhint %}

#### Responder

A **responder** is your end user—the person going through your flow and submitting answers. All session data and responses are tied to the responder.

#### Conditional logic

Conditional logic allows flows to adapt dynamically to responder inputs, enabling a personalized experience without the need to create multiple flows. It can be applied to control the visibility and behavior of **questions**, **steps**, **groups**, and redirects.&#x20;


# Versioning in Formsort (Deploying)

## Deploying Your Flow

Before your responders can interact with your form, it needs to be **deployed**. Deployment is the final step that makes your latest changes live and accessible, and also creates a [variant revision](/core-concepts/versioning-in-formsort-deploying/variant-revisions) of your form.&#x20;

{% hint style="info" %}
:bulb:Deploying a variant to any environment (e.g., Production, Staging) creates a unique 36-character `variant_revision_uuid`. This UUID represents the exact state of the variant at the time of deployment and can be found in the **History** tab of the variant.
{% endhint %}

In this section, we'll walk through how to deploy your form, and cover the configuration options available during deployment—like selecting environments, adding deployment notes, and managing variant revisions.

### Deployment Workflow

To make your flow updates visible to responders, click **Deploy** at the top right of the flow editor.\
If the flow has already been deployed before, the button will read **Redeploy**.

<div align="left"><figure><img src="/files/LzimZDKMbekpNWBcbAOO" alt=""><figcaption></figcaption></figure></div>

During deployment, you can:

* **Select the environment** you want to deploy to (e.g., Production, Staging)
* **Add a note** describing the changes in this deployment
* **Enable superseding** of existing variants (to replace older versions)

{% hint style="info" %}
Enterprise customers have access to multiple deployment environments (e.g., Production and Staging) and can create additional environments as needed.
{% endhint %}

Once deployment is complete:

* A new [variant revision](/core-concepts/versioning-in-formsort-deploying/variant-revisions) will be created
* If you’ve set up [Subscriptions](/event-subscriptions), a **Variant revision published** event will be sent to the specified endpoint

{% hint style="warning" %}
No in-studio changes will go live until the variant is explicitly **re-deployed** to the production environment. This includes changes to:

* Theme and styling
* Logic and variables
* Third-party integrations
  {% endhint %}

### Drafts and Deployment

Before a flow is deployed, it exists as a **draft**—only visible in the [**preview window**](/publishing-and-deployment/preview-window) to Studio users who are signed in to Formsort. Drafts are not accessible to the public.

Once you make a **deployment**, the flow becomes **live** and publicly accessible.

Any edits made after deployment will **not** be reflected in the live version until you **re-deploy** those changes.

After deployment, you can **revert** to previous revisions.&#x20;

To learn more, see the [Managing Revisions](/core-concepts/versioning-in-formsort-deploying/managing-revisions) section.

***

### Superseding past revisions of a variant

When publishing a variant, you can choose to **Supersede past revisions**. This setting forces responders who started on an older revision of the same variant to load the latest version the next time they return.

<figure><img src="/files/alZiHPoyzRz4HzgEeF6v" alt=""><figcaption></figcaption></figure>

This is especially useful when:

* Fixing logic or content errors
* Updating the design or copy of an active variant

{% hint style="warning" %}
Superseding only applies to responders pinned to an older revision of the *same variant*. It does **not** affect responders who were on a *different variant entirely*.
{% endhint %}

#### Behavior of Returning Responders

When a superseding revision is loaded by a returning responder:

* They will be taken to the **beginning of the flow**
* Their previous progress will not be restored

We may offer more granular control over this behavior in the future—for now, assume superseding will reset in-progress sessions.


# Variant revisions

## Variant Revision UUID&#x20;

When you [deploy](/core-concepts/versioning-in-formsort-deploying#deploying-your-flow) or redeploy a variant to any environment (e.g., Production or Staging), Formsort creates a unique 36-character `variant_revision_uuid`. This UUID represents the exact state of the variant at the time of deployment and can be found in the **History** tab of the variant.

### Pinning

When a responder loads a flow and begins interacting with it, a `variant_revision_uuid` will be loaded based on the `responder_uuid` cached in their browser. This “pins” the responder to that exact variant revision on return visits, ensuring a consistent experience in terms of content, style, and logic.

This pinning behavior helps maintain:

* A seamless experience for returning users
* Cleaner cohort analysis, since responders in-flight on older revisions won’t be affected by new deployments

To bring responders onto the latest revision, see [**Superseding existing versions**](/core-concepts/versioning-in-formsort-deploying#superceding-past-revisions-of-a-variant).

#### Preventing pinning&#x20;

There are two main ways to bypass variant pinning:

* Enable **Start each session as a new responder** in your variant settings
* Use your browser’s **incognito mode**, which doesn’t store `responder_uuid` during incognito sessions

{% hint style="info" %}
💡 **Pro tip:** Testing a recent change but still seeing an old version? Open the form in an incognito window to make sure you're seeing the latest deployed revision!
{% endhint %}

### Checking Variant Caching

If you’re troubleshooting and want to confirm which variant revision is cached:

1. Open your live flow.
2. Right-click anywhere and select **Inspect** to open your browser's developer tools.

<div align="left"><figure><img src="/files/r3WauG6JSqZ7gDNWiF8z" alt=""><figcaption></figcaption></figure></div>

3. Go to the **Network** tab.
4. Look for a fetch request with the name of the variant deployment ID.
5. In that request, you’ll find the `variant_revision_uuid`.

<figure><img src="/files/eG9Tp8xuPStbbcpwOQ4s" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
You may need to reload the page after opening the **Network** tab—the fetch call is made **once** when the flow **initially loads**.
{% endhint %}

***

### Force‑Loading a Specific Revision

If you need to test or share an exact revision, append the `variantRevisionUuid` parameter to your form’s URL:

```http
https://your-form-url?variantRevisionUuid=YOUR_UUID_HERE
```

Replace `YOUR_UUID_HERE` with the target revision’s `variant_revision_uuid`.

{% hint style="info" %}
💡 Use this sparingly—hardcoding a revision UUID bypasses the normal publish workflow and can prevent future live updates from taking effect.
{% endhint %}


# Managing revisions

Formsort automatically saves every deployed revision of a variant. From the **History** tab, you can revert to a previous revision to inspect its structure and, if needed, redeploy it to make it live again. You can also view the JSON definition of any revision.

## Viewing & Reverting Revisions

1. **Open the variant** in Studio.
2. Click the **History** tab to see all past deployments—each entry shows the revision UUID, date, and any deployment notes.

<figure><img src="/files/ChbtbfArZmw87sf1tPai" alt=""><figcaption></figcaption></figure>

3. To revert, find the desired revision, open its 3-button menu, and select **Revert**. Confirm the action to roll back the live flow to that revision.

<figure><img src="/files/rFJOT9HEK9NpdiCF8P4c" alt=""><figcaption></figcaption></figure>

4. **Optional:** Deploy your form for the reverted changes to go Live.&#x20;

{% hint style="warning" %}
You can only revert to revisions that have been deployed—drafts aren’t available for reversion.
{% endhint %}

### Viewing JSON definition

You can also use the 3-button menu to access the JSON definition of the schema of the revision.&#x20;

<div align="left"><figure><img src="/files/mP5MWQYDoEzYdwuTdUhZ" alt=""><figcaption></figcaption></figure></div>


# Building a new flow

Create a new flow by starting from a prebuilt template or from scratch. You’ll choose a theme, configure flow details like name and URL slug, and then launch your form with a click.

In your studio, click **+ New Flow** to open a dialog box with two options:

* **Start from a template**
* **Start from blank**

The sections below walk through each option.

<figure><img src="/files/AOK9KV0LfS9VBcqKFi9C" alt=""><figcaption><p>You can either pick a template or start from scratch</p></figcaption></figure>

### Templates

#### Choosing a template&#x20;

Templates give you a head start by including prebuilt content and a default theme tailored for a specific use case—such as Patient Health Questionnaires, Net Promoter Score surveys, or Job Application forms.

If a template includes questions with calculated answers, the necessary calculated variables will be included automatically. For example, the PHQ‑9 template includes a scoring variable based on responder answers.

Templates are fully editable. You can modify or delete any content after creating your flow.

To get started, find the template you want and click on it. You’ll be taken to **Flow Details** to finalize setup before creating your flow.

<figure><img src="/files/60NloD0U6w0Bj2QJHBMn" alt=""><figcaption></figcaption></figure>

### Starting blank

If you choose to start blank, you’ll be taken directly to the **Pick a Theme** step.

<figure><img src="/files/dz6JcF5tQU7aFVfCzArx" alt=""><figcaption></figcaption></figure>

Here, you can:

* Choose a theme
* Edit flow details like the name, URL slug, and description

See **Flow Details** below for more on these settings.\
Once you're happy with your selections, click **Create a new flow!**

***

## Flow Details&#x20;

Flow Details help organize your studio and define how your form is accessed. These include:

* **Flow Name** – The internal name for your flow in your studio
* **URL Slug** – The unique part of your form’s URL
* **Description** (optional) – A short internal note to help differentiate flows

If you’re starting from a template, you’ll also see a live preview of the template. You can interact with it before clicking **Use this template** to create your flow.

<figure><img src="/files/lTR0JRivkJ4tfYK2Y9Gd" alt=""><figcaption></figcaption></figure>

### Set a Flow name and URL slug

The **Flow Name** determines how your flow appears in your studio. By default, it’s also used to generate the **URL Slug**, which becomes part of the public form link:

```http
https://{your-domain}/flow/{url-slug}
```

If you open **Advanced Options**, you can:

* Edit the URL slug (it’s pre-filled from the Flow Name)
* Add an optional description

{% hint style="warning" %}
**Note:** The URL slug cannot be changed once the flow is created. Double-check it before proceeding to avoid ending up with a confusing or messy public link.&#x20;
{% endhint %}

If you need to change the slug after creating the flow, see our [duplicating variants](/flow-variant-management#duplicating-variants) guide to create a new flow with your desired slug.

The Flow Name and Description are only visible to your team within the studio.


# Groups

Groups are helpful organizers for steps.

**Groups** help structure your flow by organizing steps into logical sections. They support conditional logic, custom styling, and dynamic ordering, making it easier to manage and present complex forms.

Groups themselves are not content, but help keep your flow and studio organized by acting as containers for **steps**. They can:&#x20;

* be [conditionally enabled](/conditions-and-logic), which will either hide or render the steps inside.
* use their own styling that overrides the flow theme.
* be dynamically re-ordered using the [Group Ranking API](/form-behavior-settings/variant-settings/group-ranking-api).

The user moves through the flow starting from the group at the top, down to the last group. This is the default behavior for Formsort.

{% hint style="info" %}
If you'd like to allow your user to jump around the flow, see our [Navigation Sidebar](/styling/customizing-appearance/content-area-and-form-layout#navigation-sidebar).
{% endhint %}

## Adding steps to a group

<figure><img src="/files/Snti5By8ltX4wRRPiU7q" alt=""><figcaption><p>Click "Edit groups" to open the Group menu</p></figcaption></figure>

<figure><img src="/files/iD5DieYDTsGf7i8WuRI5" alt=""><figcaption><p>Press the caret to close the Group menu</p></figcaption></figure>

To add a group, click **Edit groups** when hovering the left-side groups editor, or click the arrow at the top right.

In the expanded group editor, you can add groups and then create steps within them.

If you want to move existing steps into a group, you can drag the step into the groups while the group editor is expanded. Alternatively, you can use [cut and paste](/creating-flows/building-a-new-flow/copy-paste-of-form-content) steps to re-organize content between groups.

If you do not create groups, all steps appear within a *default* group.&#x20;

***

## Group settings

#### Group Label

You can add a label to easier identify the function of a Group, e.g. naming a Group "Introduction" if it houses the content/questions related to introducing a user to the form. Group labels are not shown to users, unless you choose to show them within the [progress bar](/styling/customizing-appearance/progress-bars#counting-groups-or-steps).&#x20;

#### Group ID

Used to create a stable definition/ID of the group when using the [Group Ranking API](/form-behavior-settings/variant-settings/group-ranking-api). See the linked documentation for more information.&#x20;

***

## Group logic

Groups can be made [conditional](/conditions-and-logic), which will show or hide all the steps within a group all at once.

<figure><img src="/files/8DS6Y0uS6TvslbbPi7mF" alt=""><figcaption><p>Rendering a group based on a <a href="/pages/-MJTlk6wJVwarKI2Io8r">external variable</a> being passed in at flow load. </p></figcaption></figure>

## Style

### Styling groups

[Group style overrides](/css-and-advanced-styling#group-style-overrides) can be used to alter any part of the general Theme set within a group.

<figure><img src="/files/Fuv03CWje0U8n2zfZLko" alt=""><figcaption><p>Group style override menu</p></figcaption></figure>

These are particularly handy if you have distinct sections of a flow which might want to adopt a slightly different look and feel.&#x20;

For example, you might have an *Introduction* group that contains  some lightweight questions and content perhaps a more colorful and spacious design. You can follow that with a *Data collection* group that has a denser layout to facilitate answering many questions all together


# Steps

Steps are containers for questions and content.

<figure><img src="/files/qsJVWWJG5DBRbVEZnLHO" alt=""><figcaption></figcaption></figure>

A step is viewed as a single page within a flow, which may contain one or many questions. Steps themselves act as containers and do not collect information directly.

In the Studio, steps can be easily reordered by dragging and dropping them. Each step is indexed in the top-left corner (e.g., "Step 1") for easy reference. When a user navigates through a live flow, the step index is reflected in the URL, allowing browser navigation between steps. For example, the URL `https://formsortstuff.formsort.app/flow/all-components/variant/main#1` indicates that the user is at step index 1.

Each step has its own settings, logic, styling, and redirect behavior.&#x20;

{% hint style="info" %}
:bulb:For more information about redirects, see the **Redirects** section.
{% endhint %}

***

### Settings

<figure><img src="/files/CfubBPvlOOvBeYGYCWuC" alt=""><figcaption></figcaption></figure>

#### Step ID

Assigning a Step ID creates a stable reference for a step even as steps are reordered or renamed. This is especially helpful for analytics, where tracking step performance across versions is important, and for applying custom CSS. Step IDs must be unique when set but are not required—if omitted, the step index will be used instead in analytics payloads.

{% hint style="info" %}
To include Step ID in the form submission payload, add **current step id** to your [library variable](/response-data-collection-and-management/variable-schema/system-library-answers#overview-of-library-answers) schema.
{% endhint %}

#### Auto-advance after ms

If enabled, the step will automatically advance after the specified timeout. This is useful for creating interstitial screens that do not require user input. Auto-advancement will only occur if:

* All required questions are answered,
* All questions are optional, or
* No questions are present (e.g., only Statements or Images).

#### Is savepoint

If your answer submission frequency is set to *On savepoint* or *When the flow is finalized or abandoned*, enabling *Is savepoint* ensures that responder answers are submitted to your integrations when this step is **completed**.

***

### Logic

Steps can be conditionally rendered based on previous answers and can control flow navigation.

<figure><img src="/files/CcSWlG1aYXwqf8vS6MjP" alt=""><figcaption><p>Rendering the first step based on an external variable value</p></figcaption></figure>

#### Show conditionally

Use conditional logic to show or hide steps based on answers provided earlier in the flow. Logic cannot be based on answers that come later.&#x20;

{% hint style="info" %}
:bulb:See Conditions and Logic for more details.
{% endhint %}

#### Disable back navigation

When enabled, responders cannot navigate backward past the step. The "Previous" button will be hidden, and browser back navigation will be disabled. This is useful for steps where changing earlier responses would not make sense (e.g., after a payment has been completed).

#### Disable forward navigation

When enabled, responders cannot move forward from the step. ***This finalizes the form session when the step loads***, preventing further answer collection. Content like Images or Statements can still be displayed.

#### Finalizing steps

If *Is finalizing?* is enabled:

* The `FlowFinalized` event will be emitted to analytics integrations upon loading.
* An answer payload with `finalized: true` will be delivered to your answer integrations.
* Responders will not be able to advance beyond this step, and redirects cannot be configured for it.

{% hint style="warning" %}
Since the flow is finalized when the step loads, you cannot collect answers from a finalizing step.
{% endhint %}

***

### Common Step Patterns: Collecting a Waitlist Email from Disqualified Responders

Let’s put some of the Step settings and logic into action with a common pattern: collecting an email address from users who are disqualified from continuing. This approach helps you build waitlists, collect feedback, or stay in touch with users who don't complete the primary flow.

To implement this pattern:

1. **Determine disqualification:**\
   Use a [calculated answer](/response-data-collection-and-management/variable-schema/calculated-answers) or [API answer](/response-data-collection-and-management/variable-schema/api-answers) to evaluate whether a responder should be disqualified. Name it something semantic, such as `is_disqualified`.
2. **Add a waitlist step:**\
   Create a step containing the questions you want to ask disqualified users (for example, an email input saved as `waitlist_email`). Set a show condition on the step: `is_disqualified Equals true`.\
   This ensures only disqualified users see this step and can submit their contact information.

   To prevent users from returning to earlier questions and altering their answers to re-qualify, enable **Disable back navigation** on this step.
3. **Finalize the experience:**\
   After collecting the waitlist information, you can either:

* **Option 1: Show a thank-you step:**\
  Add a final step that displays informational content (e.g., a thank-you message).
  * Set **Disable forward navigation** so the responder cannot proceed further.&#x20;
  * Set the step to **show conditionally** if `waitlist_email Is defined`, ensuring it only appears if an email is provided.&#x20;
  * Optionally, **enable Finalizing** to trigger a `FlowFinalized` event for your analytics and integrations.
* **Option 2: Redirect the user:**\
  Instead of showing a final step, you can configure a **redirect** to send the responder to an external page, such as your homepage or a custom thank-you page.\
  This can be done by setting a redirect conditionally after the waitlist step is completed.


# Copy-pasting form content

Cut, copy, and paste work whenever your have a group, step, question, or variable selected: within flows, or even between flows.

### Copying and pasting items

When an item is focused, there is a sub-menu that will allow you to copy/cut an item. Alternatively, you can right-click the item.&#x20;

This is available on groups, steps, and questions.&#x20;

<div align="left"><figure><img src="/files/bpftTdWf36n0sbOBtc48" alt="" width="429"><figcaption></figcaption></figure></div>

<div align="left"><figure><img src="/files/4Jgk6HWEMAoAoGXLRcPu" alt="" width="563"><figcaption></figcaption></figure></div>

Once an item is copied, you can paste by right clicking the target area and:&#x20;

<div align="left"><figure><img src="/files/BXCOkRBbvonwyQ2IAYo5" alt="" width="420"><figcaption></figcaption></figure></div>

When pasting content that depends on answers from other parts of a flow, you’ll need to guide the Formsort studio on how to handle those dependencies. This process is called **mapping input variables**.

### Mapping input variables

<div align="left"><figure><img src="/files/gh8p9VfPtr3OS8DIpkM6" alt="" width="458"><figcaption></figcaption></figure></div>

If you paste a flow slice that contains variables—such as [templated labels](/variable-templating) or questions that rely on conditional logic—you'll be prompted to map those variables to existing answers in your flow.

Flow slices that contain only static content (like informational text or images) can be pasted without any additional configuration.

When mapping input variables, you’ll have a few options:

### Create new answer

Adds a new question to the beginning of the pasted slice to collect the required input from the responder.

<div align="left"><figure><img src="/files/yxwJlDqceNlYR0TKKyRI" alt="" width="332"><figcaption></figcaption></figure></div>

<div align="left"><figure><img src="/files/pyF5lpGLYfMR41feRWA9" alt="" width="400"><figcaption></figcaption></figure></div>

### Map to existing answer

If your flow already collects an answer of the correct type, you can map the variable to that existing answer—no new question needed.

<div align="left"><figure><img src="/files/eqBkmrz20qLF2mdS0UQI" alt="" width="350"><figcaption></figcaption></figure></div>

This process is great for creating duplicate instances of a question.&#x20;

<div align="left"><figure><img src="/files/WNaOwmVRmgCqffGeHMZg" alt="" width="394"><figcaption></figcaption></figure></div>


# Adding questions and content

### Understanding Questions

To capture **variables** in your flow, you’ll use questions—building blocks designed to collect input from responders. Each question stores the responder’s input as a variable, which can then be referenced throughout your flow for logic, personalization, and integrations. Formsort provides a variety of question types and configuration options to support different data formats, UI preferences, and conditional logic needs. The sections below cover how variables are created through questions and how they behave across your flow.

See the question-variable relationship here:&#x20;

<figure><img src="/files/RMkx78nCs7viVFTbE2Xo" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Head to the [Questions](/adding-questions-and-content/question-reference) sub-section for more information.
{% endhint %}

***

### Variables (Answers)

Variables in Formsort allow you to use and classify answers and other data once it has been collected. Every **question** in your form is associated with a **variable** where the responder's answers are stored. These variables are referred to as **variables from questions**.

<div align="left"><figure><img src="/files/T031GfDkKMhGA4s66qEK" alt="" width="474"><figcaption></figcaption></figure></div>

{% hint style="info" %}
:bulb: Additionally, the following types of variables return values that are also stored in variables, though their resolution mechanism differs:

* **Externally provided**
* **API lookups**
* **Calculated inline**
* **System library**
  {% endhint %}

{% hint style="info" %}
See our [Schema](/response-data-collection-and-management/variable-schema) section for more information about variables.
{% endhint %}

***

### Understanding Content

In contrast to **questions**, **content** components are used purely for displaying information—they don’t capture or store any data from the responder. These read-only elements help you explain concepts, break up long pages, embed media, and add supporting context to your flow. From simple statements to rich video embeds, content blocks give you the flexibility to design a form that communicates clearly and guides responders effectively.

<figure><img src="/files/Y3TKVTyigfeEUIcZU4U2" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
See our [Content](/adding-questions-and-content/content-reference) sub-section for more information.
{% endhint %}


# Questions

Collect answers.

<figure><img src="/files/sBh3ujyVegOioXKgojxK" alt=""><figcaption></figcaption></figure>

Questions are used to collect specific information from responders within a step and store their answers in variables. Formsort provides a wide range of question components to accommodate different needs—everything from single or multiple-choice options to specialized fields for capturing addresses and more.

These question components can be easily reordered by dragging and dropping them within a step, or even between different steps and groups. Just like steps and groups, questions can also be conditionally rendered based on responder input.

Check out the sub-sections below to explore the different types of questions available and determine which is best suited for your flow’s objectives.

{% hint style="info" %}
Need something more custom? You can embed your own questions using an **iframe** question.
{% endhint %}

***

### General Settings

<div align="left"><figure><img src="/files/eYsNMmhD6MVfl5fqGZHu" alt=""><figcaption></figcaption></figure></div>

While some settings are specific to individual question types, the following options are available for all questions:

#### Question label

A text field used to prompt the responder. This label can be edited directly on the question component or via the settings menu.

#### Variable

The name assigned to the answer, as it will appear in your answer payload.\
By default, this mirrors the Question Label, but you can edit it independently.\
Example answer format:

```json
answers: {
    variable_name: variable_value
}
```

#### Readable description

An internal-only field to help your team understand the purpose of the question.\
This is not shown to responders but can be included in the metadata of answer payloads if enabled in your integration settings.&#x20;

Example payload snippet:

<pre class="language-json"><code class="lang-json"><strong>"answers": {
</strong>  "variable_name": "variable_value"
}
// Metadata fields
"descriptions": {
  "variable_name": "A Boolean T/F question"
}
"responder_uuid": "debe557f-16bc-4370-9e14-cc238c77879b",
"flow_label": "flow",
"variant_label": "main",
"variant_uuid": "bf5efd0a-2869-44af-91a2-c0128b9ecf6c",
"finalized": false,
"created_at": "2023-10-05T21:47:49+00:00"
</code></pre>

#### Data classification

Tags the question as collecting personally identifiable information (PII) or another sensitive data type.

#### Question ID

Assign an HTML `id` attribute to the question.\
Useful for targeting with custom CSS.

#### Has info Text?&#x20;

Provide additional context without cluttering the UI. Enable **Has info text?** to display a tooltip or modal alongside the question.\
You can choose between text links or an icon-based trigger.

{% hint style="warning" %}
Info text is not supported for Select Button components.
{% endhint %}

#### Allow Multiple Answers

Enable this option to let responders submit more than one answer to a question.

Supported question types:

* Date
* Email
* Number
* Phone
* Text
* Select

When enabled, answers are stored as an array:

```
{
  "pets": ["cat", "dog", "bird"],
}
```

A **+ Add** button will appear after the first input, allowing responders to enter additional values.


# Select

Allows picking between a few choices.

**Select questions** are the easiest way to collect structured data from responders. Unlike text input fields, responders choose from pre-defined options, reducing friction and error. Common use cases include:

* Single- or multiple-choice questions
* Dropdown selections (e.g., U.S. states)
* Image or video-based pickers

***

### Core Settings

<figure><img src="/files/IU0NFV8grP1NsXeSpPIe" alt=""><figcaption></figcaption></figure>

#### Allow multiple selection

Enable this setting to let responders choose more than one answer.

#### Autoadvance if possible

When enabled (default), the flow will automatically advance after the responder selects a choice—**but only if all other questions on the step are already answered**.

{% hint style="info" %}
Autoadvance is disabled when multiple selection is allowed.
{% endhint %}

***

### Customizing choices

<div align="left"><figure><img src="/files/QxpJqLxxRv08krq1IwTz" alt=""><figcaption></figcaption></figure></div>

#### How to add choices

You can populate choices via:

* **Manual entry** in the **Choices** tab
* **Bulk add** from the **Choice library**
* **Dynamically** via external API lookup or local calculation (see below)

#### Randomize choice order

To reduce bias, enable this setting to shuffle the display order each time the step loads.

{% hint style="info" %}
**Pin to position**

When randomization is on, you can **pin** specific choices (e.g., “Other”, “None”) to always appear in the same position.
{% endhint %}

#### Choice value type

By default, values are stored as strings. You can also choose `boolean` or `number` types from the **Choice value type** menu.

#### Customizing individual choices

![exclusive choice](/files/AWk7tyl25J5oTvM30XeP)

Each choice has a:

* **Label** (shown to responder)
* **Value** (stored in submission)

Optional settings include:

* **Show conditionally:** Show the choice only if a condition is met.
* **Exclusive:** Deselect all others if selected (available only when multiple selection is enabled).
* **Disabled:** Prevent selection.
* **Checked by default:** Pre-select this option when the step loads.
* **Description:** Internal-only helper for admins (can be sent in payloads).
* **Media (image/video):** Attach visuals to choices if **Show images on choices** is enabled.

{% hint style="info" %}
You can control the appearance of image/video choices in Theme → Buttons → Select → Image.&#x20;
{% endhint %}

***

### Using the Choice Library

<div align="left"><figure><img src="/files/elnda45zcl43rfJohtZY" alt="" width="319"><figcaption></figcaption></figure></div>

Formsort provides pre-built sets (e.g., U.S. states, Likert scales). Find them at the bottom of the **Choices** tab.\
Want to suggest a new one?

{% hint style="warning" %}
Adding from the choice library will replace/overwrite any existing choices.&#x20;
{% endhint %}

***

### Collecting "Other" responses

To capture custom answers:

1. Add an “Other” option.
2. Add a **Text** question that appears conditionally when “Other” is selected.

***

### Dynamic Choice Loading

Dynamic loading is ideal when choices change frequently or are not known at build time.

<figure><img src="/files/0a3AEu7gRQ0J48w6BkLb" alt=""><figcaption></figcaption></figure>

#### Required format (choice schema)

Choices must follow this structure:

```json
[
  {
    "label": "The first choice", // Displayed to the user
    
    "value": "choice_a",         // Stored in the answers
    
    "disabled": false,           // [Optional]: Whether the choice
                                 // is disabled
                                 
    "imageUrl": "https://..."    // [Optional]: If loading choices
                                 // for a select question with
                                 // images, the URL of the image.
  }
] 

```

#### How to configure

**1. External API**

<figure><img src="/files/iDb4QelGi60WIAmgYMgE" alt=""><figcaption></figcaption></figure>

* Go to **Choices** → **Load choices dynamically** → **External API** → **Define api source**.
* Set the URL for the API.&#x20;
* If the API response doesn’t match the required schema, use a **result mapping function** to transform it.

Learn more in **API answers**.

**2. Local calculation**

<div align="left"><figure><img src="/files/UQinkL2ndHYzXkNJBXnq" alt="" width="563"><figcaption></figcaption></figure></div>

* Choose **Calculated locally** and use TypeScript to generate choices
* The function must `return` an array of objects

```typescript
function myFunction(): IChoice<string>[] { // readonly line
  return [
   {
     label: string, 
     value: string
    } 
  ]
}
```

#### Caclulate locally

When **calculated locally** is enabled, you will be able to use Typescript to output the answers for responder.

You can write any functions you need, but at a minimum, they must return an array of objects that follow the format below.

<div align="left"><figure><img src="/files/22NYBTwgJH1DM3T5yGD4" alt="" width="447"><figcaption><p>Calculated</p></figcaption></figure></div>

{% hint style="success" %}
See [this video](https://drive.google.com/file/d/1FyxvRJIGjnFKwPs1TyUl58rhvd2qDAGg/view?usp=sharing) comparing different approaches to specifying choices for a visual walkthrough.
{% endhint %}

***

## Styling Choices

<figure><img src="/files/g4m6ceSL5dZoiWFWYbvu" alt=""><figcaption></figcaption></figure>

In the **Style** menu, use the **Picker Style** dropdown to choose how choices appear:

* Buttons
* Carousel
* Dropdown
* Native dropdown
* List
* Sliders

{% hint style="info" %}
:bulb: For more style customization details, see [Button Styling](/styling/customizing-appearance/buttons).
{% endhint %}


# Text

Collects text answers

Text questions are used to collect short or long-form free-text input from responders. Common use cases include gathering names, email addresses, or open-ended feedback.

Text question answers are stored as a `string` data type.

<figure><img src="/files/jqQUwoqsrW1o8Gq8Nv1z" alt=""><figcaption></figcaption></figure>

***

### Long-Text Input

By default, a text question shows a single-line input. To enable multiline responses:

* Select the question.
* Enable **Multiline?** in the settings panel.
* Specify the **minimum** and **maximum number of lines** for the input box.

To customize the dimensions of the input box further (e.g. to create a longer feedback box):

* Open the **Style** tab.
* Toggle **Override default text input style**.
* Apply your desired styling.

#### Limiting Answer Length

To control the length of a responder’s answer:

* Enable **Max length restriction?**
* Choose a limit by **characters** or **words**.

You can also enable **Show remaining count?** to display the number of characters or words remaining as the responder types.

***

### Formatting Input with Prefixes and Suffixes

To provide additional context and guide responders on the expected input, you can add:

* A **Prefix** (e.g. `$` or `@`)
* A **Suffix** (e.g. `USD` or `.com`)

These can be configured in the **Settings** menu under **Prefix?** and **Suffix?**.

***

### Allowing Multiple Answers

To allow a responder to submit more than one value for a single question:

* Enable **Allow multiple answers** in the settings.

Each new input will be treated as an individual item in an array of string values.

***

### Default Answer

Use the **Default answer** setting to prepopulate the text field with a preset value. This value can still be edited by the responder.

<figure><img src="/files/q6TFIBJBklR5MNNWbzjP" alt=""><figcaption></figcaption></figure>

#### Using Other Values as Answers

<figure><img src="/files/bF4AA90zbOHLWtysntmo" alt=""><figcaption></figcaption></figure>

You can dynamically set the value of a Text question by referencing other variables in your flow:

* Enable **Another answer/variable** in the settings.
* Any other data-type-compatible variables will be available to choose (e.g. from a previous question, a calculated variable, or an API response).

This is useful for personalization or autofill behavior (e.g. pulling a first name from a previous step).

#### Constant

To assign a static value to a Text question that never changes:

* Use the **Constant** setting.

This is helpful for storing a fixed value (e.g. an internal tag or a source code) in the answers payload without requiring responder input.


# Address

Collects an address, including street, state and postal code.

Unlike most answers, a default address answer value is stored as an `object`, since it contains multiple parts.

```
{
  "my_address": {
    "address_1": "213 Main St",
    "address_2": "Apt 17",
    "city": "Kansas City",
    "state": "MO",
    "postal_code": "64116"
  }
}
```

{% hint style="info" %}
Need only the town / city? See the [Region](/adding-questions-and-content/question-reference/region) question type.
{% endhint %}

If you would like to access one of the parts of the address in a string [template](/core-concepts/formsort-quickstart-guides/template-your-variables), you may use the `get` template function.

Given the above example, a template like:

```
Great, your postal code is {{my_address | get 'postal_code'}}.
```

Would render:

```
Great, your postal code is 64116
```

### Use Single Field

When this option is enabled, Formsort will provide a single input field for the user address, rather than distinct input fields for street address, city, state, and zip.&#x20;

<figure><img src="/files/n4tvFLmkXstec9j4rbxZ" alt=""><figcaption><p>Single field address vs default address components</p></figcaption></figure>

The payload for a single field address is a little different than the default, since the data type is considered `string` instead of an address `object`. This means the address details will come in as one `"raw"` property in the address object.&#x20;

```
{
  "my_address": {
    "raw": "213 Main St., Apt 17, Kansas City, MO 64116"
  }
}
```

### International addresses <a href="#international-addresses" id="international-addresses"></a>

If you enable **International?**, then responders will be allowed to choose the country of their address. The saved address will contain the `country` that was selected.

```
{
  "my_international_address": {
    "address_1": "Strahovské nádvoří 301",
    "city": "Praha",
    "postal_code": "118 00"
    "country": "Czech Republic"
  }
}
```

#### Default <a href="#default" id="default"></a>

When international addresses are enabled, you can set a default country using Default country.

## Setting Address via URL parameter

If you'd like to pass an address into the flow for your user, here is an example of the syntax required for the address component at the top of this section. Notice each `[field]` corresponds to an object key from the example.&#x20;

You can set all the address fields, or just the fields you have (or want to use).&#x20;

#### Default address component syntax:&#x20;

`https://your-flow-url.com?my_address[address_1]=213+Main+St&my_address[address_2]=Apt+17&my_address[city]=Kansas+City&my_address[state]=MO&my_address[postal_code]=64116`

#### Single-field address component syntax:

Since the Single-Field address string will require commas, you may have to URL encode the address before passing it in as a value for the parameter.&#x20;

`https://your-flow-url.com?my_address[raw]=123%20Main%20St%2C%20Apt%2017%20Kansas%20City%2C%20MO%2C%2064116`

{% hint style="info" %}
See [URL Parameters](https://docs.formsort.com/handling-data/passing-data-in/url-parameters) for more information on passing answers in to a flow.&#x20;
{% endhint %}

## Autocomplete using Google Places API <a href="#autocomplete-using-google-places-api" id="autocomplete-using-google-places-api"></a>

Addresses are sometimes tedious to type, so you can use the [Google Places API](https://developers.google.com/places/web-service/get-api-key) to auto-complete addresses, by providing your API key.&#x20;

{% hint style="info" %}
If you are restricting referrers and/or embedding your Formsort flow, make sure to add `flow.formsort.com` (which is where your flow will be hosted) to the list of allowed referrers in the Google API console, **in addition** to any custom domains on which you are hosting your flows.

Whitelisting `flow.formsort.app` may be necessary for use of the Places API in the Live Preview as well!&#x20;
{% endhint %}

When enabled, the address answer will also contain a `raw` field which stores the raw string that was entered by the user.

```
{
  "my_address": {
    "raw": "213 Main St., Apt 17, Kansas City, MO 64116",
    "address_1": "213 Main St",
    "address_2": "Apt 17",
    "city": "Kansas City",
    "state": "MO",
    "postal_code": "64116"
  }
}
```

## Validating form addresses with USPS <a href="#validating-form-addresses-with-usps" id="validating-form-addresses-with-usps"></a>

{% hint style="info" %}
This feature is only available for addresses in the United States.
{% endhint %}

Enabling **Validate addresses (US only)?** activates the [USPS Addresses API](https://developers.usps.com/addressesv3), which verifies that entered addresses are valid U.S. mailing addresses. Before using this feature, you must configure the USPS integration.\
Follow the guide [here ](/integrations/integration-reference/usps)to get started.


# Comparison

A comparison table, often used for pricing pages.

This question type is commonly used to compare complex offerings such as plans or packages. &#x20;

![](/files/MaaKipGtFkCrRSQOS67t)

## Adding choices

You can add choices by clicking the **+Choice** button in the settings menu with the questions selected. In order to add more details to your choice, click the down caret to expand the setting menu.&#x20;

Using the same menu, you also have the option to highlight, disable or [conditionally](/conditions-and-logic) show your choices.

<figure><img src="/files/KLmDfzgoLSHqMYGbfdjG" alt=""><figcaption></figcaption></figure>

For details on how to customize the comparison cards, you can refer to the documentation [here](/styling/customizing-appearance/comparison).


# Confirmation

An acknowledgement checkbox.

This question type is commonly used for documenting that the responder has read the terms and conditions or is acknowledging the information presented in the page.  &#x20;

The answer to a confirm question is stored as a [boolean](/response-data-collection-and-management/variable-schema#variable-data-types).

<div align="left"><img src="/files/tMTl6OCYTh5B2oaYof32" alt=""></div>

## **Changing the confirmation text**

You can personalize the text that shows up next to the checkbox by editing the **Confirmation text** field in the setting menu that appears when the question is selected.&#x20;

## **Setting the default behavior**

If you enable the **Default to be checked** option in the setting menu, the confirmation box will appear in its checked state when the content is first loaded.&#x20;

{% hint style="info" %}
In order to style the confirmation box (changing the padding between the text and the box or the color), you can style **Multiple Choice (and not the radio buttons)** boxes [checkmarks](/styling/customizing-appearance/checkmarks) menu.
{% endhint %}


# Date

A date picker.

This question type is commonly used for getting a responder's date of birth or their preference for when an event should take place.&#x20;

The answer to a date question is stored as [Formsort subtype](/response-data-collection-and-management/variable-schema#subtypes) date and are always formatted as [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601).&#x20;

## **Limiting answers to a specific timeframe**

You can force the responder to pick dates in the future or in the past by enabling the **Only allow dates in the past?**  or **Only allow dates in the future?**  options. This way you can ensure that your responder's birthdates are not in the future or they do not accidentally schedule an event in the past.

## **Allowing multiple answers**

Enabling **allow multiple answers** lets the respondent pick more than one date as an answer to a question.&#x20;

## Date picker styling options

The date picker menu can either be native to the platform the respondent is using (e.g. using Google’s date picker on Chrome) or can inherit from the [dropdown menu styling](/styling/customizing-appearance/dropdown-menu).

In order to change the styling of the date picker, you can go to the style tab with the question selected.

## Setting a date answer with URL parameters

Answers for the date component can be passed in via [URL parameter](/importing-data/url-parameters), with the format as `YYYY-MM-DD`. For example, if you want to pass in the `appointment_date` answer "Apr 25 2023", append `?appointment_date=2023-04-25` to the flow URL.<br>


# Date & time

A date picker with the option of specifying time.

The **Date and Time** component is used when specifying time is important as well as the date. Setting meeting times or otherwise establishing timeframes for availability are common use cases.&#x20;

The answer to a date question is stored as [Formsort subtype](/response-data-collection-and-management/variable-schema#subtypes) *datetime* and are always formatted as [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601).&#x20;

## **Limiting answers to a specific timeframe**

You can force the responder to pick dates in the future or in the past by enabling the **Only allow times in the past?** or **Only allow times in the future?** options. This way, you can ensure that responders do not accidentally schedule a meeting for a time in the past, or specify a past event as some time in the future.&#x20;

Additionally, you can set a range of dates and times by enabling **Min date** and **Max date**, which can help ensure users set meetings during available hours.&#x20;

## Date picker styling options

The date and time menu is rendered from the native date time input of the responder's browser. Currently, there is no dropdown version.&#x20;


# Email address

Collects email addresses.

This question type is commonly used for collecting and validating email addresses from responders.&#x20;

## Data structure

The answer to the email question is stored as [Formsort subtype](/response-data-collection-and-management/variable-schema#subtypes) email .&#x20;

```
{
  "my_email": "hello@formsort.com"
}
```

## **Suggesting spelling corrections**

Beyond validating for email structure itself, when **Warn about common domain typos** is enabled, Formsort suggests potential corrections to non-standard domains.

For example, an answer ending like *gnail.com* would be met with a prompt asking if the responder meant *gmail.com,* and the responder would be prompted to correct it before continuing.

![A warning about an email type](/files/-MKpAyz8EOM0vbj83wym)

{% hint style="info" %}
Warnings about common domain typos work best when responders are consumers using common email providers. If your responders are using business emails you may want to disable this feature.
{% endhint %}

## **Allowing multiple answers**

Enabling **Allow multiple answers** lets the respondent type more than one email address as an answer to your question.&#x20;


# File upload

Collects various files from the responder.

This question type is used in cases where the responder needs to upload documents, such as a `pdfs` of contracts, certificates, prescriptions, etc.

If you would like to ask the responder take a picture of an ID card or themselves, you can use the[ image upload](/adding-questions-and-content/question-reference/image-upload) content type. If you would like the responder to sign a document, you can use the [signature ](/adding-questions-and-content/question-reference/signature)content type.

{% hint style="info" %}
If you don't want the responder to be held up by a file upload question, make sure to mark the question as [optional](/conditions-and-logic#optional).
{% endhint %}

### Managing File Storage

By default, Formsort securely stores uploaded files and images with our [data storage service](/saving-and-retrieving-responses) — no setup required.\
If you prefer to manage your own storage, you can optionally configure an **S3** or **GCS** bucket as a custom file destination.

To set up a custom integration, follow our detailed guides for:

* [Amazon S3 setup](/integrations/integration-reference/s3)
* [Google Cloud Storage setup](/integrations/integration-reference/google-cloud-storage)

### Creating a filename pattern

You can opt to define a naming pattern for the files that your responders upload by enabling **Filename pattern** in the question settings.&#x20;

Here, you can [template](/variable-templating) any relevant variable you would like to be included in the name of the file into the field. For example, if you type `{{first_name}}` into the field, the value of the `first_name` variable will be used as the filename at upload.&#x20;

Formsort also offers three custom variables for these types of file upload components, built right into the settings of the component itself: `sessionUuid`, `randomUuid`, and `currentTimeUTC`. These variables are also able be templated into the **Filename pattern** field to create dynamic filenames.

* `sessionUuid`: a uuid that is created **once**, when the form is loaded.&#x20;
* `randomUuid`: a uuid that is **created at and re-run** at each upload instance.&#x20;
* `currentTimeUTC`:  a timestamp that is **created at and re-run** at each upload instance.&#x20;

<div align="left"><figure><img src="/files/QxrTbJLavbtvTS09A0Bh" alt="" width="563"><figcaption></figcaption></figure></div>

You can use several of these variables at once, if required, and one pattern we suggest is `{{currentTimeUTC}}_{{responder_uuid}}`. This ensures uniqueness via the timestamp, while still tying the file to a particular user via the `responder_uuid` System Library variable.&#x20;

{% hint style="info" %}
Visit the [System Library variables](/response-data-collection-and-management/variable-schema/system-library-answers) section for details on built-in variables that provide information about the responder's environment and context.&#x20;
{% endhint %}

Note that these extensions will be added automatically. You should also set a fallback naming convention in case the variable is not defined by typing it into the **Filename pattern fallback** field. Values here cannot use templated variables.&#x20;

### Specifying file types allowed

You can whitelist [MIME (Multipurpose Internet Mail Extension) types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) by adding them to the **Allowed MIME types** field. Once a MIME type is whitelisted, the file upload will only accept that specific file type (as well as any other file type whitelisted).

The below is a list MIME types currently supported by the file upload component:&#x20;

| Category | MIME types                                                                                           |
| -------- | ---------------------------------------------------------------------------------------------------- |
| Images   | <p>image/gif</p><p>image/vnd.microsoft.icon</p><p>image/png</p><p>image/jpeg</p><p>image/svg+xml</p> |
| Video    | image/mp4                                                                                            |
| Document | application/pdf                                                                                      |

{% hint style="info" %}
You should whitelist MIME types, otherwise you may receive uploads of formats that you cannot process; i.e. if you want to allow *only* document uploads, whitelist the **application/pdf** MIME type.&#x20;
{% endhint %}

### **Allowing multiple answers**

Enabling **allow multiple uploads** lets the respondent upload more than one file as an answer to a question.&#x20;


# Grid choice

Ask multiple questions with the same set of choices

A **grid question**, sometimes referred to as a *matrix* question, is used to select multiple values, each with the same set of choices.

Grid questions are most commonly used when multiple answers are collected on the same scale.

![An example grid question](/files/-MKazpg-hcJV5_sL0nq1)

If you are asking for a single choice, use a [select](/adding-questions-and-content/question-reference/select) question instead.

## Specifying choices

Similar to the select question, you can specify choices for a grid question under **SETTINGS**. There you may also specify the type of the value, whether number, string, or boolean.

The choices will be shown as options for each of the sub-questions that you define, displayed as individual columns.

#### Allow multiple selection

When **allow multiple selection** is checked a user will be allowed to provide multiple answers for every row.

### Sub-questions

To define the rows of the grid, specify the questions to ask in **SUB-QUESTIONS**. These can be [conditional](/conditions-and-logic) on previous answer values.

Each of the sub-questions will have its own [answer variable name](/response-data-collection-and-management/variable-schema#naming-answer-variables), and will otherwise behave the same as if the question were asked separately.

## Style

The grid question uses the radio or check selection from [checkmarks](/styling/customizing-appearance/checkmarks) in the style editor.

In order to further customize the grid question, you can use the [Table](/styling/customizing-appearance/tables) tab in the style editor.&#x20;


# Iframe

A custom question, hosted by you within an iframe.

Formsort supports dozens of question types out of the box. However, in some cases it makes sense for you to implement a custom question type yourself using the Formsort [Custom Question API](https://github.com/formsort/oss/tree/master/packages/custom-question-api).

A great example of this would be an airline ticket purchasing flow, which at one point requires picking a seat (or multiple seats).

While you could use select buttons in a grid to achieve this within Formsort, backed by an API to fetch the choice options, maintaining the logic of which planes are in your fleet and which seats are available would be much more easily done as part of your existing technical infrastructure.

Instead of having to custom engineer the entire form flow, you can build just the single question you need and plug it into your existing flow, reusing the Formsort questions and logic for the remainder.

{% hint style="info" %}
To view various sample implementations of custom questions, check out this repo: <https://github.com/formsort/custom-question-examples>
{% endhint %}

## Adding read-only content <a href="#adding-read-only-content" id="adding-read-only-content"></a>

The simplest custom content is read only. Once a user sees the content, they will be allowed to continue along in the flow.

This is useful if you have something like a privacy policy you need to show a user, and you don't want to link to it or have to keep the content within Formsort.

To add it, set the **Source URL** to the URL of your content, and enable **Is read-only?**.

You yourself host custom content and questions (or use something like a headless CMS to do so). During development, it's recommended to point the Source URL to localhost and view the flow in the Live preview.

## Setting the size of the container <a href="#setting-the-size-of-the-container" id="setting-the-size-of-the-container"></a>

Within the custom question settings, you can set the Default width and Default height.

If your content or question is larger than this size, the default behavior is to allow it to scroll. If you'd rather disallow scrolling, you can set the behavior in overflow-y.

### Dynamic setting of size <a href="#dynamic-setting-of-size" id="dynamic-setting-of-size"></a>

The `<iframe>` security model does not allow the containing frame to access your content directly, so Formsort does not know the size of your content if you don't tell it.

For example, this snippet would set the size of the question within formsort to the size of the body.

```
import { setQuestionSize } from '@formsort/custom-question-api';

window.addEventListener('load', () => {
  const width = document.body.offsetWidth;
  const height = document.body.offsetHeight;
  setQuestionSize(width, height);
})
```

Take care to only call this when your content has fully loaded or rendered (eg, if you're using React, then on the `componentDidMount()` class method or `useEffect` hook), and not to make it *too big* so that the rest of the step it is embedded in becomes unusable.

## Collecting an answer from within the question <a href="#collecting-an-answer-from-within-the-question" id="collecting-an-answer-from-within-the-question"></a>

Of course, being able to actually collect data and store it is the essence of form building.

`<iframe>` elements may communicate with their containing pages (in our case, the Formsort flow) by passing messages. The Formsort [Custom Question API](https://github.com/formsort/oss/tree/master/packages/custom-question-api) faciliates this with a few methods.

### Setting an answer value <a href="#setting-an-answer-value" id="setting-an-answer-value"></a>

To set an answer, use the custom question API's `setAnswerValue` method with the value you want to store. Like answers to other questions in Formsort, this value will be stored at in the answers using the key specified in Answer variable name, you only need to provide the value.

If I just had a simple text input, my setup would look like this:

```
<input id="my-input" />
```

```
import { setAnswerValue } from '@formsort/custom-question-api';

const inputEl = document.querySelector('#my-input'):
inputEl.addEventListener('change', e => {
  setAnswerValue(e.target.value);
})
```

Formsort is type-aware, make sure to set the Expected answer type to match the type you are returning.

### Clearing an answer value <a href="#clearing-an-answer-value" id="clearing-an-answer-value"></a>

If your custom question has a notion of clearing a value, you can use the `clearAnswerValue` with no parameters.

Adding a button to clear the input from the previous example, we can add clearing behavior.

```
<input id="my-input" />
<button id="clear-btn">Clear</button>
```

```
import {
  setAnswerValue ,
  clearAnswerValue
} from '@formsort/custom-question-api';

const inputEl = document.querySelector('#my-input'):
inputEl.addEventListener('change', e => {
  setAnswerValue(e.target.value);
})

const clearBtnEl = document.querySelector('#clear-btn'):
clearBtnEl.addEventListener('click', e => {
  inputEl.value = '';
  clearAnswerValue();
})
```

## Getting the existing value in the frame <a href="#getting-the-existing-value-in-the-frame" id="getting-the-existing-value-in-the-frame"></a>

Sometimes, the user will already have an answer to the question you are asking, either because they are returning to a flow and you have enabled Returning responder behavior to retain their answers, or they have merely clicked back to a previous step when filling out their flow.

To initialize your question with the existing answer, use the `getAnswerValue()` method. Note that it returns a promise, since `<iframe>` can only communicate via asynchronous messages.

```
import { getAnswerValue } from '@formsort/custom-question-api';

getAnswerValue().then(value => {
  // Initialize your question with the existing value.
})
```

### Accessing other answers already collected <a href="#accessing-other-answers-already-collected" id="accessing-other-answers-already-collected"></a>

If your question needs to know other answers that have already been collected in the flow, you may obtain a promise for the the whole object of answers already collected using `getAllAnswerValues()`. You can trust that the answers will be of the types configured within Formsort.

```
import { getAllAnswerValues } from '@formsort/custom-question-api';

getAllAnswerValues().then(answers => {
  // Initialize your question with the existing values

  const birthday = answers['birthday'] // Access answers using their variable names as defined in Formsort.
})
```

If you're interested in the responder UUID, you may obtain that in a similar way using `getResponderUuid()`. This can be used for analytics on your end, or to obtain data about this user from your own systems that might not be present within Formsort.

For additional details, see the [Custom Question API](https://github.com/formsort/oss/tree/master/packages/custom-question-api) repo on GitHub.


# Image upload

An image uploaded from the user, optionally via the camera

This question type is used in cases where you would like the user to take a picture of a document, ID card or themselves.&#x20;

If you would like the responder to be able to upload files as opposed to take images, you can use the [file ](/adding-questions-and-content/question-reference/file-upload)content type.  If you would like the responder to sign a document, you can use the [signature ](/adding-questions-and-content/question-reference/signature)content type.

{% hint style="info" %}
If you don't want the responder to be held up by an image upload question, make sure to have the question be [optional](/conditions-and-logic#optional).
{% endhint %}

### Managing File Storage

By default, Formsort securely stores uploaded files and images with our [data storage service](/saving-and-retrieving-responses) — no setup required.\
If you prefer to manage your own storage, you can optionally configure an **S3** or **GCS** bucket as a custom file destination.

To set up a custom integration, follow our detailed guides for:

* [Amazon S3 setup](/integrations/integration-reference/s3)
* [Google Cloud Storage setup](/integrations/integration-reference/google-cloud-storage)

### Creating a filename pattern

You can opt to define a naming pattern for the images that your responders upload by enabling **Filename pattern** in the question settings.&#x20;

You can [template](/variable-templating) any relevant variable you would like to be included in the name of the file into the field. For example, if you type in `{{first_name}}` into the field, the value of the `first_name` variable will be used as the filename at upload.&#x20;

Formsort offers three custom variables for these types of file upload components, built right into the settings of the component itself: `sessionUuid`, `randomUuid`, and `currentTimeUTC`. These variables can also be templated into the filename pattern field to create dynamic filenames.

* `sessionUuid`: a uuid that is created **once**, when the form is loaded.&#x20;
* `randomUuid`: a uuid that is **created at and re-run** at each upload instance.&#x20;
* `currentTimeUTC`:  a timestamp that is **created at and re-run** at each upload instance.&#x20;

<figure><img src="/files/ZiH3oiCIphZZwDRoJQ0k" alt=""><figcaption></figcaption></figure>

You can use several of these variables at once, if required, and one pattern we suggest is `{{currentTimeUTC}}_{{responder_uuid}}`. This ensures uniqueness via the timestamp, while still tying the file to a particular user via the `responder_uuid` System Library variable.&#x20;

{% hint style="info" %}
Visit the [System Library variables](/response-data-collection-and-management/variable-schema/system-library-answers) section for details on built-in variables that provide information about the responder's environment and context.&#x20;
{% endhint %}

Note that these extensions will be added automatically. You should also set a fallback naming convention in case the variable is not defined by typing it into the **Filename pattern fallback** field. Values here cannot use templated variables.&#x20;

### Specifying file types allowed

You can whitelist [MIME (Multipurpose Internet Mail Extension) types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) by adding them to the **Allowed MIME types** field. If none are set, all MIME types will be allowed.&#x20;

{% hint style="info" %}
You should whitelist MIME types when generating signed upload URLs.
{% endhint %}

### Allowing images taken by a camera

By enabling **Allow camera** option in the settings menu with the question selecte&#x64;**,** you can give the responder the option to upload their image by taking a photo on their supported devices. The default camera on mobile devices is the front camera and you can mirror the image taken (depending on whether you expect your subjects to be people or documents) by enabling the **Mirror image?** option.

### **Allowing multiple answers**

Enabling **allow multiple uploads** lets the respondent upload more than one image as an answer to a question.&#x20;

### **Restricting image upload size**

You can enable a size restriction for a responder's upload. The minimum value must be greater than 0. If the restriction is not met, the responder will be shown an error and be prevented from moving forward in the flow.

<div align="left"><figure><img src="/files/yu9FtqVmoSjJKc6aHWuS" alt=""><figcaption></figcaption></figure></div>


# Number

Collects numbers.

This question type is used for situations when you are looking for a numerical answer (e.g. rating on a service, amount of money in the bank, etc.)

The answer to the number question is stored as number [data type](/response-data-collection-and-management/variable-schema#data-types).&#x20;

For phone numbers, you can use [phone](/adding-questions-and-content/question-reference/phone-number) content type.

## **Limiting answers to a certain range**

You can force the responder to pick numbers within a desired range by typing in enabling **Minimum value?** or **Maximium value?** options and typing in your upper or lower bound. This way you can ensure that your responder is not typing in negative numbers when they should not be or giving you a 11 out of 10 for the services you have rendered.

## Formatting answers

Formatting the input field for a number question can help the responder anticipate what format the answer should be in.

By clicking **Is money?** you can add a dollar sign in front of the input field to let the responder know that they are expected to put in a dollar amount. Alternatively, you can put in text in the **Suffix** field which will show up to the right of the input field.

## Sliders&#x20;

Changing the **input style** to **slider** will enable the responder to submit answers by dragging a slider.&#x20;

You can add labels to both ends of the slider to inform the responder about what the number answer would imply.

## **Allowing multiple answers**

Enabling **Allow multiple answers** lets the respondent type more than one number as an answer to a question.&#x20;

## Default Answer

**Default answer** will allow you to preset the value of a number question. The values of these answers can be changed by the responder when they arrive at the step.&#x20;

<figure><img src="/files/4upyklsVANvdO5HR1mKa" alt=""><figcaption></figcaption></figure>

#### Another answer/ variable

<figure><img src="/files/KsF2RSdMBASersFSFT3o" alt=""><figcaption></figcaption></figure>

This setting will give access to other question variables in your flow, allowing you to set the value of the number question using the value of a different question - as long as the data type matches.&#x20;

Since the **Number** component is a `number` [data type](/response-data-collection-and-management/variable-schema#data-types), you can use `number` returns from  [calculated variables](/response-data-collection-and-management/variable-schema/calculated-answers), [API variables](/response-data-collection-and-management/variable-schema/api-answers), and other question components that return a `number` value.&#x20;

#### Constant

Use when you want to set a "constant" value that doesn't depend on possible different answers to other questions.&#x20;


# Payment

Payment information, collected using a third party partner.

Formsort does not store credit card information - we use the third-party payment provider to obtain a payment token that is provided as an answer. You are responsible for using the token to actually charge the customer.

As a consequence, if a user enters payment information and then reloads, their credit card information will not be visible, although the fact that it has been collected will be known, and they will be allowed to continue.

## Stripe <a href="#stripe" id="stripe"></a>

[Stripe](https://www.stripe.com/) allows for simple credit card payments online. The [Stripe Integration page](/integrations/integration-reference/stripe) guides you through enabling **Stripe** in your flow. Once you have entered **Stripe** credentials, simply select 'stripe' as a 'provider' for a Payment question.&#x20;

If you do not wish to charge customers from within a Formsort flow, but rather, only aim to verify their card information, the **Allow charging the card immediately** option should be disabled.

If you do wish to charge customers from within a Formsort flow, the **Allow charging the card immediately** should be enabled. Once enabled, choose whether to charge users a fixed amount, or an amount calculated by a [variable](/response-data-collection-and-management/variable-schema). From the flow, Users will  be asked to input credit card information. After they enter their information, a **Charge my card** button will appear automatically

{% hint style="info" %}
The amounts users will be charged must be defined as an integer representing the number of **cents** to charge the user. If you were hoping to charge, for example, $10.00, you would enter 1000. The same applies for money amounts calculated by variable.
{% endhint %}

## Affirm <a href="#affirm" id="affirm"></a>

[Affirm](https://www.affirm.com/) is a payment option well-suited for large purchases - rather than charging the full amount upfront, customers can pay monthly to finance their purchases.

When the `affirm` payment provider is chosen for a question, you must set three values:

* Production Affirm API key: Your production public API key.
* Beta Affirm API key: Your sandbox public API key.
* Lastly, the Checkout object template: The [checkout object](https://docs.affirm.com/affirm-developers/docs/create-a-checkout-object) to pass to the Affirm integration. Note that this is a templated string, so you can use variable templating to set values within this JSON object, such as the price, the responder's billing address, etc.


# Phone number

A phone number.

This question collects valid phone numbers from the responder. If the responder's input is not in the expected format, there will be an error message prompting to user to enter in a valid phone number.

The answer to the number question is stored as number [data type](/response-data-collection-and-management/variable-schema#data-types).&#x20;

## Showing input mask

By enabling **Show input mask**, you can format the input from the responder as (000)-000-0000 when they are entering their phone number.&#x20;

## International phone numbers

**International** option allows the responder to enter in numbers outside of the U.S. phone format. If you enable this option, the responder will be able to choose from a list of countries and their respective prefixes (+90, +1, etc.).&#x20;

If you would like to change the default country phone number, you can enable the **Default country** option and pick a country from the menu.&#x20;

## **Allowing multiple answers**

Enabling **Allow multiple answers** lets the responder type more than one phone number as an answer to a question.&#x20;


# Postal code

A ZIP code.

This question collects only ZIP codes from the responder. If you would like the responder to enter in their full address, you can use the [address ](/adding-questions-and-content/question-reference/address)content type instead.

## Validating ZIP Code

If the responder enters more than 5 digits, there will be an error message displayed. Formsort currently only supports 5 digit ZIP codes. If you would like to collect different type of postal codes, you can use the [text ](/adding-questions-and-content/question-reference/text-question)content type.


# Question group

Collects answers that consist of multiple fields. Allows for repeating set of sub-questions.

For complex data sets, nest sub-questions within main questions to create hierarchical structures. By doing this, you can easily analyze data about specific medications, including dosages, or family health conditions that affect siblings and parents. Repeat this question multiple times (unlimited, fixed number, or based on another variable answer) so responders can enter information as many times as they need.

Unlike most answers, a compound answer value is stored as an `object`, since it contains multiple parts.

```json
{
	"emergencyContact": {
		"name": "John",
		"lastName": "Doe",
		"email": "john@doe.com"
		"phone": "2345..."
	}
}
```

### Using compound answers in templated strings

If you would like to access one of the parts of a compound in a string template, you can use the `get` [template function](/variable-templating#template-formatting-functions).

e.g.

`We'll call {{emergencyContact | get 'phone'}} in case of emergency.` would render `We'll call 2345... in case of emergency.`

### Using compound answers in conditional logic

In the conditional logic editor, you can select fields of a compound question to conditionally show a question, step or question. e.g. If you want to show a boolean question “Renew insurance” when insurance is expired, you can set conditional logic as following.

<figure><img src="/files/99pjxyPjoi6tys0eSSUJ" alt=""><figcaption><p>Setting conditional logic based on compound fields</p></figcaption></figure>

### Repeating question group answers

It is possible to submit multiple answers to a compound question. The repetition count can be set to three different options.

* `Unlimited`: Responders can add as many answers as they want by clicking on +add in the flow itself.
* `Fixed number`:  Set a fixed number of answers for the responder to answer. e.g. 2 emergency contacts in an intake form.
* `Another answer/variable`:  Add a question prior to the compound question that will set the number of answers required in the compound question. e.g. First ask for the number of medicines or pass it via url, then use the compound question to ask for specific questions about the medicines as many times as set in the medication number question.

<figure><img src="/files/o9hQlzLzxVZWNdcAKxvl" alt=""><figcaption><p>Setting repetition style</p></figcaption></figure>

Repeating compound answers appear in form payload as array of objects. e.g.

```json
{
	"numberOfMedicines": 2,
	"medicines": [
		{
			"name": "Medicine 1",
			"dailyDose": 2,
		},
		{
			"name": "Medicine 2",
			"dailyDose": 3,
		}
	]
}
```

### Conditional fields

Fields (sub-questions) in a compound question can be shown based on other answers/variables just like regular questions. If you want to show a field based on other fields in the same compound question, you can set conditional logic in the logic editor. e.g. You have a compound question asking for work experience and you want to ask for “end date” only if the responder quit the job.

<div align="left" data-full-width="false"><figure><img src="/files/t8vK7QEn8XOFFZM1ussJ" alt=""><figcaption><p>Setting conditional logic</p></figcaption></figure></div>

<div><figure><img src="/files/QHk74jIxuwtlqIq0p2xS" alt=""><figcaption><p>End date disabled</p></figcaption></figure> <figure><img src="/files/GUmwlhsWJu5Pju4J3O9W" alt=""><figcaption><p>End date enabled</p></figcaption></figure></div>

### Layout in compound questions

You can have multiple fields in a row in a compound question — just like other questions in a step. You can drag/drop and reorder fields. We currently do not support moving fields in or out of a compound question.

<figure><img src="/files/wxHYlLfW6TwwiadyfKaH" alt=""><figcaption><p>Compound question layout</p></figcaption></figure>

### Copy-pasting compound questions

You can copy and paste compound questions just like other questions, but currently, copying and pasting fields inside a compound question (sub-questions) is not supported.

### Setting compound answers via URL parameter

If you'd like to pass a compound answer into the form instead of requiring the responder to answer a question, you can use object notation like in address answers. e.g.

`https://your-flow-url.com?emergencyContact[name]=John+Doe&email=john@doe.com`

Passing multiple compound answers via url is currently not supported. It will be addressed soon.


# Region

Pick a city/town, without specifying an address

Similar to an [address question](/adding-questions-and-content/question-reference/address), the **region** question allows collecting location data, but does not require a street address.

Sometimes you might not want to collect an address, but just a region where a responder is interested in or living. A good example would be if you're collecting information about where a responder might be looking to rent or buy a property, without knowing where exactly. That's what the region question is for!

## Autocomplete using Google Places API <a href="#autocomplete-using-google-places-api" id="autocomplete-using-google-places-api"></a>

Providing a Google Places API key will allow autocompleting the remainder of the address parts from the value that the responder types into the first field.

### Guessing the postal code

Most larger towns and cities in the United States have more than one corresponding ZIP code. Consequently, autocompleting a string like "brooklyn" is ambiguous when it comes to ZIP code.

If you need ZIP code, you can enable the **Guess postal code?** option. Guessing the ZIP code results in a geolocation call being made to the Google Maps Javascript API, to find the ZIP code as close to the center of the region as possible, if one exists.

{% hint style="info" %}
Your API key must permit the **Maps Javascript API** and **GeoCode API** in addition to the **Places API** if using **Guess postal code.**
{% endhint %}


# Signature

Collecting a signature from the responder.

This question type is used in cases where the responder needs to sign a document.&#x20;

{% hint style="info" %}
If you would like the responder to check a confirmation box instead of a signing, use [confirm](/adding-questions-and-content/question-reference/confirmation) type instead.
{% endhint %}

### Universal Signature

The universal signature component allows responders to either **type** their name and adopt a generated signature or **draw** their signature directly within the form.

**Type (adopted signature):** The responder enters their name and clicks *Adopt and sign*. This creates a valid electronic signature and submits signature metadata to your [answers endpoints](/integrations/getting-data-out#sending-answers) and [Formsort Data Storage](/saving-and-retrieving-responses). This method does **not** generate an image file for file upload integrations.

<div align="left" data-full-width="false"><figure><img src="/files/64QTUxtjvUPnR3LhfZMS" alt="" width="518"><figcaption></figcaption></figure></div>

**Draw:** The responder draws their signature using their mouse or touch input. This method behaves like the default signature component and will generate an image file that can be sent to your configured file storage integration.

<div align="left"><figure><img src="/files/jEoQsAyWdOOYqmhETh3q" alt="" width="482"><figcaption></figcaption></figure></div>

Both methods capture key signature data, including the signer’s name, timestamp, and signature method, which are included in webhook submissions and stored response data.

### Managing File Storage

By default, Formsort securely stores uploaded files and images with our [data storage service](/saving-and-retrieving-responses) — no setup required.\
If you prefer to manage your own storage, you can optionally configure an **S3** or **GCS** bucket as a custom file destination.

To set up a custom integration, follow our detailed guides for:

* [Amazon S3 setup](/integrations/integration-reference/s3)
* [Google Cloud Storage setup](/integrations/integration-reference/google-cloud-storage)

### Creating a filename pattern&#x20;

You can opt to define a naming pattern for the signature images that your responders upload by enabling **Filename pattern** in the question settings.&#x20;

You can [template](/variable-templating) any relevant variable you would like to be included in the name of the file into the field. For example, if you type in `{{first_name}}` into the field, the value of the `first_name` variable will be used as the filename at upload.&#x20;

Formsort offers three custom variables for these types of file upload components, built right into the settings of the component itself: `sessionUuid`, `randomUuid`, and `currentTimeUTC`. These variables can be [templated](/variable-templating) into the **Filename pattern** field to create dynamic filenames.

* `sessionUuid`: a uuid that is created **once**, when the form is loaded.&#x20;
* `randomUuid`: a uuid that is **created at and re-run** at each upload instance.&#x20;
* `currentTimeUTC`:  a timestamp that is **created at and re-run** at each upload instance.&#x20;

<div align="left"><figure><img src="/files/sPn0kIfqxSA4elFU4X39" alt="" width="563"><figcaption></figcaption></figure></div>

You can use several of these variables at once, if required, and one pattern we suggest is `{{currentTimeUTC}}_{{responder_uuid}}`. This ensures uniqueness via the timestamp, while still tying the file to a particular user via the `responder_uuid` System Library variable.&#x20;

{% hint style="info" %}
Visit the [System Library variables](/response-data-collection-and-management/variable-schema/system-library-answers) section for details on built-in variables that provide information about the responder's environment and context.&#x20;
{% endhint %}

Note that these extensions will be added automatically. You should also set a fallback naming convention in case the variable is not defined by typing it into the **Filename pattern fallback** field. Values here cannot use templated variables.&#x20;


# SSN

Social Security Number question

The **Social Security Number question type (SSN**) allows you to capture social security numbers in a standard format with some additional security settings.

<figure><img src="/files/PhB7prGVcnKEEdf2kRrb" alt=""><figcaption></figcaption></figure>

**Show input mask:** This will show \_ \_ \_ - \_ \_ - \_ \_ \_ \_ when entering the SSN

**Hide characters:** This will replace previously entered characters with a bullet point (•) to hide sensitive data.

{% hint style="danger" %}
Take special care with collecting social security numbers and other sensitive data.
{% endhint %}


# Yes/No&#x20;

A yes or no question.

<figure><img src="/files/wg1ZG5Zwu1PVPZakodZE" alt=""><figcaption></figcaption></figure>

Yes/no questions are a quick way to add a binary question type to your form. The value types of these questions are stored as the boolean `true` or `false` data type.

```
{
  "Am I a yes/no question?": true
}
```

### Customizing the question <a href="#changing-the-labels" id="changing-the-labels"></a>

A boolean question may use any of the [styles of the select question](/adding-questions-and-content/question-reference/select#styling-choices), but defaults to displaying as buttons.

<figure><img src="/files/x3NHeBcHfiX5MuL6vb9S" alt=""><figcaption></figcaption></figure>

Yes/no questions only have two choices, but the "yes" and "no" labels of the choices can be changed in the choices tab.&#x20;

The boolean true/false values of the choices cannot be changed.

<figure><img src="/files/X4ZrPbzTO0ksqwwAx0jn" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
If you'd like to have more than two choices mapped to true or false, use a [select](/adding-questions-and-content/question-reference/select) question and change *Choice value type* to boolean.

Similarly, use a select question if you need an alternative value type to Boolean returned altogether.
{% endhint %}


# Content

Show information.

Formsort has several components available for presenting your information - from Statement blocks to help explain the ideas of a page, to Divider lines which can be used to break up content, and even Video embeds so you can show off those video editing skills.&#x20;

Take a look at the reference in the sub-sections here to find which content components help bring your form together.&#x20;

***

### General Settings

Similar to Question components, there are universally-available settings for all content components - with only some slight variation of availability depending on the component. &#x20;

<figure><img src="/files/B9SjuT04oFwLuUTaq2ES" alt=""><figcaption></figcaption></figure>

#### Content label

A text field that can be used to add copy to the statement block. You can use markdown here to change the styling of your font, or add [templated variables](/variable-templating) to show the user dynamic information based on their answers.

#### Question ID

Allows you to create an HTML ID for the specific question. This aids in targeting elements for custom CSS application.&#x20;

#### Has info Text?&#x20;

To provide additional clarification or context for a question without cluttering your design, add info text to a question by enabling **Has info text?** with a question selected.

The link can be text or an icon, and a tooltip and a modal display style are available.


# Statement

Displays content that does not require an answer from the responder.

Using a **Statement** component type, you can add any image, video or text. This is commonly used for explanation of concepts or as breaks in between various sections of your flow.&#x20;

<figure><img src="/files/44eFuk4IFuPbM8Nn6Mfi" alt=""><figcaption></figcaption></figure>

## Styling text

Like in any other component, you can use [markdown](/styling/customizing-appearance/typography#markdown-styling-support) to change the font properties of the Statement text.&#x20;

You can also change the alignment of the component block by enabling **Text alignment override** to override the default alignment setting.&#x20;

<figure><img src="/files/IsWDqTGc2kFMepaYrlZI" alt=""><figcaption></figcaption></figure>

### Images and video

To add images and videos, you can enable **Has image or video** toggle and use the **positioning** and **image style** menus to position and size the content appropriately.&#x20;

<div align="left"><figure><img src="/files/lA0bgOEi90iXXHLzl5wv" alt="" width="409"><figcaption></figcaption></figure></div>

#### Positioning

**Top, Right, Bottom, Left, Center** are all valid keywords for background image positions - the image will be placed in the respective position within the element, which can differ between the X and Y axis.

### Adding links

You can also use informational content to embed links. The default setting opens the links in a new tab for the responder. By enabling **open links inline**, you can ensure that the links are opened in the same tab.&#x20;

### Collapsible content

By enabling **Is collapsible?**, you can have content that expands and collapses, which can show or hide more details when the responder clicks on the header. You can change the default behavior to collapsed or expanded.&#x20;

<figure><img src="/files/omgBGPUMLSPuZoZJQ32g" alt=""><figcaption></figcaption></figure>


# Image

An image shown to the user.

Using image content, you can add an image to show to the responder. This is commonly used for graphics or any other visual content to help explain concepts.

If you would like to include text with your image, use the [informational ](/adding-questions-and-content/content-reference/statement)content type instead. Also if you would like to include a video, use the [video](/adding-questions-and-content/content-reference/video-content) content type.&#x20;

## Adding ALT text

If you type in text into the **Alt** field that appears in the settings menu with the question selected, you can add alternative (ALT) text to your image.

{% hint style="info" %}
Adding ALT texts to your images will improve accessibility for the visually impaired, who might be using assistive devices such as screen readers.
{% endhint %}


# Next button

The next button, but rendered inline with the content.

This content type can be used when the Next button needs to pulled out of the [layout](/styling/customizing-appearance/content-area-and-form-layout#layout-editor), and placed inline with the content of the page. A common use case is "Begin Quiz" or "Start Survey" buttons that are placed above images or text.&#x20;

If the responder clicks the next button while there are remaining unanswered questions on the current step, they will be scrolled to the first unanswered question, with that question highlighted.

#### Next button settings

You can customize the label of the next button, assign a Question ID, and optionally append a variable value to it. The next button variable returns a Boolean—`true` if the button is clicked, and `false` if it is not.

<figure><img src="/files/NZDtCGmxKl9CtOnlWy4c" alt=""><figcaption></figcaption></figure>

### Overrides

#### Next button overrides

You can override the overall style and horizontal alignment of the next button in the **style** menu of a next button component.&#x20;

<div align="left"><figure><img src="/files/Sq2toqqLBx2QXZQhrL9F" alt=""><figcaption></figcaption></figure></div>

{% hint style="info" %}
For more detailed instructions on styling options, you can refer to [this section](/styling/customizing-appearance/buttons#next-button).
{% endhint %}

#### Step-level overrides

If you would like to use your default next button styling, but would only like to change either the button text or horizontal alignment, you can do so by going to the style tab with the step selected and using the **Next button overrides** option.

<figure><img src="/files/Jvd8JwNsKw5mJwQS0pTp" alt=""><figcaption></figcaption></figure>


# Video

A video shown to the responder

Using video content, you can add a video in your flow. This is commonly used for graphics or any other visual content to help explain concepts.

Click the "**+Add Image**" box under the **URL** toggle to open to modal. Here, you will have two options for displaying video:&#x20;

1. Upload a video file to the studio (.mp4 and .mov)&#x20;
2. Provide a URL to a publicly available resource (i.e. a server) that is hosting the video content&#x20;

{% hint style="info" %}
Video-sharing websites, such as Youtube and Vimeo, are not available as a URL option. These sites do not allow sharing the video file, rather they provide a link to the page of their site. You may want to consider creating a [iframe question](/adding-questions-and-content/question-reference/iframe-question), and embedding the web page there.&#x20;
{% endhint %}

{% hint style="info" %}
The maximum size for the video content is 10MB.
{% endhint %}

## Configuring your videos

You can ensure that your video starts playing as soon as the video content is loaded, by enabling **Auto-play?** option in the settings menu that appears when the question selected.&#x20;

Additionally, you can set the image that you would like the responder to see until your content is loaded with the **Loading image url** option.

You also have the option to run your video in repeat by enabling **Loop?** and letting the responder decide when to play and pause the video by enabling **Show controls to user?**


# Divider

A separator between questions.

In order to delineate between two questions or any two logical parts of your step, you can insert a divider component.&#x20;

<figure><img src="/files/HqxXGg19gf3MjWmkGoV7" alt=""><figcaption></figcaption></figure>

## Divider placement

With the divider component selected, you can use the margin menu to change the placing of the divider in the **settings** menu.&#x20;

## **Line formatting**&#x20;

You can hide the line by disabling **Has line** option menu in the settings. This can allow the divider to function as a page break.&#x20;

In order to further customize the style of the line, you can refer to the [divider line styling](/styling/customizing-appearance/divider-lines) page.


# Map

A static map with a pin

Using map content, you can show a specific location to the responder. This is commonly used in cases when displaying store locations for a specific service.

{% hint style="info" %}
Based on the preferences of the responder, you can show them the nearest store available using map content.
{% endhint %}

## Google maps API key

Map content is drawn directly from Google Maps. In order to display a map, you need to have a [**Google maps API key**](https://developers.google.com/maps/documentation/javascript/get-api-key)**.**

## Display settings&#x20;

Using a number from 1-20, where 1 shows you the largest area,  you can change the **Zoom level** to control how big the area you would like to show in the map is.&#x20;


# Controlling the flow with conditions and logic

Modify your flow's behavior using answers collected.

Conditional logic allows you to create dynamic, personalized flows without duplicating efforts across multiple form versions. You can use logic to control when certain elements appear based on responder inputs.

Logic can be applied to:

* [Questions](/adding-questions-and-content/question-reference), [Steps](/creating-flows/building-a-new-flow/steps), or [Groups](/creating-flows/building-a-new-flow/organizing-using-groups) &#x20;
* [Redirects](/redirects-and-endings#redirects)
* [Calculated variables](/response-data-collection-and-management/variable-schema/calculated-answers)&#x20;
* [API variables](/response-data-collection-and-management/variable-schema/api-answers)

***

### Content order and conditional logic

Responders experience the flow according to the step order defined in the Content Editor—from Step 0 onward. Conditional logic doesn’t override this order, but it can hide or reveal elements based on logic that evaluates to **true** or **false**.

* If the logic evaluates to **true**, the element is shown.
* If it evaluates to **false**, the element is hidden.

{% hint style="warning" %}
**Important:** Logic does not *skip* responders to other steps. For example, applying logic to Step 30 will not cause the user to jump there from Step 1—they must progress sequentially unless explicitly redirected.
{% endhint %}

**Example flow:**

<figure><img src="/files/9nuRBlQfB2ELxYOxiOfR" alt=""><figcaption></figcaption></figure>

Step 1: *Do you have a cat or a dog?*

* If the responder selects **Cat**: logic on Step 2 evaluates to true → Step 2 is shown, Step 3 is hidden.
* If the responder selects **Dog**: logic on Step 2 evaluates to false → Step 2 is hidden, Step 3 is shown.

This simple pattern allows you to branch the experience without creating a separate flow.

### Simple logic editor

The **Simple Logic Editor** enables basic conditional rendering using one or more logical arguments.

Each condition consists of:

* A **variable** (e.g., answer, external variable)
* A **logical operator** (see the [operator reference](#logical-operator-reference) below)
* A **target value** to compare against

<figure><img src="/files/JCWUWTzjLcxVYI7Anqpv" alt=""><figcaption><p>Using the simple logic editor to conditionally render the first step</p></figcaption></figure>

{% hint style="warning" %}
Logic can only reference variables that are already defined earlier in the flow.\
You cannot apply logic to Step 1 based on a value defined in Step 3.
{% endhint %}

You can:

* Add multiple conditions using **+ Add condition**
* Group conditions using **+ Add group** for more complex logic

<figure><img src="/files/DKBaRuR6MsucHegmivaF" alt=""><figcaption><p>Creating a two-argument AND statement in the logic editor</p></figcaption></figure>

If you find yourself needing more flexibility, consider using [calculated variables](/response-data-collection-and-management/variable-schema/calculated-answers) or the advanced logic editor.

### Advanced logic

Advanced logic allows you to create boolean expressions across multiple answers using a freeform logic editor.

For even more control, use a **calculated variable** that returns a `true` or `false` value using TypeScript. This boolean value can then be referenced by simple logic elsewhere in the flow.

![](/files/EtWdVxidXjy92v6LKxY6)

***

### Optional?

By default, Formsort flows require all questions to be answered before moving forward. Toggling on **Optional?** allows responders to skip a question.

***

### Logical operator reference&#x20;

Logical operators evaluate expressions to determine whether a question, step, or group should be enabled.

<figure><img src="/files/F98dlBKYTgfqW8kzROWL" alt=""><figcaption></figcaption></figure>

| Operator                              | Description                                                                   |
| ------------------------------------- | ----------------------------------------------------------------------------- |
| **Equals**                            | The value exactly matches the expected value.                                 |
| **Does not equal**                    | The value does not match. Also true if the value is undefined.                |
| **Greater than**                      | The value is greater than the expected value. *(Numbers only)*                |
| **Less than**                         | The value is less than the expected value. *(Numbers only)*                   |
| **Matches regular expression**        | The value matches the regex pattern.                                          |
| **Does not match regular expression** | The value does not match the regex pattern.                                   |
| **Is defined on load**                | The value was set via URL param, POST body, or similar on form load.          |
| **Is defined**                        | Any value (including `false`) is present. Useful for gating logic.            |
| **Is not defined**                    | The value has not been set.                                                   |
| **Had a loading error**               | The value couldn’t be loaded or computed due to an error (e.g., API failure). |


# Advanced logic

Enabling **Advanced logic** allows you to write custom boolean expressions using [MongoDB’s query and projection operators](https://www.mongodb.com/docs/manual/reference/operator/query/), giving you full flexibility over conditional rendering in your flow.

Instead of using a visual logic builder, you write logic directly in JSON format. Each answer variable maps to a field, and standard MongoDB query operators evaluate those fields.

#### Why JSON, not Drag-and-Drop?

While we may support a visual builder in the future, the JSON-based syntax is:

* Powerful and expressive
* Lightweight and easy to copy/paste
* Flexible enough for most complex logic needs

### Common patterns

#### **Both conditions must be true** — `$and`

Enable an item only if **both** conditions are satisfied:

* `distance_to_office` is less than or equal to 75
* `is_qualified` is true

```javascript
{
  "$and": [
    {
      "distance_to_office": {
        "$lte": 75
      }
    },
    {
      "is_qualified": true
    }
  ]
}
```

Read more about [$and](https://docs.mongodb.com/manual/reference/operator/query/and/).

#### **At least one condition is true -** $or

Enable an item if **either** of the following is true:

* `is_parent` is true
* `has_parent_consent` is true

```javascript
{
  "$or": [
    {
      "is_parent": true
    },
    {
      "has_parent_consent": true
    }
  ]
}
```

Read more about [$or](https://docs.mongodb.com/manual/reference/operator/query/or/).

#### **Answer is one of several values** — `$in`

Check if an answer matches **any** value from a list.

Enable if `country` is **CA**, **MX**, or **US**:

```javascript
{
  "country": {
    "$in": [
      "CA",
      "MX",
      "US"
    ]
  }
}
```

Read more about [$in](https://docs.mongodb.com/manual/reference/operator/query/in/).

#### **Answer is&#x20;*****not*****&#x20;in a list of values — `$nin`**

Check that an answer **does not** match any value from a list.

Enable if `state` is **not** NY or PA:

```javascript
{
  "state": {
    "$nin": [
      "NY",
      "PA"
    ]
  }
}
```

Read more about [$nin](https://docs.mongodb.com/manual/reference/operator/query/nin/#mongodb-query-op.-nin).

#### **Answer is defined** — `$exists`

Enable if a variable has **any value** (even `false`):

```javascript
{
  "income": {
    "$exists": true
  }
}
```

This mirrors the behavior of the **Is defined** simple logic operator.

Read more about [$exists](https://docs.mongodb.com/manual/reference/operator/query/exists/).


# Variable templating

Personalize your flows by reusing answers you've collected.

Variable templating allows you to dynamically insert answers collected from your flow into other content, such as question labels and redirects. This enables a more personalized and dynamic user experience.

### Inserting Answer Values into Content

You can reference any previously collected answer by wrapping its variable name in double curly braces:

```
Hello, {{first_name}}!
```

If the user entered **Susana** as their `first_name`, the above would render as:

```
Hello, Susana!
```

This syntax is similar to templating systems like **Mustache** or **Handlebars**.

{% hint style="info" %}

#### Important Behavior to Know

* **Templated variables must be defined before use.**\
  Formsort does not allow referencing variables on steps that come **before** the variable is collected.
* **If a variable is missing, its name will be shown as-is.**\
  For example: `Hello, {{first_name}}!` will render exactly like that if `first_name` hasn’t been defined.
  {% endhint %}

***

### Where Variable Templating Works

Templating is most commonly used in text content (like question labels), but it’s supported almost everywhere across a flow. Some examples include (but are not limited to):&#x20;

#### Text and Question Labels

Personalize instructions and messages dynamically based on user input:

<div align="left"><figure><img src="/files/N54Pxaj17fUqsh06xYmt" alt="" width="381"><figcaption></figcaption></figure></div>

#### Redirect URLs

Redirect users to dynamic URLs based on their answers.

**Example:**\
Forward the user's name to the next destination:

&#x20;![](/files/KJBBYfgbCL7zSGOQOK5o)

***

### Template Formatting Functions&#x20;

Template functions let you format answer values before they are displayed, making your flow content cleaner and more user-friendly.

#### Using template functions <a href="#using-template-functions" id="using-template-functions"></a>

Sometimes raw answers—especially numbers—need formatting to be user-friendly. Template functions allow you to transform these values within your templates using the pipe symbol (`|`).

**Example:**\
You might have a variable called `product_price` from an API that returns a number:

```
Price: {{product_price}}.
```

If `product_price = 1499.95`, the result would be:

```
Price: 1499.95
```

This isn’t ideal for displaying prices. Instead, use the `usd` function to format the number as US dollars:

```
Price: {{product_price | usd}}.
```

This would render as:

```
Price: $1,499.95
```

Much better.

#### Chaining template functions <a href="#chaining-template-functions" id="chaining-template-functions"></a>

You can apply multiple formatting functions by chaining them with `|`.

**Example:**

```
Price: {{product_price | round | usd}}.
```

This first rounds the number, then formats it as currency:

```
Price: $1,500
```

### Available functions <a href="#available-functions" id="available-functions"></a>

Here’s a list of supported functions you can use within templates:

<table><thead><tr><th width="166.54547119140625">Function</th><th>Description</th></tr></thead><tbody><tr><td><code>capitalize</code></td><td>Capitalizes the first letter of a string.</td></tr><tr><td><code>ceil</code></td><td>Rounds <strong>up</strong> to the nearest integer.</td></tr><tr><td><code>default "text"</code></td><td>Displays fallback text if the variable is not defined. Example:<br><code>Allergies: {{allergies | default "none"}}</code></td></tr><tr><td><code>floor</code></td><td>Rounds <strong>down</strong> to the nearest integer.</td></tr><tr><td><code>get "key"</code></td><td>Gets a nested value from an object.<br>Example: <code>{{user_address | get "city"}}</code></td></tr><tr><td><code>label</code></td><td>Returns the <strong>label</strong> of a selected choice (useful with multiple choice questions).</td></tr><tr><td><code>lowercase</code></td><td>Converts text to lowercase.</td></tr><tr><td><code>round</code></td><td>Rounds to the nearest whole number.</td></tr><tr><td><code>uppercase</code></td><td>Converts text to UPPERCASE.</td></tr><tr><td><code>usd</code></td><td>Formats a number as US currency.<br><code>1499.95</code> → <code>$1,499.95</code></td></tr></tbody></table>

***

### Need More Customization?

If none of the built-in functions meet your needs:

* **Use a** [**calculated answer**](/response-data-collection-and-management/variable-schema/calculated-answers) to pre-format your value before inserting it into templates.
* **Have a feature request?** Reach out via chat—we’d love to hear what would help!


# Redirects and endings

**Redirects** and **Endings** determine what happens when a responder reaches the end of a form—or any point within the flow where redirection is appropriate. Redirects can be configured globally (at the end of the flow) or locally (at a specific step), and both options support conditional logic for personalized experiences.

### Redirects

Redirecting users is essential for connecting multiple applications or flows, and Formsort offers flexible options to support complex navigation logic.

#### **Types of Redirects**

**1. Global (Default) Redirects**\
Set in the **Endings** group of the Content Editor, default redirects occur when a responder reaches the end of a form **without triggering any earlier step-level redirects**.

* If no default redirect is configured, the responder will see a plain confirmation screen with optional text.

<figure><img src="/files/lYzN9q8w0AeyBNSWfKn6" alt=""><figcaption><p>Setting a default redirect</p></figcaption></figure>

**2. Step-Level Redirects**\
Redirects can also be defined on a per-step basis. Select a step and open the **Redirect** tab to configure.

* Step-level redirects can only use variables that have been defined by that step.
* If a step-level redirect has no condition, it will trigger automatically when the step is completed, finalizing the flow.

<figure><img src="/files/5uxvpH1PzLLMKVtDTi4I" alt=""><figcaption></figcaption></figure>

{% hint style="danger" %}
Redirects always finalize the form, unless configured to open in a new tab.
{% endhint %}

***

### Configuring Redirect Behavior

#### Open redirect in new tab?

If enabled, the redirect will open in a new browser tab. This is useful when linking to a supplemental page (e.g., terms and conditions) without finalizing the form.

#### Adding query parameters to redirect URLs

<figure><img src="/files/ihQ2auHbcTREy1GGEguY" alt=""><figcaption></figcaption></figure>

You can append query parameters to any redirect URL using the standard format:

```http
https://redirect-out-url.com?item1=value1&key2=value2
```

To pass values collected during the flow, use templating syntax:

```http
https://redirect-url.com?email={{responder_email}}
```

{% hint style="info" %}
**Fallback values**

When using templated variables, it is recommended to specify fallback values to avoid broken redirects if the variable is not defined at runtime.
{% endhint %}

***

### Redirecting into Formsort

Formsort supports redirection **into** a flow with data passed via URL parameters.

Example:

```arduino
https://flow.formsort.com/client/acme/flow/signup/variant/default/?utm_source=google
```

* Use [**external variables**](/response-data-collection-and-management/variable-schema/external-answers) to capture data from the incoming URL.
* Define the external variable in your flow schema (e.g., `utm_source`) so Formsort knows to bind the incoming value.
* Formsort will automatically ingest and assign the value to the matching variable.

{% hint style="info" %}
An external variable does not need to be created for answers that are already created in questions.
{% endhint %}

***

## Accessing the expected redirect URL as an answer

You can reference the **current expected redirect URL** as a [System library answer](/response-data-collection-and-management/variable-schema/system-library-answers).&#x20;

This is useful if you want to:

* Display the expected redirect URL to the user
* Include it in a submission payload
* Log it for analytics purposes

<figure><img src="/files/ces05VQBx6ZibskHIXD1" alt=""><figcaption></figcaption></figure>

***

## Endings

The **Endings** group is a default, non-removable section at the end of every flow. It serves two primary purposes:

#### 1. Configure Default Redirects

From the Endings group, define the default behavior when a responder completes the form:

* One or more conditional redirects using form logic
* A single unconditional redirect for all users

These act as a fallback if no step-level redirect has been triggered earlier in the flow.

<div align="left"><figure><img src="/files/jDZLD3odvy5WYqkQEAG8" alt="" width="563"><figcaption></figcaption></figure></div>

#### 2. Display Confirmation Text

If no redirect is configured, you may provide a simple **confirmation message** to be shown as the final screen.

* The screen is unstyled and cannot be customized.
* Use only if you have no destination to send users to or wish to keep the experience minimal.

<figure><img src="/files/pgPCALe3af0orlALjM3t" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
⚠️ We generally recommend using a styled last step or redirect URL instead of  plainconfirmation text for better user experience and branding consistency.
{% endhint %}


# Field validation

Field validation ensures that the data collected in your flows meets defined formatting, business, and quality requirements. Formsort supports both built-in and custom validators.

***

### Built-in Validators

Formsort includes many default validators for common input types:

* **Number questions**: Enforce min/max value, integer constraints, and step intervals.
* **Email fields**: Automatically validate email formatting.
* **Text fields**: Can be configured with length or character restrictions.

These default validators cover the most common cases. For advanced use cases, **custom validators** are available.

***

### Custom Validators (Enterprise Only)

Custom validators allow you to define your own validation logic using regular expressions, custom code, or asynchronous API calls. They are defined at the **workspace** level and can be reused across flows.

<figure><img src="/files/OkpodK9oqKRaBvQ9OpUu" alt=""><figcaption><p>Custom validators are defined at the workspace level</p></figcaption></figure>

### Defining Custom Validators

Custom validators are composed of two main parts:

#### **1. Validator Metadata**

Metadata includes:

* **Name** and **description** (used for identification in the Studio)
* **Answer type** (e.g., string, number, object) – this controls which questions or variables can use the validator

<div align="left"><figure><img src="/files/v7EG1B8p3jHgJHX8ScdT" alt="" width="281"><figcaption></figcaption></figure></div>

{% hint style="info" %}
Ensure the validator's answer type matches the target question’s type—e.g., string validators won't apply to number fields.
{% endhint %}

#### **2. Validation Rules**

Validators can contain multiple rules. Each rule must pass for the value to be considered valid.

<div align="left"><figure><img src="/files/hQeFnMqZacP6Ze0VYY3y" alt="" width="275"><figcaption></figcaption></figure></div>

#### **Rule Severity**

* **Error**: Blocking. The responder cannot proceed unless the input is corrected.

<div align="left" data-full-width="false"><figure><img src="/files/5IOPwlZ7Eovg1lMnnvga" alt="" width="375"><figcaption></figcaption></figure></div>

* **Warning**: Non-blocking. The responder is alerted, but may choose to proceed.

<div align="left"><figure><img src="/files/WX8tiWlluvTjXDqG3aj8" alt="" width="375"><figcaption></figcaption></figure></div>

### Regular expression rules

Regular expressions (`regex`) are available only for string inputs. You can create rules for inputs that **must match** or **must not match** a given pattern.

**Example: Twitter Handle Validator**

| Rule Type          | Pattern           | Error Message                                                                   |
| ------------------ | ----------------- | ------------------------------------------------------------------------------- |
| **Must match**     | `^[a-zA-Z0-9_]+$` | A username can only contain alphanumeric characters (A–Z, 0–9) and underscores. |
| **Must match**     | `^.{1,15}$`       | Handles must be between 1 and 15 characters long.                               |
| **Must not match** | \`Twitter         | Admin\` (case-insensitive)                                                      |

{% hint style="info" %}
Rules 1 and 2 (and probably 3) can be collapsed into a single regular expression that checks for both, which would be  `^[a-zA-Z0-9_]{1,15}$.`

Splitting out rules is preferred, as it allows for more targeted error messages. We can warn users with a specific message when they have an invalid character OR their handle is too long, making it easier to see what's wrong with the input and correct it.
{% endhint %}

<div align="left"><figure><img src="/files/56GUn85hViaSjpNGSMmz" alt=""><figcaption></figcaption></figure></div>

### Custom code rules

For more flexibility, you can write custom validation functions using TypeScript.

**Example:**

```typescript
function myFunction(value: string): ValidatorResult | undefined { // readonly line
  if (value.match(/admin|twitter/i)) {
    return {
      severity: 'error',
      message: 'Handle cannot contain the words "admin" or "twitter"'
    }
  }
  if (value.match(/[^a-zA-Z0-9_]/)) {
    return {
      severity: 'error',
      message: 'A username can only contain alphanumeric characters (letters A-Z, numbers 0-9) with the exception of underscores.'
    }
  }
  if (value.length > 15) {
    return {
      severity: 'error',
      message: 'Usernames cannot be more than 15 characters long'
    }
  }
}
```

<figure><img src="/files/FI05CcrP6ZyaOIGWFKLp" alt=""><figcaption><p>Writing a custom code validator</p></figcaption></figure>

* Return a `ValidatorResult` when the input is invalid.
* Return `undefined` when input passes validation.

{% hint style="info" %}
Note: Only the first validation error is shown—custom functions exit at the first `return`.
{% endhint %}

### Async custom code

Enable **async** mode to fetch data from external sources during validation using `await`.

**Example (hypothetical Twitter handle check):**

```typescript
function myFunction(value: string): Promise<ValidatorResult | undefined> { // readonly line
  const res = await fetch('https://api.twitter.com/check-handle');
  const handleInfo = await res.json();
  if (handleInfo.taken) {
    return {
      severity: 'error',
      message: 'This handle has already been registered'
    }
  }
}
```

{% hint style="info" %}
You must enable the **Async** checkbox in the validator configuration.
{% endhint %}

### Testing validators

Use the **Test** tab to try out inputs and preview validation behavior:

* **Regex rules**: All rules are evaluated in parallel. Violations for each rule are shown independently.
* **Custom functions**: Only the first failed check is returned.

**Examples:**

<figure><img src="/files/JYZGGGrLqoNuaWJrpUXi" alt=""><figcaption><p>Rule 1 being enforced</p></figcaption></figure>

<figure><img src="/files/Y668gaDphxAtTp8wzpeu" alt=""><figcaption><p>Rule 2 being enforced</p></figcaption></figure>

<figure><img src="/files/a8B4PjE0ICHOlCo4piD2" alt=""><figcaption><p>Rule 3 being enforced</p></figcaption></figure>

Using the Regular Experssion UI (described [above](#regular-expression-rules)) has the added benefit of evaluating all rules concurrently, and outputting an error message for each rule that is violated:&#x20;

<figure><img src="/files/8kyGQP6yDdhhYx7x8g0E" alt=""><figcaption><p>All rules being enforced at once</p></figcaption></figure>

***

### Using custom validators in a flow

Once created, custom validators become selectable in Question settings (if the question’s data type matches the validator).

<figure><img src="/files/AAvpe6k5blxat8ApGn9T" alt=""><figcaption><p>Picking a validator within the content editor</p></figcaption></figure>

***

### Updating custom validators

You can modify validators at any time. However:

* **Flows using the validator need to be redeployed to pick up changes.**
* Use the **Show usages** option to view which flows depend on a validator.

<figure><img src="/files/0SVRDQwXIq83WQj1kMEx" alt=""><figcaption></figcaption></figure>

### Deleting Validators

Custom validators can be deleted from the validator list.

* **Flows already using the validator will continue to function.**
* Deleted validators will no longer be available to assign to new questions or answers.


# Flow and variant management

Manage your Studio.

Flows and variants are the building blocks of how you structure, test, and maintain your form experiences in Formsort. This section covers how to organize and edit your flows, archive and restore them, manage variant versions, and monitor flow start activity. Whether you're iterating on design, setting up redirects, o r keeping track of usage, this guide will help you stay in control of your flow lifecycle.

### Editing Flows&#x20;

<figure><img src="/files/CaFcx8ofs1JGveQHBj9T" alt=""><figcaption><p>The top-level view of flows in the Studio</p></figcaption></figure>

You can update the **Display name** of a flow to help you stay organized.

{% hint style="info" %}
This change only affects how the flow appears in your Studio. It does **not** update the URL slug associated with the flow.
{% endhint %}

### Archiving Flows

To archive a flow, select it from your list of flows and click **Archive**.\
Archiving will:

* Remove the flow from the visible list in your Studio
* Prevent both new and returning responders from accessing it
* Automatically archive all of its variants

Archiving is reversible—you can re-activate a flow at any time if needed.

<figure><img src="/files/rtWyRams3JFNxPLIwroi" alt=""><figcaption><p>Archive flow settings</p></figcaption></figure>

#### Redirect to URL&#x20;

When archiving a flow, you can optionally set a **Redirect to URL**. This ensures that any new or returning responders who attempt to access the archived flow are automatically redirected to a URL of your choosing.

Redirects are especially helpful when:

* There are existing links (e.g. in emails or ads) still pointing to the archived flow
* A returning responder has a cached variant URL and attempts to revisit the flow

If no redirect is set, users will see a generic “failed to load” screen.

{% hint style="info" %}
See Split Testing for more on iterating on flows and archiving old variants when it’s time to deprecate them.
{% endhint %}

### Viewing archived flows&#x20;

To view an archived or deleted flow, change the filtering options to the right of the flow table's search to make archived and deleted flows visible.

<figure><img src="/files/KJqUNI2xTlRo8IVhrLNl" alt=""><figcaption></figcaption></figure>

***

## Variants

Variants are versions of a flow that allow you to test different content or styles while maintaining the same core objective. Think of them as A/B tests for your form. By using variants, you can continuously iterate on your flow to enhance the responder experience.

<figure><img src="/files/MGNLvy1pmQ8k3bWJJfXB" alt=""><figcaption><p>The variant list for the Follow-Up-Flow</p></figcaption></figure>

The **Integrations editor** for a flow is located in the variant-list view of the studio. Since all variants within a flow collect the same data, it makes sense to direct all submissions to the same integrations endpoint.

### Duplicating variants

The **duplicate** **variant** feature allows you to make *variations*. These changes can be wildly different from one another in terms of style and content, but should still have the same goal and collect the same information as the other variants in the same flow.

To make a copy of a variant, you can either:

* Select **Duplicate** from the dropdown while in the variant list section (seen in pic above).
* Click **Duplicate variant** at the top right when you have a variant open.

<figure><img src="/files/ow33xIPGYR8LU6ASCXw4" alt=""><figcaption></figcaption></figure>

In both cases, you'll be prompted with a modal offering three options:

1. Create a new variant in the current flow.
2. Duplicate this variant into another flow.
3. Create a completely new flow with the duplicate variant.

You'll also need to provide a new name for the duplicated variant. Optionally, you can assign a new URL slug.&#x20;

<figure><img src="/files/S66j1iBtp6G26mIb29Bp" alt=""><figcaption></figcaption></figure>

**Changing the URL Slug**

If you're not happy with your flow's current URL slug, the duplication process is the only way to create a new URL slug.

### Archiving Variants

To archive a variant, select it from the list and click **Archive**. Once archived, the variant becomes inaccessible to both new and returning responders—similar to the behavior of archived flows.

You can also set a [**Redirect to URL**](#redirect-to-url) when archiving a variant. This ensures that:

* Returning users with a cached variant ID are redirected to the new location.
* Anyone accessing a direct link to the archived variant is taken to the URL you specify.

Redirects are especially useful when links to the variant are already in circulation or bookmarked by users.

<figure><img src="/files/ZDIhjVZD7nmGO4w8I6ly" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
You can view your archived variants with the "Hide/Show archived" hamburger menu at the top right. It's a similar process to viewing [archived flows](#viewing-archived-flows).&#x20;
{% endhint %}

***

### Restoring Archived Flows and Variants

To restore an archived flow or variant:

1. Adjust the filter controls next to the search bar to display archived items.

<figure><img src="/files/f1AM7jhDvvS2zgvDPttB" alt=""><figcaption></figcaption></figure>

2. Hover over the archived item to reveal the **Restore** icon.

<figure><img src="/files/SGyNWOeU13nFhZv8H5F0" alt=""><figcaption></figcaption></figure>

3. Click the icon to unarchive the flow or variant.

{% hint style="danger" %}
Formsort allows you to restore archived flows and variants at any time. Because of this, names and URL slugs for archived items **cannot** be reused.
{% endhint %}

***

### Flow Starts

A **flow start** is recorded when a responder:

* Loads a flow and completes at least one step, **or**
* Restarts a flow, submitting a new set of answers.

If a responder returns to a previously started flow without starting over, it **does not** count as a new flow start.

{% hint style="info" %}
Flow start counts may take up to 15 minutes to update in the Studio.
{% endhint %}

#### Flow Start Limits

Each workspace has a monthly limit on flow starts, tracked per calendar month. Flow activity in **non-production environments** does **not** count toward your limit.

Flow starts are counted at the **workspace level**—this includes all flows and their variants. Once the monthly limit is reached, your workspace may incur **overage charges**.

To view your current usage, visit the **Subscription** tab in the Studio.

<div align="left"><figure><img src="/files/rLYcanCceu2Y4yHa9zpN" alt=""><figcaption></figcaption></figure></div>

{% hint style="warning" %}
Need to adjust your flow start limit? Reach out to us via Intercom, and we’ll connect you with our sales team.
{% endhint %}


# Content library

Reuse content blocks across multiple flows.

{% hint style="info" %}
The content library only available in the **Enterprise** tier
{% endhint %}

<figure><img src="/files/Kl3f3AMQk5I2ia2ubylk" alt=""><figcaption></figcaption></figure>

The content library allows for adding groups, steps, or questions to a central content library as **prototypes**. **Instances** of those prototypes can then be inserted into other flows. Changes to the prototype will be automatically reflected in the instances once they are re-deployed.

## When should content library be used?

Oftentimes, an organization's form flows will have many variations that share much of their content.

As an example, a hypothetical veterinary practice might have multiple form flows for new patients depending on the pet's species -- asking, for example, questions about fish tanks if the patient is a fish, but questions about litter boxes for cats -- but those flows might share much of their structure, such as basic information, insurance, mailing address.&#x20;

A simple way to achieve this kind of logic would be to express it within a single flow, using [conditional logic](/conditions-and-logic).

This approach becomes hard to scale once the number of permutations grows large, or the differences between flows start becoming larger and larger. Using conditions to choose between two paths is straightforward, but can be much easier to manager content using multiple flows if you have a dozen different branches.

<figure><img src="/files/gJLiPYONORga1577gwDj" alt=""><figcaption></figcaption></figure>

## Adding item prototypes to the content library

To add a group, step, or question to the content library, right click it, and choose **Add to library...**

<figure><img src="/files/XfJwJpRxU9SIOzV9WgoR" alt=""><figcaption><p>Adding a step to the content library</p></figcaption></figure>

You will be asked to provide some metadata, to make it easier to find the content later.

At this point, the item will be in the library, and you will see that the source step (the **prototype**) will have a small **In library** badge on it. It's possible to update the metadata in the settings sidebar, as well as to view the instances of this library item in other flows. Since we just added this item to the library, there are no instances of it in use yet.

<figure><img src="/files/VtnWYgo2eLgyZAl81KUL" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
**What is included in content library item instances?**\
\
Content library instances will contain everything that is in the original item, including the text, logic, and all settings associated with the group, step, or question. It is not currently possible to override any settings inside of the item on an instance basis.
{% endhint %}

## Using content library items in flows

Once there is content in the content library, it can be used in other flows.

You will see that the **Add** button corresponding to the content type will now be a dropdown, and provide a **From library...** action.

<figure><img src="/files/10TDKiTYoVgbyB08bq9j" alt=""><figcaption></figcaption></figure>

From here, you can search for the content library item you'd like to add:

<figure><img src="/files/qnDpNiLGhrjAm8ebh7o1" alt=""><figcaption></figcaption></figure>

Most content items will either depend on variables, or set answer variables. When inserting a content library item you will need to decide how to map those inputs and outputs to the rest of the flow, similarly to the mapping that happens in [copy and paste](/creating-flows/building-a-new-flow/copy-paste-of-form-content).

<figure><img src="/files/8kTAcCQOazyMSlpsqRv1" alt=""><figcaption></figcaption></figure>

Once added, the content library item will be shown with a **From library** badge. It is no longer editable, as its content is defined in the prototype that is referenced in the content library.

<figure><img src="/files/q9EpSC5Ie9XqP4QSFlQI" alt=""><figcaption></figcaption></figure>

**Detach** can be used to remove the association between this instance and the prototype. After being detached, the content (and any child content, like steps or questions within) will behave exactly like normally-created content will, and will no longer receive updates from the prototype.

To change the mapping of input and output variables, you can press **Edit.**

## Updating content library item prototypes

The power of the content library comes from the ability to not just share content, but to update it from a single place across many flows.

To update a content library item, first we must go to its prototype.

You can find a prototype for any content library item by:

1. Accessing the **Content library** section from the main page and finding the item in question.<br>

   <figure><img src="/files/XBQbxDocivNNf8gdsk7Z" alt=""><figcaption></figcaption></figure>
2. Clicking **View prototype** from any content library instance.

Content library items are not immediately updated when the prototype is updated.

To update the instances,

1. Once you are complete making changes, you must deploy the flow containing the prototype to production.
2. At this point, the instances will indicate which flows have not been redeployed since the prototype was updated.
3. You can click through to each of the flows and re-deploy them so that they pick up the latest changes.

## FAQs

* Where should I keep my prototype items?
  * We recommend creating a "Shared content" or "Item library" flow that contains your most frequently used items.&#x20;
* Can I have variants of content library items, or define "settings" for content library items?
  * Not directly, but you can take advantage of conditional logic within the content library item to create a "settings"-like behavior.
  * When you insert a content library item, they will have to map the input variables -- which can be set as constants.
* Can I nest library items inside of one another?
  * We do not support nesting of library items because versioning becomes quite difficult to understand, both from a technical and user perspective.
* Can I bulk re-publish flows using content library items?
  * It is not currently possible to bulk re-publish flows, primarily because there may also be other pending changes within those flows unrelated to the updated content library item.&#x20;


# JSON schemas

Exporting form payload metadata for validation or analysis.

Every variant revision is available as a [JSON schema](https://json-schema.org/) (Draft 07), which describes the possible answer payloads that a flow may produce. Exporting this JSON allows you to write validators on your end, or understand the possible payloads that a flow may produce using the many tools that use the JSON Schema format.

### Sample form schemas

Each of these examples shows how a form that only collected a single answer would be respresented in a JSON schema

{% tabs %}
{% tab title="string" %}

```javascript
{
  '$schema': 'http://json-schema.org/draft-07/schema#',
  'type': 'object',
  'properties': {
    'user_fname': {
      'type': 'string',
      'description': 'The first name of the user'
    }
  }
}
```

{% endtab %}

{% tab title="select (single)" %}

```javascript
{
  '$schema': 'http://json-schema.org/draft-07/schema#',
  'type': 'object',
  'properties': {
    'A choice': {
      'oneOf': [
        {'const': 'Value A'},
        {'const': 'Value B'}
      ]
    }
  }
}
```

{% endtab %}

{% tab title="select (multiple)" %}

```javascript
{
  '$schema': 'http://json-schema.org/draft-07/schema#',
  'type': 'object',
  'properties': {
    'A choice': {
      'type': 'array',
      'items': {
        'oneOf': [
          {'const': 'Value A'},
          {'const': 'Value B'}
        ]
      }
    }
  }
}
```

{% endtab %}

{% tab title="address" %}
Note that **address** is the only current object answer type with a distinct object type definition. Custom object types will be supported in the future.

```javascript
{
   "$schema":"http://json-schema.org/draft-07/schema#",
   "type":"object",
   "definitions":{
      "address":{
         "type":"object",
         "properties":{
            "raw":{
               "type":"string"
            },
            "address_1":{
               "type":"string"
            },
            "address_2":{
               "type":"string"
            },
            "city":{
               "type":"string"
            },
            "state":{
               "type":"string"
            },
            "postal_code":{
               "type":"string"
            },
            "country":{
               "type":"string"
            }
         },
         "required":[
            "address_1",
            "city",
            "state",
            "postal_code"
         ]
      }
   },
   "properties":{
      "An address":{
         "type":"array",
         "items":{
            "$ref":"#/definitions/address"
         }
      }
   }
}
```

{% endtab %}
{% endtabs %}

Note that none of the root object properties are marked required: if you are receiving events with answers on every step, you may receive partial payloads, so you cannot assume the presence of any answer.

### Accessing the JSON schema for a flow

The JSON schema for a given flow, variant, and revision combination is available at the following URL:

```
https://variant.formsort.com/flow-api/client/{CLIENT_ID}/flow/{FLOW_LABEL}/variant/{VARIANT_LABEL}/revision/{VARIANT_REVISION_UUID}/schema.json
```


# Validating flow schemas

Ensure form variants stay conformant to a defined schema.

**Flow schemas** allow you to define sets of answers that *must* appear within a flow. If any of the answers are missing, or are of the wrong type, content authors will be prevented from deploying.

As an example, if you have an onboarding flow, it is likely that a downstream consumer of the flow's information would consume a specific set of required fields, such as `user_name`, `email`, `date_of_birth`, etc. A flow schema for this object can be made, capturing that external dependency.

This gives developers working with teams that use Formsort confidence that less-tech savvy content authors do not accidentally remove critical information from a form flow while they make changes and run experiments. [Roles and permissions](/workspace-management/accounts/roles-and-permissions) can be further used to enforce that users don't make changes they should not be able to make.&#x20;

## Defining flow schemas

To add a schema, first, go to the **SCHEMAS** tab within a flow:

![](/files/TXCQSrF4OhWObzXdpTWm)

Then, name your schema. Here we create a schema that will contain all the information that we need to create an account:

![](/files/x3ajprnDntZwdOX6v2MG)

Once a schema is created, a new revision can be made. Here, the properties that the schema requires can be defined:

![](/files/CbpignLaHFGf1Zw6OHMT)

At this point, the schema has been defined, and is enabled by default.

![](/files/sQU1NJNgw0EJYq2BY8a2)

Here, it's possible to view the schema in Typescript...

```typescript
interface AccountInfo {
  first_name: string;
}
```

... as well as [JSON schema](https://json-schema.org/) formats.

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "AccountInfo",
  "description": "Everything needed to create an account",
  "type": "object",
  "required": [
    "first_name"
  ],
  "properties": {
    "first_name": {
      "type": "string"
    }
  }
}
```

## Verifying that a flow conforms to a schema

Once a schema has been defined and is enabled, any attempt to publish any variant within the flow must conform to all schemas defined within.

### Missing values

If a flow does not collect an answer, deployment will be prevented:

![](/files/MjaKAVscUkScQJNFWLM9)

### Incorrectly-typed values

If a flow contains an answer keyed with one of the schema properties, but the answer is not of the correct type, deployment will be blocked:

![](/files/dtvOjOQK0pK2TMtUZXF0)

{% hint style="info" %}
Keep in mind that partials of the schema values may be sent as the responder completes the flow - since questions collecting the answers can be spread across multiple steps.
{% endhint %}

## Updating a flow schema

To help with schemas that evolve over time, schema revisions are versioned using [semantic versioning](https://semver.org/).

Since deployment of Formsort revisions cannot be precisely timed with the deployment of downstream services that consume data from Formsort, for maximum safety, the process of updating a schema should proceed as follows:

* Define a new revision of the schema.
* Deploy all variants compliant to the new schema.
  * These variants should be compliant with both new and old revisions of the schema. Use calculated variables if you need to alias any fields.
* Remove support for the previous revision of the schema from downstream services.
* Disable the previous revision of the schema.
* Republish any variants

### Additive changes

Adding a property to an existing schema is a non-breaking change: existing consumers will be happy, since they can be unaware of the new property.&#x20;

Changes like this represent minor version updates (`0.1` -> `0.2`)

### Breaking changes

Removing a property, or changing its type, is a breaking change: existing consumers may break, since they expect the value to be present or typed a certain wait.

Changes like this represent major version updates (`0.1` -> `1.0`)

## Schema events

**Coming soon!**&#x20;

{% hint style="info" %}
In the future, we will emit events whenever a schema's fields become completed, change, or become incomplete.
{% endhint %}

## Disabling a flow schema

If you no longer need to enforce a schema, disable each of the schema's revisions.


# Events subscriptions

Receive notifications when important events happen within Formsort

**Events** allow you to receive notifications whenever notable events happen within the Formsort ecosystem. They can be configured using **Add an event subscription** within the **Events** tab.

<figure><img src="/files/spda6qdyYfpdW7vaWGb4" alt=""><figcaption></figcaption></figure>

### Add an event subscription

#### Event Type

<div align="left"><figure><img src="/files/dyNlDqCBSamodIBkkqTH" alt=""><figcaption></figcaption></figure></div>

As of this doc writing, the only triggering event available will be the `variant_revision_published` event. With this enabled, any variant published to any environment in any Flow, by any member of your team, will send an event notification to the destination you have configured.&#x20;

See [About the payload](#about-the-payload) for more information about the event payload sent.

#### Destination Type

<div align="left"><figure><img src="/files/ZOH01Ux0umDxKbdwkmYg" alt=""><figcaption></figcaption></figure></div>

You can choose to have a JSON payload sent to a webhook endpoint, or you can choose to have a notification sent to your Slack space.&#x20;

#### Details about your connection

You will need to set the endpoint address for your integration type. Descriptions are optional. Once the endpoint is set, you can **Send test event** to ensure everything has been configured correctly.&#x20;

If you choose to send a JSON payload, you will have to register a webhook endpoint URL.

<div align="left"><figure><img src="/files/FgZ02swlntj4pvieJM9V" alt="" width="417"><figcaption></figcaption></figure></div>

If you opt to send notifications to Slack, you will have to register a Slack URL.&#x20;

<div align="left"><figure><img src="/files/bKvbnEgpiSwDRKuCr9f6" alt="" width="405"><figcaption></figcaption></figure></div>

#### Signing Key

While not required, you can use a Signing Key to provide extra security for your Events payloads. Only one key can be set for Events subscriptions.&#x20;

***

### About the payload

#### jsonSchema

This is the [JSON Schema](https://json-schema.org/) representation of the answers object that this flow creates. Read more about this in the documentation about [JSON Schema at Formsort](/json-schemas).

#### flowContent

Flow content is a representation of the structure of the form, including the groups, steps, and questions within it. Read more about the [flow content data format](/flow-content-data-format) if you'd like to use it.

Below is an example webhook payload, with every component and configuration in Formsort represented.

```json
{
  "event": {
    "id": "5gQOVUsxs8o35vVHF4IKM",
    "type": "variant_revision_published",
    "createdAt": "2023-07-25T19:33:30+0000"
  },
  "payload": {
    "flowLabel": "sub-event-test",
    "environmentLabel": "production",
    "variantLabel": "main",
    "variantRevisionUuid": "5af64f19-42cb-45f6-8d69-2d1cd491016f",
    "jsonSchema": {
      "$schema": "http://json-schema.org/draft-07/schema#",
      "type": "object",
      "definitions": {
        "address": {
          "type": "object",
          "properties": {
            "raw": {
              "type": "string"
            },
            "address_1": {
              "type": "string"
            },
            "address_2": {
              "type": "string"
            },
            "city": {
              "type": "string"
            },
            "state": {
              "type": "string"
            },
            "postal_code": {
              "type": "string"
            },
            "country": {
              "type": "string"
            }
          },
          "required": [
            "address_1",
            "city",
            "state",
            "postal_code"
          ]
        }
      },
      "properties": {
        "text_multiline": {
          "type": "string"
        },
        "number": {
          "type": "number"
        },
        "date": {
          "type": "string",
          "format": "date"
        },
        "select_buttons": {
          "oneOf": [
            {
              "const": "1"
            },
            {
              "const": "0"
            },
            {
              "const": "3"
            },
            {
              "const": "4"
            },
            {
              "const": "2"
            }
          ]
        },
        "single_field_address": {
          "$ref": "#/definitions/address"
        },
        "grid_vegetables": {
          "oneOf": [
            {
              "const": "never"
            },
            {
              "const": "always"
            },
            {
              "const": "sometimes"
            }
          ]
        },
        "boolean": {
          "type": "boolean"
        },
        "this_will_load_example_com_h": {
          "type": "string"
        },
        "grid_question": {
          "type": "string"
        },
        "utm_params": {
          "type": "string"
        },
        "payment": {
          "type": "string"
        },
        "text_single_line": {
          "type": "string"
        },
        "email": {
          "type": "string",
          "format": "email"
        },
        "address": {
          "$ref": "#/definitions/address"
        },
        "api_response": {
          "type": "number"
        },
        "example_calc_var": {
          "type": "string"
        },
        "postal_code": {
          "type": "string"
        },
        "confirm": {
          "type": "boolean"
        },
        "grid_fruits": {
          "oneOf": [
            {
              "const": "never"
            },
            {
              "const": "always"
            },
            {
              "const": "sometimes"
            }
          ]
        },
        "phone_number": {
          "type": "string"
        }
      }
    },
    "publishedAt": "2023-07-25T19:33:29.072738+00:00",
    "publishedByEmail": "eric@formsort.com",
    "notes": null,
    "flowContent": {
      "groups": [
        {
          "label": "Questions",
          "id": "question-group",
          "steps": [
            {
              "questions": [
                {
                  "label": "# Boolean question",
                  "schemaKey": "boolean",
                  "id": "boolean-question",
                  "choices": [
                    {
                      "label": "Yes",
                      "value": true
                    },
                    {
                      "label": "No",
                      "value": false
                    }
                  ],
                  "optional": true,
                  "type": "boolean"
                }
              ]
            },
            {
              "questions": [
                {
                  "label": "# Select question",
                  "schemaKey": "select_buttons",
                  "choices": [
                    {
                      "label": "Strongly Disagree",
                      "value": "0"
                    },
                    {
                      "label": "Disagree",
                      "value": "1"
                    },
                    {
                      "label": "Undecided",
                      "value": "2"
                    },
                    {
                      "label": "Agree",
                      "value": "3"
                    },
                    {
                      "label": "Strongly Agree",
                      "value": "4"
                    }
                  ],
                  "optional": true,
                  "type": "select"
                }
              ]
            },
            {
              "label": "Step 1 label",
              "questions": [
                {
                  "label": "default address",
                  "schemaKey": "address",
                  "optional": true,
                  "type": "address"
                },
                {
                  "label": "single field address",
                  "schemaKey": "single_field_address",
                  "type": "address"
                }
              ]
            },
            {
              "questions": [
                {
                  "label": "# Confirm question",
                  "schemaKey": "confirm",
                  "optional": true,
                  "type": "confirm"
                }
              ]
            },
            {
              "questions": [
                {
                  "label": "# Custom question",
                  "type": "custom"
                }
              ]
            },
            {
              "questions": [
                {
                  "label": "# Date question",
                  "schemaKey": "date",
                  "optional": true,
                  "type": "date"
                }
              ]
            },
            {
              "questions": [
                {
                  "label": "# Email question",
                  "schemaKey": "email",
                  "optional": true,
                  "type": "email"
                }
              ]
            },
            {
              "questions": [
                {
                  "label": "# Grid question",
                  "optional": true,
                  "subQuestions": [
                    {
                      "label": "Fruits",
                      "schemaKey": "grid_fruits",
                      "type": "select",
                      "choices": [
                        {
                          "label": "Never",
                          "value": "never"
                        },
                        {
                          "label": "Sometimes",
                          "value": "sometimes"
                        },
                        {
                          "label": "Always",
                          "value": "always"
                        }
                      ]
                    },
                    {
                      "label": "Vegetables",
                      "schemaKey": "grid_vegetables",
                      "type": "select",
                      "choices": [
                        {
                          "label": "Never",
                          "value": "never"
                        },
                        {
                          "label": "Sometimes",
                          "value": "sometimes"
                        },
                        {
                          "label": "Always",
                          "value": "always"
                        }
                      ]
                    }
                  ],
                  "type": "grid"
                }
              ]
            },
            {
              "questions": [
                {
                  "label": "# Number question",
                  "schemaKey": "number",
                  "optional": true,
                  "type": "number"
                }
              ]
            },
            {
              "questions": [
                {
                  "label": "# Payment question",
                  "schemaKey": "payment",
                  "optional": true,
                  "type": "payment"
                }
              ]
            },
            {
              "questions": [
                {
                  "label": "# Phone question",
                  "schemaKey": "phone_number",
                  "optional": true,
                  "type": "phone"
                }
              ]
            },
            {
              "questions": [
                {
                  "label": "# Postal code question",
                  "schemaKey": "postal_code",
                  "optional": true,
                  "type": "postalCode"
                }
              ]
            },
            {
              "questions": [
                {
                  "label": "# Text question (single line)",
                  "schemaKey": "text_single_line",
                  "optional": true,
                  "type": "text"
                },
                {
                  "label": "# Text question (multi-line)",
                  "schemaKey": "text_multiline",
                  "optional": true,
                  "type": "text"
                }
              ]
            }
          ]
        },
        {
          "label": "Content",
          "id": "content-group",
          "steps": [
            {
              "questions": [
                {
                  "label": "# This is informational content",
                  "type": "informational"
                },
                {
                  "label": "",
                  "type": "divider"
                }
              ]
            }
          ]
        }
      ],
      "variables": {
        "external": {
          "utm_params": {}
        },
        "calculated": {
          "example_calc_var": "function myFunction(utm_params: string): string {\n  return utm_params\n}"
        },
        "api": {
          "api_response": {
            "apiUrl": "https://jsonplaceholder.typicode.com/todos/1",
            "apiResultAccessor": "userId"
          }
        }
      }
    }
  }
}
```

{% hint style="info" %}
This is currently in development, and may not be present for 100% of flows as we roll it out. When it's fully released as a 1.0 version, a comprehensive documentation of its structure will be provided. For now, use the above example as a guide.
{% endhint %}


# Flow content data format

Describing flows using JSON

### Overview

Formsort flows can be built entirely within the Studio, which provides an intuitive UI for managing your form logic and layout. However, advanced users may want to work directly with the underlying flow content data—a structured JSON format that mirrors the Studio’s interface. Accessing this format can be useful for tasks like auditing questions, [generating PDFs](https://github.com/formsort/api-examples/tree/main/answers-pdf-service), building internal review UIs, or importing long or complex forms.

### Where to Access Flow Content

Flow content is available in several places:

* [**Event subscriptions**](/event-subscriptions): Available in the `flowContent` property of event payloads.
* **Studio UI**: View past `flowContent` revisions in the History tab of a variant.
* &#x20;[**Admin API**](/formsort-admin-api/admin-api):
  * Fetching a variant revision includes a `flowContent` property.
  * Importing a flow requires providing a `flowContent` object.

### Structure Overview

The structure of this JSON format reflects closely the UI of the Formsort studio, wherein questions are contained within steps (which are usually displayed as a single page), which are themselves contained withing groups.

```json
{
  "groups": [
     {
       "steps": [
         {
           "questions": [
             {}
             // ... more questions
           ]
         },
         // ... more steps
       ]
    },
    // ... more groups
  ],
  "variables": {
    "external": {},
    "calculated": {},
    "api": {}
  },
}
```

### Groups

A **group** is a collection of steps (pages) that contain questions presented to the responder. Each group is designed to organize related questions for better flow and user experience. The structure and requirements for groups are as follows:

* **`label`**: (Required) A required string property that serves as the identifier or title for the group. This label is primarily for organizational purposes and may be shown to the responder in certain configurations.
* **`steps`**: (Required) An array of objects, each representing a step within the group. This is a required property, where each step contains a set of questions or content to be presented on a single page.
* **`enabledWhen`**: If set, the condition under which the group is enabled or visible. See [conditional logic](#conditional-logic) below.
* **`id`**: An optional string property that provides an internal identifier for the group.

### Steps

**Steps** organize the questions and content into individual pages or segments of the flow.&#x20;

* **`questions`** (Required): An array of `question` objects to be shown within this step. This array structures the questions that the responder will encounter on a single page or segment of the questionnaire.
* **`label`**: If set, provides a label or title shown to the responder at the top of the step. This offers additional context or instructions for the questions within this step.
* **`enabledWhen`**: If set, specifies the condition under which the step is enabled or visible, using MongoDB syntax for conditional logic based on answer variables.
* **`redirects`**: If set, contains an array of `redirect` objects that define conditional navigation paths upon the completion of the step. Each redirect can specify a URL to which the responder will be sent, based on answers within this step or previous steps.
* **`id`**: An optional identifier for the step, facilitating reference and navigation within the flow.

### Questions

**Questions** capture and define individual questions within a step. Each question object may vary in properties based on its type, but common aspects include:

* **`type`** (Required): Specifies the type of question (e.g., `boolean`, `select`, `text`). This property determines the structure and behavior of the question.
* **`label`** (Required): The text displayed to the responder as the question. It should clearly describe what is being asked.
* **`schemaKey`**: (Required for non-informational types). If set, a string that specifies the variable name under which the answer will be stored, corresponding to the answer in the JSON schema.
* **`id`**: An optional identifier for the question, allowing for internal reference.
* **`enabledWhen`**: If set, the condition under which the question is enabled or visible. See conditional logic below.
* **`choices`** (Required for question types that pick between choices like `select` and `boolean`: Defines the set of options from which the responder can choose.
* **`optional`**: If set, indicates whether the responder can skip the question without providing an answer.

### Question options

The **question** object will also contain a nested `options` object containing settings specific to certain question types.

#### **Confirm questions**

* **`confirmationText`**: If set, specifies the text shown next to the checkbox or confirmation control. This text is meant to clarify the action or choice for the user.

#### **Select questions**

* **`multiSelect`**: If set, allows multiple choices to be selected. The answer will always be stored as an array if this is true.
* **`randomize`**: If set, the choices presented to the responder will be randomized in order before being displayed.
* **`dynamic`**: If set, indicates that the choices are calculated at runtime, either through an inline calculation or an API lookup, allowing for dynamic updates based on previous inputs.

#### **Grid questions**

* **`subQuestions`**: The set of nested questions that are contained within the grid question
  * The required properties are the same as with standard questions.
  * These each can take an optional `options` object that&#x20;
    * **`multiSelect`**: Similar to SelectQuestion, if set, this allows multiple selections within a sub-question. The answer is stored as an array.

{% hint style="danger" %}
Not all properties available within the UI are present in the flow content. If there is something missing that is critical to your use case, please contact the team using the in-app chat and we can make an addition request.
{% endhint %}

### Variables

The top-level `variables` key contains information about variables that are not themselves collected by questions, but take their value from elsewhere.

* &#x20;Variables under the **`external`** key describe [External variables](/response-data-collection-and-management/variable-schema/external-answers) that can be passed in as URL parameters, POST bodies, or defaulted to a constant value.
* The **`api`** and **`calculated`** keys describe [API variables](/response-data-collection-and-management/variable-schema/api-answers) and [Calculated variables](/response-data-collection-and-management/variable-schema/calculated-answers), respectively.

{% hint style="info" %}
It is not currently possible to import API or calculated variables. They are present in exports for reference
{% endhint %}

### Conditional logic

[Conditional logic](/conditions-and-logic) is represented in the flow content data structure as [a MongoDB query](https://www.mongodb.com/docs/manual/reference/operator/query/).

The field names represent answers collected within the flow.

In this sample flow content, the responder is asked whether they have a cat, and if they do, the cat's name is asked

```
{
  "groups": [
    {
      "label": "Pet Information",
      "steps": [
        {
          "questions": [
            {
              "type": "boolean",
              "label": "Do you have a cat?",
              "schemaKey": "hasCat",
            },
            {
              "type": "text",
              "label": "What is your cat's name?",
              "schemaKey": "catName",
              "enabledWhen": {
                "hasCat": {
                  "$eq": true
                }
              }
            }
          ]
        }
      ]
    }
  ]
}
```

## Validation errors

Formsort itself will always generate valid flow contents.

When using the Admin API to import flow content, the flow content format is validated before importing to ensure that it conforms to the specification.

If you provide an invalid flow content, you will receive a response with a 400 status code, and a [Pydantic](https://docs.pydantic.dev/latest/) error message as the `message` field of the response JSON.

The message can be a bit terse, but can be used to determine where in the data structure the problem lies:

```
# 1 validation error for ImportFlowArgs
# flowContent.groups.0.steps.0.questions.0.text.label
#   Field required [type=missing, input_value={'schemaKey': 'first_name', 'type': 'text'}, input_type=dict]
#     For further information visit https://errors.pydantic.dev/2.4/v/missing
```

{% hint style="info" %}
Imported flow contents may still display validation errors within the studio itself, such as dependency errors, where an answer is used before it is collected. These must be resolved before you can deploy the flow.
{% endhint %}

## Detailed field reference

A detailed field reference can be found by calling the Admin API's `/spec` endpoint, which will return an OpenAPI specification of the input fields, along with descriptions of their usage.


# Customizing appearance

Customize the look and feel of your flows.

The **Theme** tab within each variant allows you to control the visual style of your flow. From here, you can define or override your design system to ensure a consistent, branded experience for responders.

### Starter themes&#x20;

Formsort provides a set of prebuilt **starter themes** that you’ll encounter when creating a new flow. These base themes serve as a great foundation for building and customizing your own themes.

<div align="left"><figure><img src="/files/Sb5opZKGWcNK7iSO0PDw" alt="" width="563"><figcaption></figcaption></figure></div>

***

### Base Themes and Local Styles

Styling is customized in the **Theme** tab, and edits don't change the base theme directly. Instead, you’re applying **variant-specific overrides** on top of the base theme. This allows for design flexibility across variants without affecting the core styling used elsewhere.

<div align="left"><figure><img src="/files/JIMUhKP695BslIRUxsNh" alt="" width="563"><figcaption></figcaption></figure></div>

{% hint style="info" %}
:bulb:The base theme will remain unchanged unless you explicitly [publish](#publishing-themes) a new version of it.
{% endhint %}

### Publishing themes

**Publish a New Base Theme**

Once you’ve finalized styling in the **Theme** tab, you can save your changes as a new base theme:

1. Click in the **Gear wheel** in the corner of the toolbar.
2. Select **Save a copy to workspace**.
3. Enter a **New Theme Label**
4. Click **Save**.

<div align="left"><figure><img src="/files/WUVZqCcQWmgEw40BCOhC" alt="" width="563"><figcaption></figcaption></figure></div>

**Update an Existing Theme**

Once a new workspace Theme has been saved, you can publish updates to it by:

1. Clicking the **Gear wheel** in the corner of the toolbar.
2. Select **Publish new version of theme**.

Your updated theme will then be available for use across other flows and variants in your workspace.

<div align="left"><figure><img src="/files/G4U3weNVsx1RCb9AfyLt" alt="" width="563"><figcaption></figcaption></figure></div>

***

### Switching themes

To change the base theme of a variant:

1. Navigate to the **Theme** tab.
2. Click the **Theme name** in right corner of the toolbar.
3. Choose from your existing base themes or one of Formsort’s starter themes.

<div align="left"><figure><img src="/files/llaA5NL7OCllb03MPKTl" alt="" width="563"><figcaption></figcaption></figure></div>

### Remove Local Styles

Since each variant can include local styling changes on top of the base theme, you may want to revert or remove these overrides.

To reset to a clean version of your base theme:

1. Open the **Switch theme** menu.
2. Select the base theme you'd like to use (this can be the same one currently applied).
3. Enable the **Remove local styles** option.
4. Click **Switch**.

This will discard any variant-specific styling changes and return the flow to the default look of the selected base theme.

<div align="left"><figure><img src="/files/jXGKPGTQ4wDv55t2wErd" alt="" width="563"><figcaption></figcaption></figure></div>


# Content area & form layout

The **Content** section defines the layout and appearance of the main flow area—where steps are presented to the responder. This area includes multiple styling options to control the visual layout and structure of the flow.

<div align="left"><img src="/files/OAyAn88PNVQvLbORoVWp" alt=""></div>

#### Content Settings

<div align="left"><figure><img src="/files/dtEUwXxNHl1vjo0bChoN" alt="" width="563"><figcaption></figcaption></figure></div>

At the top of the **Content** page, you’ll find list of global styling controls for the flow. Some of the more important settings are:

* **Favicon** → Upload a favicon for your deployed page.
* **Loading indicator image** → Display a custom image while the page is loading.
* **Show navigation sidebar** → Table-of-contents-style panel on the left side of the flow
* **Padding** → Adjust inner spacing for the content container.
* **Background color** → Set the background color of the content area.
* **Content column and text alignment** → Justify content horizontally.
* **Max width** → Set a maximum width for the content container.
* **Vertical alignment** → Control vertical placement of the content block.

{% hint style="info" %}
Refer to our CSS Reference guide for full details on configurable properties.
{% endhint %}

***

### Action Bar Styling

![The top, inline (middle), and bottom Action Bars](/files/geqLRUUMD2qBx1694EtR)

The **Action Bar** is the area where navigation buttons (Next, Back, etc.) and other elements like the progress bar may reside. Styling options include:

* **Background color**
* **Bar shadow** (visual effect similar to box-shadow)

***

### **Footer**&#x20;

The **Footer** section supports customization for:

* **Background color**
* **Padding**
* **Vertical positioning** (above or below the bottom action bar)

You can also enable:

* **Show cookie management link** → Loads Osano (if configured in your privacy policy) to let users manage cookie preferences. If Osano is not used, Formsort will use local storage.

***

### Inline Steps

The **Show all steps inline** setting renders all steps in a single, scrollable column. This layout makes the entire flow appear as a continuous vertical page. You can also control the **opacity of inactive steps** to help responders see where they are.&#x20;

{% hint style="warning" %}
This feature is still in Beta.
{% endhint %}

***

### Layout editor

Flows are rendered inside a layout that may include persistent elements like a logo, navigation buttons, and a progress bar.

<div align="left"><figure><img src="/files/ue5PfpceZpVbG8kNWG5u" alt="" width="563"><figcaption></figcaption></figure></div>

### Grid template

The Layout editor uses a grid system for positioning UI elements. You can define the number of **rows and columns** for each Action Bar (top, inline, bottom), then drag and drop components such as:

* **Buttons** (Next, Previous, Exit, Select)
* **Progress bar**
* **Logo**

This gives you fine control over positioning and layout.

<div align="left"><figure><img src="/files/8tm91R9eNFOoEzDsdzg8" alt="" width="380"><figcaption></figcaption></figure></div>

## Button Styling&#x20;

Click any button to open its styling menu. Options include:

* **X/Y position** → Adjust button placement.
* **Alignment** → Set horizontal and vertical alignment.
* **Height/Width** → Define how many rows or columns the button spans.

<div align="left"><figure><img src="/files/vFtMU2GzSVGtuB2SlFWz" alt=""><figcaption></figcaption></figure></div>

To access additional styling properties, click the **Style →** link at the bottom of the button menu to open the advanced button styling page.


# Buttons

CTA all day.

Buttons are very useful, and have many different applications in Formsort. Below are the some options provided, as well as some insight on configuring their behavior and design.&#x20;

***

### Select buttons

Select questions (and Boolean questions) can use customizable Select buttons to display choices to the responder. There are three main styles available—**Default**, **Checkmark**, and **Image**—each with its own configuration options.

Like other design elements in Formsort, Select buttons can be styled on a per-UI basis.&#x20;

#### Select Button Types

**Default**

The **Default** style presents a clean button with just a label—no icons or imagery. This is the base styling applied unless other visual features are enabled.

<div align="left"><figure><img src="/files/TrnK3ztrD5eqJRr8nbmA" alt="" width="364"><figcaption></figcaption></figure></div>

**Checkmarks**

When **Show checkmarks and radio buttons on choices** is enabled in the **Style** tab of a Select question, checkmarks (or radio buttons) appear inside the choice buttons.

<div align="left"><figure><img src="/files/liW3tslbSeCHabZPtV3q" alt="" width="563"><figcaption></figcaption></figure></div>

Styling for checkmark-based buttons is controlled in two places:

* **Theme** → **Buttons** → **Select** → **Checkmark** → Controls button styling, and checkmark positioning.
* **Theme** → **Checkmarks** → Controls the styling of the checkmark or radio icons.
  * Checkmark styles will adapt styles defined in either **Single choice** or **Multiple choice** based on whether **Allow multiple selection** is enabled or not.

You can also control the **vertical alignment** of the checkmarks from the right-hand menu.&#x20;

{% hint style="info" %}
For more details, see our documentation on [Styling checkmarks and radio buttons](/styling/customizing-appearance/checkmarks).
{% endhint %}

**Image**

Enabling **Show images on choices** in the Style tab allows you to add images, videos, or GIFs to select choices.

<div align="left"><figure><img src="/files/i5A5MxzDrgk6STwouwvT" alt="" width="563"><figcaption></figcaption></figure></div>

After enabling this option, you can add media directly within each choice configuration.

<div align="left"><figure><img src="/files/5r6hwFM8SO7ZDILMohNW" alt="" width="563"><figcaption></figcaption></figure></div>

Image-style settings are located under:

**Theme → Buttons → Select → Image**

#### Layout Options

You can adjust how choices are laid out within a Select question. Available layout styles include:

* Grid
* Vertical (default)
* Horizontal
* Snug
* Space Around
* Space Between

<div align="left"><figure><img src="/files/th3yTBMeYsDnsrIcdAgg" alt="" width="549"><figcaption></figcaption></figure></div>

The layout setting can be adjusted directly on the Select question.

#### Style Overrides

Because Select buttons are used in various design contexts, you can override default styling on a per-question basis.

To do this:

1. Select the question.
2. Open the **Style** tab.
3. Enable **Override default select button style**.

***

### Next button

The [Next](/adding-questions-and-content/content-reference/next-button) button is probably the most important button within the flow. Clicking it will advance the user to the next available step.

The Next button position can be configured in Layout, and style settings can be found in the Buttons → Next section of the Theme tab.&#x20;

#### Style options

<div align="left"><figure><img src="/files/RQkiAodrTpcgQazazvmw" alt="" width="563"><figcaption></figcaption></figure></div>

By default, the Next button will only appear when the user has answered all the required questions and needs a way to move forward. You can opt to "Always show next button" by enabling the setting under **Behavior**, in which case the Next button will show in it's **Disabled** state until the user is available to proceed.

By default, "Next" is provided as the button label, but this can be modified or removed by changing the string inside the **Label** input.&#x20;

You can add an Image to your next button, style settings for which can be found at the bottom of the menu under **Image style** once an image has been added.

#### Next button style override

If you've added your [Next button as a component](https://docs.formsort.com/building-flows/content-types/next-button) on a step, the style settings can be overriden in the component's style menu. With the component selected, open the Style menu and disable "Use default select button style".&#x20;

<div align="left"><figure><img src="/files/N5wnA0DCGd4lEnYpfPAJ" alt="" width="563"><figcaption></figcaption></figure></div>

***

### Skip button

A **skip button** will appear if enabled, but only on steps where **all** questions within the step are [optional](/conditions-and-logic#optional). Of course, the responder could just click the Next button in this case, but having an explicit skip action may help people understand that their responses are not required on the current step.&#x20;

#### Options

<div align="left"><figure><img src="/files/eAneYgGwKcqSzS6V4i6D" alt="" width="563"><figcaption></figcaption></figure></div>

By default, the button will have "Skip" as the copy inside the button, but this can be modified or removed by changing the string inside the **Label** input.&#x20;

You can add an Image to your skip button, style settings for which can be found at the bottom of the menu under **Image style** once an image has been added.

***

### Previous button

The **previous button** is optional, but recommended - it gives responders more control in navigating the flow.

#### Options

<div align="left"><figure><img src="/files/BTMOhfes8FFKE5KUMkFu" alt="" width="563"><figcaption></figcaption></figure></div>

By default, the button will have "Previous" as the copy inside the button, but this can be modified or removed by changing the string inside the **Label** input.&#x20;

You can add an Image to your Previous button, style settings for which can be found at the bottom of the menu under **Image style** once an image has been added.

***

### Exit button

If you enable the **exit button** in the style editor, users will be able to click it to abandon the flow.

There are a few cases where having a close or exit button is helpful:

* If users are accessing your flow directly, but you wish to bring them to your own page if they are abandoning.
  * &#x20;If you set a **redirect URL** then they will be brought to that URL.&#x20;
* If [embedding](/publishing-and-deployment/embedding) Formsort and you need a way to allow users to close without abandoning the page entirely.&#x20;
  * The parent window will receive the `FlowClosed` event when this happens, so you can close the Formsort embed in your code.

#### Options

<div align="left"><figure><img src="/files/3iqvdns5zpOB4vSmaF4T" alt="" width="563"><figcaption></figcaption></figure></div>

By default, the button will have an "X" as the copy inside the button, but this can be modified or removed by changing the string inside the **Label** input.&#x20;

You can add an Image to your Exit button, style settings for which can be found at the bottom of the menu under **Image style** once an image has been added.

***

### Restart button

If you add a **restart button** to your flow, the user will be able restart the flow with all their inputs cleared.&#x20;

<div align="left"><figure><img src="/files/ezZo4G2pxjkLZwwLTtnS" alt="" width="563"><figcaption></figcaption></figure></div>

By default, the Restart button will have "Restart" as the copy inside the button, but this can be modified or removed by changing the string inside the **Label** input.&#x20;

You can add an Image to your Restart button, style settings for which can be found at the bottom of the menu under **Image style** once an image has been added.

***

### Default buttons

The **default button** styles will be applied to any buttons not listed above.

<div align="left"><figure><img src="/files/skgnF4Q6V55oiGeeqNpg" alt="" width="519"><figcaption></figcaption></figure></div>


# Typography

Typography settings in Formsort give you full control over how text appears in your flows—from base font size to specific fonts and styling for individual text elements like headings and body text.

### Font selection

You can add and manage fonts in your flow via **Theme → Typography → Add font family...**. Formsort supports the following font sources:

* **Google Fonts** (available out of the box)
* **Adobe Fonts**
* **Self-hosted web fonts (e.g., via S3)**

{% hint style="warning" %}
Make sure you have the legal right to use any uploaded font. Per our Terms and Conditions, Formsort will comply with takedown requests from copyright holders.
{% endhint %}

<figure><img src="/files/twgcrJe9eJjIC3RuP9yE" alt=""><figcaption><p>Typography menu</p></figcaption></figure>

#### Markdown Styling Support

Once a font is added, you can assign it to the typographic elements found in the chart below. You can use Markdown syntax within any text field in your flow. Typography settings allow you to style each tag individually (e.g., `**bold**`, `_italic_`, etc.).

| Category | Description    | Markdown syntax                   |
| -------- | -------------- | --------------------------------- |
| H1       | Heading 1      | #                                 |
| H2       | Heading 2      | ##                                |
| H3       | Heading 3      | ###                               |
| H4       | Heading 4      | ####                              |
| H5       | Heading 5      | #####                             |
| H6       | Heading 6      | ######                            |
| P        | Paragraph      |                                   |
| A        | Hyperlink text | `[Formsort](http://formsort.com)` |
| INPUT    | Input items    |                                   |
| LI       | List items     |                                   |

***

### Uploading custom fonts to Formsort

You can also upload font files directly to Formsort:

1. Navigate to **Theme → Typography → Add font family... → Custom Fonts**

<div align="left"><figure><img src="/files/ZvxtE2wiHCUDG4q0cKsM" alt="" width="563"><figcaption></figcaption></figure></div>

2. Enter the **font family name**
3. Upload the **font files**

<div align="left"><img src="/files/BPTXjq5p2BNWuDE88fXy" alt="Upload font file" width="375"></div>

4. Map the **font weights** (e.g., Regular, Bold, Light)

{% hint style="success" %}
You can upload multiple font files at once.\
Fonts are stored in your client S3 bucket (no configuration required) and pulled from there automatically.&#x20;
{% endhint %}

***

### Adobe Fonts&#x20;

To use Adobe Fonts in your Formsort flow:

1. **Create or log in** to your Adobe Fonts account.
2. **Search** for a font and click **Add to web project**.

<div align="left"><img src="/files/-MZOiYZogj1nKT1s0GFX" alt="Adobe Fonts&#x27; Web Project editor" width="375"></div>

3. In the dialog, **create or select a web project**.
4. Click **Edit Web Project** and note the:
   * **Project ID**
   * **Font-family name**
   * **Weights** you want to use

<div align="left"><img src="/files/-MZOjXUkFba4pDMM7Lm-" alt="Relevant information within the Edit Web Project view" width="563"></div>

3. In Formsort, go to **Theme → Typography → Add font family... → Adobe Fonts**.

<div align="left"><figure><img src="/files/lSIItZ1fJhDeoFcTSFYp" alt="" width="563"><figcaption></figcaption></figure></div>

4. Enter the required values.

<div align="left"><figure><img src="/files/4dn6SYEuJLrOIMWkwSVE" alt="" width="329"><figcaption></figcaption></figure></div>

***

### Hosting Custom Fonts

If you have font files that you are already hosting somewhere else (like Amazon S3), you can use them in your flows.

#### CORS Configuration for S3

Here’s a sample CORS policy compatible with Formsort:

```markup
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>https://flow.formsort.com</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
    <CORSRule>
    <AllowedOrigin>https://studio.formsort.com</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
  <CORSRule>
    <AllowedOrigin>https://{{YOUR_DOMAIN_HOSTING_FORMSORT}}.com</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
</CORSConfiguration>
```

To test your CORS setup:

```bash
curl -H "Origin: https://flow.formsort.com" -s -D - -o /dev/null  https://{YOUR_BUCKET}.amazonaws.com/{YOUR_PATH}.otf | grep Access-Control-Allow-Origin
```

Expected output:

```
Access-Control-Allow-Origin: https://flow.formsort.com
```


# UI states

Make your components responsive to user interaction.

The **UI state** of a component defines what it will look like in various scenarios, like a [Next](https://docs.formsort.com/building-flows/styling/buttons#next-button) button when it's hovered over. These states can be customized to create a feeling that the flow is responding to user interaction.&#x20;

Here is a helpful table that shows the different UI states that can be customized.&#x20;

| UI state     | Description                                                                                                                                                        |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **default**  | No user interaction has happened.                                                                                                                                  |
| **disabled** | The button is disabled (e.g., cannot continue, or a choice is disabled).                                                                                           |
| **checked**  | <p>User has selected this choice.<br><br><strong>Only available on</strong> <a href="#select-button">select</a> <strong>buttons and their variations.</strong></p> |
| **focused**  | The user has tabbed or otherwise focused the button.                                                                                                               |
| **hover**    | <p>The user's cursor is over the button.</p><p></p><p>This will not be seen by mobile responders, as there is not an equivalent to hover on mobile phones.</p>     |

{% hint style="info" %}
See our [CSS Reference](/css-and-advanced-styling/css-reference) section for an overview of the CSS properties that can be applied to various components.&#x20;
{% endhint %}

See the following sections for more insight into components and how they can be used.&#x20;


# Color and dimension variables

### Color variables

Color variables are definable in **Theme** -> **Variables** -> **Colors**.

{% hint style="info" %}
Setting color variables lets you enforce a consistent palette across your flow and simplifies the process of changing the colors in your theme in the future.&#x20;
{% endhint %}

#### Setting a color variable

Creating a color variable is as simple as adding a name and adding the color value. When setting the color, the menu can accept hex values and rgba values. You can also use the palette interface to find a color you like, which will automatically update the hex and rgba values for you.&#x20;

<figure><img src="/files/LZtuZMOKQs8vJoqeQNdq" alt=""><figcaption></figcaption></figure>

#### Using the color variable

Color variables are accessible when styling the color of components in your flow. You will see your available colors variables at the bottom of any color menu.&#x20;

<figure><img src="/files/HcxFik9ZIUrc4w3YZxW8" alt=""><figcaption></figcaption></figure>

***

### Dimension Variables

Dimension variables let you create re-usable units that can be made [responsive](https://en.wikipedia.org/wiki/Responsive_web_design) across viewport sizes. Dimension variables are definable in **Theme tab** -> **Variables** -> **Dimensions**. Each dimension variable lets you input a [CSS Length Unit](/css-and-advanced-styling/css-reference#css-length-units) for the three major platforms users will view your content in: mobile, tablet and desktop. When you use the dimension variables anywhere in the studio (setting content widths, max image widths, etc.), you will the ability to control the dimensions of a component for each specific viewport.&#x20;

{% hint style="info" %}
Using dimension variables provides consistent spacing across your design system and enables responsive design.&#x20;
{% endhint %}

### Creating the dimension variable

Creating a dimension variable is simple: click **add dimension**, give the the variable a name that makes sense, click **save**, and then add in the dimensions for the different viewport sizes.&#x20;

In the below example, we are creating a **padding** variable that will have a value of 16px on mobile and tablet, and a value of 24px when viewed on desktop.&#x20;

<figure><img src="/files/oPQsCzu28TAVEBWixrdP" alt=""><figcaption><p>Add the  name of the variable, then click "Save", then add the dimensions. The variable values will be saved automatically. </p></figcaption></figure>

### Accessing the dimension variable

At some point you're going to want to apply these variables to different properties. Following the above example, we are adding the **padding** dimensional variable to the left and right padding values of our [**Content**](/styling/customizing-appearance/content-area-and-form-layout) box by clicking the drop-down menu in the **Content Padding** section. This will ensure that the padding applied is responsive to the viewport our users are viewing the flow from.&#x20;

<figure><img src="/files/gSAiWK3Nb0rkhFVWwCIl" alt=""><figcaption><p>Click the drop-down to access your list of dimension variables</p></figcaption></figure>

{% hint style="info" %}
The dimension variables can be re-used in as many places as you need, based on what makes sense with the [length values](/css-and-advanced-styling/css-reference#css-length-units) they will apply.&#x20;
{% endhint %}


# Question containers

Questions can have an overlay on top of the default styles applied to them, which gives the user a degree of freedom in defining how different questions and content components can appear on the page.&#x20;

Any theme provided by Formsort will have at least one container style - default - but you can build up your collection of different styles by heading to **Containers** in the Theme editor.&#x20;

**+Add question style** allows you to create a new question container.&#x20;

<div align="left"><figure><img src="/files/ul0CY8lRM4el05vPL66H" alt="" width="563"><figcaption></figcaption></figure></div>

{% hint style="info" %}
The **Label margin** is of particular note here, since it provides spacing between the Question label the buttons/input bars related to it.&#x20;
{% endhint %}

Once a container style is defined, any question can use any of the container styles by clicking the question, and choosing the container style in the **Question container style** dropdown in the Style menu.

<figure><img src="/files/BcxrtbiYarxEHSgLXP5u" alt=""><figcaption></figcaption></figure>

These containers are helpful to make different kinds of questions visually distinct: a common pattern is to  create an **optional** question class that is used for optional questions, with left border and margin to differentiate it from non-conditional questions.&#x20;


# Inputs and dropdowns

## Inputs

Input boxes are used in any question type that involves the responder typing in information. These can be edited on a per-UI state basis. The states available are:&#x20;

* default&#x20;
* disabled
* focused&#x20;
* hover&#x20;
* invalid

{% hint style="info" %}
In order to change the typography of the text input by the responder, you need to go to the [**typography**](/styling/customizing-appearance/typography) menu and change the settings in the **input** tab.&#x20;
{% endhint %}

Styling options for Input boxes can be found in **Theme** -> **Inputs**.

## Dropdowns

Dropdowns are styled [Select](/adding-questions-and-content/question-reference/select) questions. With the Select question highlighted, click on the **Style** tab and select **Picker style** -> **Dropdown**.&#x20;

<figure><img src="/files/dqMyOghvbj5QsghRnhHw" alt=""><figcaption></figcaption></figure>

The dropdown input box inherits from the **Input** style by default, but can be overridden in the **Dropdowns** section of the style tab.

There are three key pieces to the dropdown: items, container, and input.&#x20;

* The **Input** tab is the first line users will see and click on to render the dropdown.&#x20;
* The **Items** tab controls what each of the options in the menu will look like.&#x20;
* The **Container** tab will let you customize the box that the items are enclosed in.

### Native vs. Default dropdown

Styling a list of choices as **Native Dropdown** rather than **Dropdown** can often be a more pleasant experience for responders on mobile because the **Native Dropdown** will inherit the UX of the operating system. The standard **Dropdown** will fix the choices to the top so that responders are not stuck scrolling within the scroll of the step.

You can see the difference between the two styles in this demo flow [here](https://formsortdemo.formsort.app/flow/dropdown-types/variant/main). Try opening it on mobile to see the different options!

![](/files/X0rbWbJqV1QxgBtn4xTO)![](/files/THGXKmUXaGJg0EEytuMF)


# Checkmarks

Checkmarks can be edited on a per-UI state basis. The options available to customize in checkmarks are: default, checked and disabled.&#x20;

These are used in [select](/adding-questions-and-content/question-reference/select) questions when **Show checkmarks and radio buttons on choices** is enabled in the question's settings, and in [confirm](/adding-questions-and-content/question-reference/confirmation) content types.&#x20;

<figure><img src="/files/m81ZWJGuvJDILRiNDP6J" alt=""><figcaption></figcaption></figure>

### Styling

In the **Theme** -> **Checkmarks** section, you have two checkmark options: single choice (radio) and multiple choice (box).&#x20;

* **Multiple choice** will be used on any Select questions with "Allow multiple selection" **enabled**.
* **Single choice** will be used when the above setting is disabled.&#x20;

{% hint style="info" %}
The Select questions will also need **Show checkmarks and radio buttons on choices** enabled as well.&#x20;
{% endhint %}

Please refer to our [CSS reference](/css-and-advanced-styling/css-reference) section for more information on the style properties that can be adjusted in this menu.&#x20;


# Tables

Currently table formatting controls the layout of the [grid](/adding-questions-and-content/question-reference/grid-choice) questions.&#x20;

### Spacing

Using the **Row gap** and **Column gap** in the **Table** menu, you can control the spacing around cells. You can also change the padding within the cell using the **Cell** menu.&#x20;

### Styling first row or column differently than the rest of the table

In order to make the grid more legible, you might want to style the first row and the first column differently. This will allow you to emphasize the question and answer labels.&#x20;

{% hint style="info" %}
You can style the radio buttons using the [**Checkmarks** ](/styling/customizing-appearance/checkmarks)menu.
{% endhint %}


# Sliders

<div align="left"><figure><img src="/files/GVZCdKwFm0ZrwTstJIPz" alt=""><figcaption></figcaption></figure></div>

You can customize slider styles used by [Select](/adding-questions-and-content/question-reference/select) questions in the **Sliders** menu in **Theme**.&#x20;

<div align="left"><figure><img src="/files/ajMRM7nG5zkfLdpanQ7J" alt="" width="563"><figcaption></figcaption></figure></div>

The **Thumb** is the dot the users will pull to make a choice. This menu allows you to select the color, width, box shadow and the size of the thumb.&#x20;

You can use the **Track** menu to customize the color and size of the line that represents the spectrum of choices available to the responder.


# Divider lines

You can change the color and the height of the [divider](/adding-questions-and-content/content-reference/divider) content block by going to the **Divider lines** menu in the style tab.&#x20;


# Progress bar

Display user progress through a form flow.

**Progress indicators** such as completion bars and step counts are helpful in giving responders a sense of how far along in the flow they are. Especially for longer flows, showing progress indicators can help conversion, as user expectations are set correctly from the outset, and feedback is given as every step is completed.

To enable progress bars, see **Progress indicators** within the style set editor.

## Layout styles

![The linear layout type](/files/-MK_36WpfxMTIh30biRq)

![The subway stops layout type](/files/-MK_2yx-gwzSAAnxz6pp)

![The text layout type](/files/-MK_3WUZicCepNK7ppby)

The sizes, colors, and other aspects of these layout types can be further customized within the style editor.

## Changing the vertical positioning

You can use the **Vertical position** in the **Progress Indicator** menu within the style editor to change the placement of the progress bars.&#x20;

You can also use the [**Layout** editor](/styling/customizing-appearance/content-area-and-form-layout#layout-editor) to change the positioning of any of the components, including the progress indicators. &#x20;

## Counting groups or steps

By default, progress indicators show users the position of the user's current step within the total number of steps within the flow.

In the case that you have a large number of steps, it might be useful to indicate progress by [groups](/creating-flows/building-a-new-flow/organizing-using-groups), by selecting **Groups** under **Count steps or groups?**&#x20;

![Counting groups, with labels shown](/files/-MK_3ttoWC4zvvwkNwe7)

## How is progress calculated?

If your flow has steps which are only enabled based on a [condition](/conditions-and-logic), the number of steps available to the responder can change based on the answers that they provide.

To avoid having a progress indicator that jumps around, the progress indicators always include every step in the denominator - the percentage completion is just `current step index / total number of steps`. This is why sometimes the progress bar may jump: the responder is skipping over steps that were conditionally disabled.

Note that consecutive steps that all use the same variables as their condition but check for different values of that condition are collapsed together when calculating progress, as a responder would only ever be able to access one of those steps.


# Comparison cards

Configuring your comparison cards

In the Theme menu, there are three options for styling comparison cards: Default, Highlighted, and Selected.  &#x20;

<figure><img src="/files/RCSPHDpQwaL6kxedeeLx" alt=""><figcaption></figcaption></figure>

**Default**: this is the default state of the card, and inherits styling from the default theme. No additional configuration is required except adding a comparison card to the step, but additional/differentiating styling can be added if desired.&#x20;

**Highlighted**: if an option has been designated as "highlighted" in the [question settings](/adding-questions-and-content/question-reference/comparison#adding-choices), the styling for the highlighted card can be set here.&#x20;

**Selected**: styling for when a card has been selected by the user.&#x20;

Each of the above three configurations can be further styled on a per-UI state basis: default and hover.&#x20;

<figure><img src="/files/FARVd0bvpr53WK7W4r8K" alt=""><figcaption></figcaption></figure>

The **horizontal overflow behavior** selector controls how the cards fill the space on the step, and has two settings, **wrap** and **scroll**.&#x20;

* Wra&#x70;**:** if the container holding the cards exceeds the width allotted for the container of the step, the cards will begin to stack vertically.&#x20;
* Scroll: if the cards exceed the width allotted, the a horizontal scroll will become available.&#x20;

Related pages: [Content Types: Comparison](/adding-questions-and-content/question-reference/comparison)


# Animations and transitions

Transitions between steps and questions can be customized using animations.

There are several animation styles to choose from at the Step level, such as:

* Card flip
* Fade
* Horizontal and vertical slide
* Zoom
* Confetti (our personal favorite)

## Step-level animations

You can enable animations at the Step level in one of two ways: at the "Theme" level, or on a specific Step.

### **Enable animations at the “Theme” level**

This is useful if you want the same animation across all steps

1\. Go to the Step menu in the Theme section, and scroll down until you see **Step Animation Style**.

<figure><img src="/files/UriKrbd1iDEXn5A1Ug51" alt=""><figcaption></figcaption></figure>

2\. Select your preferred animation from the dropdown. This section allows you to choose from a variety of preset animations as well as their timing and easing functions.

<figure><img src="/files/MNrrA2C06mJEoBfA7Qf4" alt=""><figcaption></figcaption></figure>

### **Enable animations for a specific Step**

This is useful if you want to customize animations for a specific Step in the flow, like adding confetti.

1\. Click on the Step you’d like to enable animations on.

2\. Go the the side bar on the right and click on “Style”.

<figure><img src="/files/nn38dptL4jgxaRB2dOQW" alt=""><figcaption></figcaption></figure>

**If you want to enable a specific animation like card flip, fade, etc.:** click on the toggle next to “Step animation style override” and choose the animation of interest.

<figure><img src="/files/CDO8yAyuxAsYIIo0BuTg" alt=""><figcaption></figcaption></figure>

4\. **If you want to enable confetti:** click on the toggle next to “Show confetti in background”. This is a fun way to celebrate the end of your flow. 🎉

<figure><img src="/files/aXEl36jlSk226mG6VF53" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/OmeYPr7ZdKu4U7RKqXJy" alt=""><figcaption></figcaption></figure>

## Question-level animations

You can enable a similar set of attributes at the Question level by toggling on **Animate Questions on Appear** in the Question menu of the Theme section. Note: confetti is not available at the Question level, only the Step level.

<figure><img src="/files/jmFKx1QY7ceGtE1GTB4n" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/NAW9gnrqYFTpJmpk6Ufr" alt=""><figcaption></figcaption></figure>


# CSS & Advanced Styling

### Local styling for questions, steps, groups

While the **Theme** tab controls the overall appearance of your flow, Formsort also supports **local styling** at the level of individual questions, steps, and groups. These local overrides allow you to tailor the look and feel of specific parts of your flow—without affecting the rest of the variant.

### Group style overrides

At the **group** level, you can override the default theme styling for all content within that group:

1. Select a group in the Studio.
2. Click **Style**.
3. Click **Override the style of this group**.

Any changes made here will apply only to that specific group. If you notice styling inconsistencies with what’s defined in your Theme, this is a good place to check first.

<div align="left"><figure><img src="/files/KMOz5QTe1SsPcIBi9IpC" alt=""><figcaption></figcaption></figure></div>

{% hint style="info" %}
💡 Remember: Group overrides take precedence over base Theme styles.
{% endhint %}

***

### Step styling

Individual **steps** also support style customization. From the **Style** tab of a selected step, you can:

* Add a **background image**
* Change the **step transition animation**
* Modify the appearance of the **Next** button

These settings are useful for visually differentiating specific parts of a flow. For a full list of options, see the [Step styling](/css-and-advanced-styling/step-style) sub-section.

<div align="left"><figure><img src="/files/wUbMXCHMJZ8oXUGqVWYT" alt=""><figcaption></figcaption></figure></div>

***

### Question styling

Questions have more limited support for local styling, but certain components allow overrides of the default Theme appearance. If the question supports it, you’ll see a toggle labeled **Override default style**.

As of this writing, the following components support local style overrides:

* **Short text** and **Long text** questions
* **Button-style select questions**, including:
  * Single choice
  * Multiple choice
  * Picture choice

Once enabled, you’ll see options to customize both the **component itself** and the **container** it’s rendered in.

<figure><img src="/files/Vv2OASQ4MrklKHP1wC6V" alt=""><figcaption></figcaption></figure>

### Question Containers

Every question is rendered inside a **container**, and you can control the appearance of that container using the **content container style** setting in the question’s **Style** tab.

<div align="left"><figure><img src="/files/17xtBId1bxmfg4bo8dip" alt="" width="563"><figcaption></figcaption></figure></div>

To define or edit custom containers:

1. Go to **Theme → Containers**.
2. Use **+ Add question style** to create new container styles or modify existing ones.

<div align="left"><figure><img src="/files/C4cydndMAdrxXei7qqmC" alt="" width="563"><figcaption></figcaption></figure></div>

***

### Advanced styling with CSS

For greater flexibility, you can also use **Custom CSS** to style components that don’t expose all properties via the UI. This is especially useful for advanced use cases where question-level overrides are not available.

See our [Custom CSS](/css-and-advanced-styling/custom-css-overrides) section for more info.&#x20;


# Custom CSS overrides

Using CSS directly to style form layout and components

The style settings available within the theme editor are mostly simple wrappers around the CSS properties that are applied to the HTML elements rendered by Formsort. In simple terms, the options you see in the theme editor are easy-to-use controls for changing the appearance of your forms.

If you'd prefer to use CSS syntax directly to style your forms, you can do so by heading to **Theme > General > Content area > Custom CSS** and entering valid CSS rules there.

{% hint style="info" %}
CSS overrides are an escape hatch! Please use our theme editor tools as much as possible.
{% endhint %}

## Guide for use

### **Targeting specific groups and steps**

You should first add ids to the steps and groups you want to customize  - see our [step id](/creating-flows/building-a-new-flow/steps#step-id) and [group id](/creating-flows/building-a-new-flow/organizing-using-groups#group-id) documentation for more details.&#x20;

After setting ids, you can use `data-current-step-id` and `data-current-group-id` attributes to target those steps and groups:

```css
[data-current-group-id="contact details"] .fs__inputInner {
  border-radius: .5em;
}

[data-current-step-id="final step"] h2 {
  font-weight: bold;
}
```

{% hint style="info" %}

### **Targeting components**

When writing custom CSS rules, please use the supported classnames listed in the [Class Reference](#class-reference) section below to target specific component types and their elements.

* **Don't** use random-seeming classnames or IDs you may find within the Formsort HTML structure. These should be considered implementation details and are subject to change at any time.
* **Don't** use highly-nested selectors. We may, from time to time, change the DOM structure of layouts and components. Highly-nested selectors are brittle and subject to breaking when this happens.
  {% endhint %}

***

## Classname reference

### Step layout Overrides&#x20;

`.fs__stepContent`

This component is the container that wraps step content, question and informational blocks. It is a good choice to override some alignment, padding and font properties that effect whole step.&#x20;

For example, if you want to change color of labels in a step, you could use this snippet.&#x20;

```css
.fs__stepContent label {
    color: cornflowerblue;
}
```

You should not override background color or image in this class name. As you can see in the image below, it can have margins around. We'll add `fs__step` later so that you can override the whole page styles.

<figure><img src="/files/ZIwaqSUGqdFQ3PtwVkni" alt="" width="563"><figcaption></figcaption></figure>

***

### Question and Component Overrides

### Comparison cards

`.fs__comparison-question`

Targets the comparison card `div`. Can be used to set the alignment and justification of the comparison cards in their row.&#x20;

<div align="left"><figure><img src="/files/ElHpgYO4bdEx9kgDsPwn" alt="" width="563"><figcaption></figcaption></figure></div>

`.fs__comparisonCard`

Targets the comparison card container. You can set overrides for components within the card such as font, alignment etc. Also you can use this class name to set gradient backgrounds and adjust positioning like margins and paddings.

<div align="left"><figure><img src="/files/FDTRz4Mr56GzsX39ROg4" alt="" width="563"><figcaption></figcaption></figure></div>

The card content consists of 3 parts: **title**, **body**, and **action containers**:

* `.fs__comparisonCardTitle` to style title section.
* `.fs__comparisonCardBody` to style body section.
* `.fs__comparisonCardAction` to style action button section.

<div align="left"><figure><img src="/files/HVWdHMglDzly3OxGuF9a" alt="" width="512"><figcaption><p>Comparison card sections</p></figcaption></figure></div>

To customize `highlighted` and `selected` states, you can use `.fs__highlighted` and `.fs__selected` class names, e.g.

<pre class="language-css"><code class="lang-css"><strong>.fs__comparisonCard.fs__selected .fs__comparisonCardBody {
</strong>    font-weight: bold;
}
</code></pre>

***

### Dividers

`.fs__divider`

You can set divider color with `background-color`, height, margins, etc.

<div align="left"><figure><img src="/files/1nZD026qBaT89CcfbnvA" alt="" width="491"><figcaption></figcaption></figure></div>

***

### Select component

#### Buttons

`.fs__sb-container`&#x20;

Targets the entire Select component wrapper. The Select button container uses the `display: flex` property to order the container.&#x20;

* Use to style all select questions that are using buttons.
* Use to override the layout of a specific Select question (in conjunction with [step targeting](#targeting-specific-groups-and-steps)).

<div align="left"><figure><img src="/files/rnzck7MmXpHvgMTlDQNi" alt="" width="563"><figcaption></figcaption></figure></div>

`.fs__sb-checkmark`&#x20;

Use this to target select buttons that use checkmarks.

`.fs__sb-image`&#x20;

Use this to target select buttons that use images.

#### Dropdowns

When a select question is turned into a dropdown, the dropdown will consist of 3 parts: **container**, **list**, and **list items**.&#x20;

You can use `.fs__dropdown-container` to apply style overrides to both input and list, such as text alignment and font style. To drill down to the dropdown list and items, you can use `.fs__dropdown-list` and `.fs__dropdown-list-item`.

```css
.fs__dropdown-container {
    font-weight: bold;
    text-align: center;
}

.fs__dropdown-list {
    margin-top: 24px; /* more space between input and list */
    background-color: lightyellow;
}

.fs__dropdown-list-item {
    margin-bottom: 8px;
    padding: 4px 8px;
}
```

`.fs__dropdown-container`

<figure><img src="/files/8CZ7fG7vdBrLDyAX30Ni" alt="" width="494"><figcaption></figcaption></figure>

`.fs__dropdown-list`

<figure><img src="/files/Vyt8ts9iNOyXk0IsEdpI" alt="" width="563"><figcaption></figcaption></figure>

`.fs__dropdown-list-item`

<figure><img src="/files/vAcJUZR000AyTxL1p46L" alt="" width="563"><figcaption></figcaption></figure>

To customize `highlighted`, `selected` and `disabled` styles of list items, you can use common Formsort class names: `.fs__highlighted`, `.fs__selected` and `.fs__disabled`.

```css
.fs__dropdown-list-item.fs__highlighted {
    border-color: deepskyblue;
}

.fs__dropdown-list-item.fs__selected {
    background-color: mediumpurple;
    color: white;
}
```

***

#### Text inputs

For most of input styles you can use `.fs__inputInner`. Use `.fs__inputWrapper` for margins and widths.

```css
.fs__inputInner {
    background-color: aquamarine;
    padding: 8px;
    ...
}

.fs__inputWrapper {
    margin-bottom: 16px;
    max-width: 80%;
}
```

For input prefixes and suffixes, you can use `.fs__inputPrefix` and `.fs__inputSuffix`.

<figure><img src="/files/UCzuIxabSlkJqy6fHpJS" alt="" width="456"><figcaption></figcaption></figure>

```css
.fs__inputPrefix {
    padding-right: 24px;
}

.fs__inputSuffix {
    font-style: italic;
}
```

Just like in plain CSS, you can use pseudo-classes to customize input states like `focus`, `hover`, `disabled` and `invalid`.

```css
.fs__inputInner:hover, .fs__inputInner:focus {
    background-color: purple;
}

.fs__inputInner:invalid {
    background-color: rgba(255, 0, 0, .2);
}
```

### Styling beyond the basics&#x20;

With custom CSS, you can go beyond simple styling and introduce creative visual effects.\
Here’s an example that adds a smooth radial gradient to your step backgrounds using an embedded SVG:

```css
html, body, form {
 background-image:
  url("data:image/svg+xml,%3Csvg width='1240' height='1128' viewBox='0 0 1240 1128' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='1500.67' height='1886.79' transform='matrix(1 0 0 -1 -261 1421.79)' fill='url(%23paint0_radial_14082_2512)'/%3E%3Cdefs%3E%3CradialGradient id='paint0_radial_14082_2512' cx='0' cy='0' r='1' gradientUnits='userSpaceOnUse' gradientTransform='translate(-556.247 342.428) rotate(11.9757) scale(2041.31 603.311)'%3E%3Cstop offset='0.186517' stop-color='%2305378C'/%3E%3Cstop offset='0.235037' stop-color='%23115FD1' stop-opacity='0. 890016'/%3E%3Cstop offset='0.422887' stop-color='%2342A5F5' stop-opacity='0.45'/%3E%3Cstop offset='0.90625' stop-color='white' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3C/svg%3E"),
  radial-gradient(74.49% 74.49% at 50% 100%, #fff 0, hsla(0,0%,100%,0) 100%);
 background-position: center top, bottom;
 background-repeat: no-repeat;
 background-size: cover, auto;
}
```


# Step styling

Spruce up your steps.

Steps in your flow can be visually customized using the **Style** tab in the right-hand menu of the Studio. These customizations allow you to add background media, control step transitions, and adjust the appearance and placement of the Next button.

<figure><img src="/files/WfpdxJiVXPIRE4d0R3XJ" alt=""><figcaption><p> There are various options available in this menu</p></figcaption></figure>

***

### Image or video background

You can upload an image or video to serve as the **background** for a step. This is useful for adding visual impact or branding to important moments in your flow.

#### Background image sizing options

Keep in mind that users may access your flow on a variety of screen sizes—from small phones to large desktop monitors. When choosing size and position settings, test across viewports and use percentage-based values when possible.

* **Contain**: Ensures the full image is always visible within its container. The image may be letterboxed and won't cover the entire element.
* **Cover**: The image will always fill the container completely. Parts of the image may be cropped depending on its aspect ratio.
* **Absolute**: The image uses a fixed size and position relative to the top-left of the container.

<div align="left"><figure><img src="/files/NXJuyd3SbUAtr7dJU8ba" alt="" width="563"><figcaption><p>The background image positioning editor </p></figcaption></figure></div>

#### Background image positioning

You can control where the image is placed within the container:

* **Absolute positioning**: Enter specific offset values (e.g., `25% / 25%`) to pin the image at that position from the top and left.
* **Keyword-based positioning**: Use keywords like `top`, `center`, `bottom`, `left`, or `right`—or combinations such as `top right`—to anchor the image.

***

### Step transition animations

You can override the default **step transition animation** defined in your flow’s Theme to create custom transitions for individual steps.

This is helpful for reinforcing logic visually. For example:

* Use **slide in from the right** for positive outcomes
* Use **slide in from the left** for negative outcomes

***

### Show confetti in background

Enable this setting to add celebratory confetti effects behind the step content. Great for marking success milestones or final submission steps!&#x20;

<div align="left"><figure><img src="/files/ZzGalBaHeZdloErNB0Od" alt=""><figcaption><p>Hooray!</p></figcaption></figure></div>

***

## Next button overrides

While the default [Next button styling](/styling/customizing-appearance/buttons#next-button) is controlled in the **Theme > Buttons** section, you can customize some step-specific aspects directly in the step’s Style tab.

**Button Text**

Customize the label of the **Next** button for this step. For example, change the default text to something like **“GET STARTED”** on the first step to encourage engagement.

**Horizontal Alignment**

Override the default horizontal placement of the Next button for this specific step (e.g., align left, center, or right).

**Inline Button Placement**

If you'd like to **embed** the Next button within the step’s content (rather than below it), use the **Next Button Content** block. This renders the Next button inline with other components in the step.


# CSS reference

The Formsort studio has many CSS properties that can be manipulated to customize your flow - here is a reference to help get your bearings.

<table><thead><tr><th width="139.66237095424106">Property</th><th>Definition</th><th data-hidden></th></tr></thead><tbody><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/margin"><strong>margin</strong></a></td><td>The space around the outside of the border of the element</td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/padding"><strong>padding</strong></a></td><td>The space between the content and the border of the element</td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius"><strong>border-radius</strong></a></td><td>Rounds the corners of an element's outer border edge  </td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-color"><strong>border-color</strong></a></td><td>Sets the color of an element's border</td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-width"><strong>border-width</strong></a></td><td>Sets width of an element's border</td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow"><strong>box shadow</strong></a></td><td>Adds a shadow effect around an element's frame</td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/min-height"><strong>min-height</strong></a></td><td>Sets the minimum height of an element</td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/min-width"><strong>min-width</strong></a></td><td>Sets minimum width of an element</td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/max-width"><strong>max-width</strong></a></td><td>Sets the maximum width of an element</td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform"><strong>transform</strong></a></td><td>Let's you rotate, scale, skew, or translate an element </td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-align"><strong>text-alignment</strong></a></td><td>Sets the horizontal alignment of the content inside a block </td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/opacity"><strong>opacity</strong></a></td><td>The degree to which the content behind and element can be seen</td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/font-size"><strong>font-size</strong></a></td><td>Sets the size of the font. For responsive design, <a href="/pages/-MJTTsfI8h6fH_kNrHdr#understanding-rem-sizing">rem units</a> are recommended</td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight"><strong>font-weight</strong></a></td><td>Sets the weight (i.e. boldness) of the font. The weights available are determined by the font-family that is set </td><td></td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/line-height"><strong>line-height</strong></a></td><td>Sets the space between lines of text</td><td></td></tr></tbody></table>

### CSS Length Units&#x20;

Formsort recommends using a handful of the [CSS length units](https://www.w3schools.com/cssref/css_units.asp) when defining the dimensions of a component (margin, padding, font size, etc.)

<table><thead><tr><th width="140.36358642578125">Property</th><th>Definition</th></tr></thead><tbody><tr><td>rem<br></td><td><p>Relative to the <a href="/pages/-MJTTsfI8h6fH_kNrHdr#understanding-rem-sizing">base font size</a>. This allows responsiveness if there are differing base font sizes for different viewports. </p><p>Ex: 1.5rem of a base font size of 16px will be 24px (1.5 x 16 = 24).</p></td></tr><tr><td>vw</td><td><p>Relative to 1% width of the viewport. </p><p>100vw = 100% width of the viewport. </p></td></tr><tr><td>vh</td><td><p>Relative to 1% height of the viewport. </p><p>100vh = 100% height of the viewport.</p></td></tr><tr><td>% </td><td>Relative to the size of the parent element. <br>Setting 100% on a width property will allow that component to take 100% of the available space. </td></tr><tr><td>px</td><td>Pixel: 1px = 1/96th of 1in. <br>Pixels are unresponsive, so it is considered an absolute value. <br>If the content max width setting is 500px, it will be 500px regardless of changes in the viewport size (you can use <a href="/pages/-MJSrMcVkJuLD03lsmU3#dimension-variables">dimension variables</a> to create responsiveness, however)  <br>Useful for setting an absolute value for a component, i.e. a base font size or the width/height of a component. </td></tr></tbody></table>


# Variant settings

The Settings menu gives you more control over your Flow's behavior.

Each **variant** in Formsort has its own **Settings** tab, where you can control how your form behaves and what users see—everything from how returning responders are handled to the copy shown after completion.

These settings apply **per variant**, meaning changes here won’t affect other variants in your flow.

Settings are divided into three main areas:

* **Strings**: Customizable text that appears throughout the form.
* **Responder Behavior**: Controls the behavior for responders returning to your flow.
* **Group Ranking API**: Manages logic for dynamically ordering groups (see [Group Ranking API](/form-behavior-settings/variant-settings/group-ranking-api) for more info).

<div align="left"><figure><img src="/files/BroR0vIq8P02EUk98cLI" alt="" width="404"><figcaption></figcaption></figure></div>

### Strings

These fields control certain text shown to responders throughout the flow.

**Document title**\
Sets the text that appears in the browser tab when the form is open.

**Answer required text**\
Formsort requires users to answer all questions on a step before continuing (unless the question is marked *optional*). This setting lets you customize the error message shown when a required question is left unanswered.

<div align="left"><figure><img src="/files/q1MaqRwdqlaSrltSX0al" alt="" width="423"><figcaption></figcaption></figure></div>

{% hint style="info" %}
**Hide the "answer required" message**\
While the error behavior can't be disabled, it *can* be hidden visually. In the **Theme Editor**, go to the **color variables** menu and set the `error` color’s **transparency to 0%**.\
If an `error` variable doesn’t exist, you can create one—**note that variable names are case-sensitive**.
{% endhint %}

**Footer text**\
Text placed at the bottom of the form's content area. Often used for disclaimers, legal copy, or other always-visible messages.

<div align="left"><figure><img src="/files/4Kc3OQ4RHC9aOutz45cg" alt="" width="419"><figcaption></figcaption></figure></div>

**Optional question label**\
This sets the text that appears (in italics) next to a question label when the question is marked optional. The default is `"optional"`, but you can customize this string.

<div align="left"><figure><img src="/files/UnLntDHbCQ4DXoMPXpKi" alt="" width="405"><figcaption></figcaption></figure></div>

***

### Behavior

These settings control the **functionality** and **navigation behavior** of your form.

<div align="left"><figure><img src="/files/ADMazMKJo58kxqkAwUvg" alt="" width="397"><figcaption><p>The form behavior section</p></figcaption></figure></div>

#### **Returning responder behavior**

Defines how Formsort handles users who revisit your form. For a full breakdown, see [Returning responder behavior](/form-behavior-settings/variant-settings/returning-responder-behavior). This includes the setting to **Start each session as a new responder**.

#### **Enable URL navigation**

{% hint style="info" %}
URL navigation is not currently supported in **embedded flows**.
{% endhint %}

When enabled:

* Users can use their browser’s **Back** and **Forward** buttons to move through unlocked steps.
* You can link directly to unlocked steps using either the **step index** (default) or the **step ID** (if enabled).

**Example using step index:**\
`https://<client-ID>.formsort.app/flow/<flow-name>/variant/<variant-name>#1`

**Example using step ID:**\
`https://<client-ID>.formsort.app/flow/<flow-name>/variant/<variant-name>#<step-ID>`

#### **Enable restart once flow completed**

When this is on, responders will see the option to **restart** the form once they've completed. Their original `responder_uuid` is preserved, but Formsort clears all saved answers so they can start fresh.

* Previous submissions to your data store remain untouched.
* Useful for forms that need to be filled out multiple times by the same person.
* Responders **won’t** see the restart screen if the form ends with a redirect.

<div align="left"><figure><img src="/files/DrqKryWCRN5wHmQPVgoR" alt=""><figcaption></figcaption></figure></div>

#### **Enable close once flow completed**

If your flow is **embedded** in another web page, this setting adds a link to **close the embed** once the user finishes the form.

<div align="left"><figure><img src="/files/9r4WyavAyYzZNyHA1QB5" alt=""><figcaption></figcaption></figure></div>

#### **Enable autosave**

When enabled, answers are automatically saved:

* **3 seconds** after the user finishes typing or selecting.
* Even if they leave and return, their progress will be restored.
* Answers will be sent to connected integrations **if** [submission frequency](/integrations/getting-data-out/submission-frequencies) is set to *"When the flow is finalized or abandoned."*


# Form behavior for returning users

What happens when visitors return to a flow they already started on?

When a user returns to a form they previously started but didn’t finish, you can control where they re-enter the flow. This behavior applies to both **standalone flows** and those **embedded into your sites**.

By default, Formsort stores a responder’s answers for about **3 weeks**. After that, stored data associated with their `responder_uuid` is purged, and they will start fresh on their next visit - **unless** you've changed configuration of the settings below.&#x20;

Go to the **Settings** tab in any variant and look for **Returning responder behavior** to find the configuration menu.

{% hint style="info" %}
For more information about answers storage, see our [data retention policy](https://docs.formsort.com/handling-data/philosophy).
{% endhint %}

<div align="left"><figure><img src="/files/8UZPhx08NIo8YCZzZbi8" alt="" width="353"><figcaption></figcaption></figure></div>

***

### Options

#### Start each session as a new responder

When enabled, every visit is treated as a brand new session:

* **No previous answers** are restored.
* A new `responder_uuid` is generated on **every visit**, including page refreshes.
* The user is not **pinned to a variant** they previously saw.
* Any Formsort-related **cookies are cleared**, including [external variable](/response-data-collection-and-management/variable-schema/external-answers) cookies.

This setting is useful if you want users to **always start fresh**. For example, they can restart the form by simply refreshing the browser or opening it in a new tab.

If you [force-set a responder UUID](/core-concepts/how-data-works-in-formsort/responder-uuids#manually-setting-the-responder-uuid) manually via URL, answers from previous sessions won’t be restored.&#x20;

{% hint style="warning" %}
If this setting is **disabled**, Formsort stores the responder’s UUID in browser cookies. When they return to the form using the same browser, their **previous answers are restored**.
{% endhint %}

Learn more about [responder UUIDs](/core-concepts/how-data-works-in-formsort/responder-uuids) and how Formsort tracks responders.

### Resume session on page refresh

This setting is only available when "Start each session as a new responder" is enabled. It allows users to resume their session after a browser refresh, preserving their `responder_uuid` and the state of the form up to that point.

### Start at furthest point

(Default behavior)\
Responders will resume the form from the **furthest step** they previously reached. All previous answers will be restored.

This allows users to **continue where they left off**, even after refreshing their browser.

### Start at beginning, discard answers

Responders will restart the flow at the **beginning**, and previous answers will be **discarded**. However, their existing `responder_uuid` will still be used.

This is useful when users should **start over**, but you want to **retain their responder identity** for tracking or analytics purposes.

### Start at beginning, keep answers

Responders start at the **beginning** of the form, but their **previous answers are restored** into the fields.

This is helpful when you want users to **review or revise** their answers during a second pass through the form.

{% hint style="info" %}
Want to learn more about other behavior settings? Go back to the [Settings](/form-behavior-settings/variant-settings) overview section.
{% endhint %}


# Group ranking API

Dynamically reorder groups at runtime

<figure><img src="/files/kqpkbuYpEV1N29mVW676" alt=""><figcaption></figcaption></figure>

In the standard use of Formsort, steps can be placed into [groups](/creating-flows/building-a-new-flow/organizing-using-groups) for organizational purposes; the steps will then appear in the order of their parent groups. Groups and steps can be re-arranged by dragging and dropping, but once they are published they will always be seen by the responder in the fixed order.

Using the **Group ranking API,** it's possible to re-arrange groups dynamically. This allows for you to decide on-the-fly which content to show the responder, in which order.

One example use case would be a flow with many different offers that are shown to responders. Based on information gathered within a flow, some offers might be more likely to be relevant to the responder: the group ranking API allows for showing those first, and delaying (or hiding entirely) less-relevant groups.

## Setting up dynamic group ranking

To reliably refer to groups in the dynamic ranking, we first need to define unique group IDs. To do so, set the **Group ID** with a group selected on each group that you want to reorder.

<figure><img src="/files/mz05QEPJExf3BdV38kfX" alt=""><figcaption></figcaption></figure>

The group ranking API is configured by adding a URL to the Variant settings.

<figure><img src="/files/AHMcSjb3YioFn8DxKvtJ" alt=""><figcaption></figcaption></figure>

Like most URLs within Formsort, the URL can be templated with answers that are present within the flow: the URL will be fetched any time one of the inputs is changed.

### Expected response

The group ranking API expects a JSON response that is an object with string keys corresponding to the group ID, and values corresponding to their rank, which will be used to sort the groups.

* Groups with a lower rank will appear before groups with a higher rank
* Groups that are defined within the flow but that are not present in the ranking, or do not have a group ID defined will be given a ranking of 0.5.
* Groups given a ranking of -1 will be hidden in the flow
* The sort is *stable* - if a ranking contains multiple groups each with the same rank, the groups will appear in the order that they were originally defined.
* Groups that have already been completed or entered by the responder will not be re-ranked.

For example, imagine a flow with four groups: Intro, About You, About Your Pet, and "Bonus Content". Let's look at the following ranking response

```
{
  "intro": 0.1,
  "about-you": 0.9,
  "bonus-content": -1
}
```

This would result in a flow with three groups, in this order:

1. Intro (has a low rank of 0.1)
2. About your pet (it does not appear in the ranking, so it is given the default rank of 0.5)
3. About you (has a high rank of 0.9)

The Bonus content group would not appear in the result since it was given a rank of -1.

## Notes and suggestions

Be careful about conflicting sources of truth when it comes to determining which groups are enabled. If a group is disabled according to conditions and logic defined within the flow itself, it will always be hidden, even if the group appears in the ranking response.

Don't return orderings that would violate dependencies between groups, such as a group that uses an answer collected in a previous group. Such invalid configurations may lead to problems and should be avoided.


# Navigation sidebar

When **Show navigation sidebar** is enabled, a table-of-contents-style panel appears on the left side of the flow. This sidebar displays step groups and completed steps, allowing responders to easily navigate backward through the flow.

<div align="left"><figure><img src="/files/BFXGdTILMRL5Kp3FAAqf" alt="" width="539"><figcaption></figcaption></figure></div>

{% hint style="info" %}
It is not yet possible to style the navigation sidebar.
{% endhint %}

#### Access to future steps

* **Allow access to future steps** → Enables navigation to steps the responder has not logically reached yet.
* **Show disabled steps as read-only** → Displays future steps without allowing input until their logic is valid. This lets responders preview the flow without prematurely submitting data.


# Schema (variables)

Use and classify answers and other data once it has been collected.

### Variables (Answers)

Variables in Formsort allow you to use and classify answers and other data once it has been collected. Every **question** in your form is associated with a **variable** where the responder's answers are stored. These variables are referred to as **variables from questions**.

<div align="left"><figure><img src="/files/T031GfDkKMhGA4s66qEK" alt="" width="474"><figcaption></figcaption></figure></div>

{% hint style="info" %}
:bulb: Additionally, the following types of variables return values that are also stored in variables, though their resolution mechanism differs:

* **Externally provided**
* **API lookups**
* **Calculated inline**
* **System library**
  {% endhint %}

### Naming variables <a href="#naming-variables" id="naming-variables"></a>

You should update variable names to something that captures the semantic meaning of the information collected. For example, `first_name` is a better-named variable than just `name` since you may end up collecting multiple names in a flow.

#### Renaming <a href="#renaming" id="renaming"></a>

It's safe to rename variables within Formsort. References like templated strings or logical conditions will update automatically.

Note that if you are sending data externally (through integrations or analytics), you will want to take care with renaming, since you may have downstream dependencies that depend on an answer name taking a specific value. For example, you may have configured an email service to use `user_first_name` within an email template, which Formsort cannot "see", so if you changed the variable name to `first_name` that email template would break.

{% hint style="info" %}
See [Publishing schemas](/publishing-schemas) for an approach on keeping your downstream dependencies safe by requiring forms to implement particular schemas.
{% endhint %}

### Variable data types

Formsort uses standard **JavaScript data types** for all variables, including answers collected from responders.

<table><thead><tr><th width="177.63641357421875">Type </th><th width="431.27264404296875">Examples </th></tr></thead><tbody><tr><td>string </td><td><code>"Olivia"</code>, <code>"Some\multi-line\string"</code></td></tr><tr><td>number</td><td><code>5</code>, <code>3.14</code></td></tr><tr><td>boolean</td><td><code>true</code>, <code>false</code></td></tr><tr><td>object</td><td><code>{ city: "Brooklyn", state: "New York" }</code></td></tr></tbody></table>

Answer values can also appear as **arrays**, particularly when:

* You're using **repeated questions**
* A **select question** allows multiple selections

You do not need to manually assign a data type for answers collected from questions. The appropriate type will be automatically inferred based on the question type that sets the variable.

#### Subtypes

In addition to standard JavaScript data types, Formsort variables can have a **subtype**—a higher-level classification that provides additional semantic meaning. These subtypes are used internally by Formsort to apply special handling or formatting logic.

<table><thead><tr><th width="128.45452880859375">Subtype</th><th width="128.18182373046875">Base Type</th><th width="247.4544677734375">Example</th><th width="198.818115234375">Note</th></tr></thead><tbody><tr><td><code>email</code> </td><td><code>string</code> </td><td><code>"hello@formsort.com"</code> </td><td>Validated as proper email format</td></tr><tr><td><code>date</code></td><td><code>string</code></td><td><code>"2019-01-11"</code></td><td>Always formatted as ISO 8601</td></tr><tr><td><code>datetime</code></td><td><code>string</code></td><td><code>"2019-01-01T01:30:00.000Z"</code></td><td>Also ISO 8601 format, with time</td></tr></tbody></table>

These subtypes help ensure consistency across flows and enable certain features, such as validation, formatting, and classification for integrations.

{% hint style="info" %}
See [Publishing schemas](/publishing-schemas) for an approach on keeping your downstream dependencies safe by requiring forms to implement particular schemas.
{% endhint %}

***

## General variable settings

The settings found here are general to all type of variables (from questions, API variables, external variables, etc), and can be found in the editing menu of any of them.&#x20;

<figure><img src="/files/zXl9o6v6HCbzOSg08mbb" alt=""><figcaption></figcaption></figure>

### Readable description

It's helpful to keep your answer variable names short and succinct, since you'll be using them throughout the flow. A variable like `current_interest_rate_pct` is perfect: it describes unambiguously what is stored there.

However, if you're making a dashboard which consumes form data, `current_interest_rate_pct` might not be enough for your colleagues to understand outside of the context of a particular flow. Neither would the label of the question, which might be something intended for responders, like `What is your current interest rate, {{first_name}}?`

For that reason, it's possible to set a **Readable description** on answer variable names. You can set these to text like "Borrower's current self-reported interest rate" more suitable for downstream consumers.

[Webhook](/integrations/integration-reference/webhooks) payloads can be set to include the descriptions, allowing downstream systems to access the descriptions when generating dashboards, reports, etc.

### Data classification

D**ata classification** provides a mechanism for associating a variable with functional concept that may be used in or have implications for downstream integrations.

For example, setting a data classification for `responder_email` on an answer variable will enable the following behavior:

1. The answer will be treated as PII, and by default not sent to third-party integrations.
2. When the answer is collected for the first time, the `EmailCollected` event will be fired.

#### Available data classifications

| Semantic meaning              | Type (subtype)   | Description                                          |
| ----------------------------- | ---------------- | ---------------------------------------------------- |
| `responder_email`             | string (email)   | The responder's email                                |
| `responder_first_name`        | string           | The responder's first name                           |
| `responder_last_name`         | string           | The responder's last name                            |
| `responder_marketing_consent` | boolean          | Whether the responder consents to marketing messages |
| `responder_phone`             | string (phone)   | The responder's phone number                         |
| `responder_dob`               | string (date)    | The responder's date of birth                        |
| `responder_mailing_address`   | object (address) | The responder's mailing address                      |
| `responder_other_pii`         | any              | Generic personally-identifying information           |

{% hint style="info" %}
We'd love to expand this list to the most meaningful data classifications, so chat us if you'd like us to add more.
{% endhint %}

### Keep in URL if present on load

When a flow is loaded in Formsort, any URL parameters that match any answers defined in the flow will be placed into the answers and removed from the URL. To learn more about that, read about getting data in from [URL parameters](/importing-data/url-parameters).

If you would like instead for an answer to remain in the URL, enable **Keep in URL if present on load**. This might be helpful if you are matching up campaigns on a URL string, and want to keep things like the `utm_source` consistent across pages.

{% hint style="warning" %}
Anything in the URL will most likely be recorded by any analytics scripts you include in the integrations, so avoid using this setting for personally-identifying information.
{% endhint %}

### Storing answers in cookies

Another option is to store answers in cookies. In cases where you are collecting more sensitive information, this might be a better option, since you might not want to pass sensitive information unencrypted via URL. In order to do so, you can enable the **Read/Write in Cookie** option.&#x20;

{% hint style="info" %}
Configuring a domain is required to store answers in cookies.
{% endhint %}

### Don't send to analytics

<figure><img src="/files/mBBbj4aGjhc6kbCTr7lE" alt=""><figcaption></figcaption></figure>

Un-checking this box will prevent the answer variable from being published in the payload. By default, this box is checked.&#x20;

{% hint style="info" %}
This feature is disabled by default, as [Amplitude](/integrations/integration-reference/amplitude), [Google Analytics](/integrations/integration-reference/google-analytics), [Google Tag Manager](/integrations/integration-reference/google-tag-manager), and [Segment](/integrations/integration-reference/segment) can all be configured to send **all** data during an analytic event (see [analytics events](/integrations/getting-data-out/analytics) for more information). Use caution when sending all data, and use "Don't send to analytics" on a specific answer variable to protect user privacy, or prevent unnecessary data from being sent to a third party.
{% endhint %}


# Variables from questions

By creating questions, answer variables are created by default, using the title of the question automatically. It's a good idea to rename the auto-generated answer values to something that's a better reference. For example, a question with a title "What is your name?" will auto-generate an answer variable name of `what_is_your_name`, which is overly verbose. Something like `user_first_name` is much clearer.

### Viewing answers collected in questions

In the **Variables -> From questions** list you will see the answers that result directly from questions presented to the user. When an answer is selected, you will see a link to the relevant question in the content editor.

### Providing the same answer with two different questions

Sometimes, you will want to collect the same answer by asking two different questions, like when you want to ask for a user's email in two different paths in a flow.

To do so:

1. Create the first question
2. Set its **Answer variable name** to your desired value
3. Create the second question
4. Set its **Answer variable name** to the same one as for the first question
5. You will be prompted with a message: *This variable name is already in use.* Choose **Re-attach to existing variable** to re-use the existing question's variable.

Note that you will only be able to re-use answer variables with compatible answer types, such as using a `string` variable with `text` questions. If you are trying to add multiple select questions that all set the same answer variable, please ensure that they have the same set of choice values.

{% hint style="info" %}
An easy way to provide the same answer with two different questions is to use copy + paste. While pasting you will be provided with an opportunity to determine how the questions map to the answers in your flow, and you can choose **Map to an existing variable.**
{% endhint %}


# Externally provided variables

Capture values passed into flows, even if not collected in questions (also known as hidden fields).

There are many cases where you might want to pass data into a form that would never be provided by the responder directly, like:

* tracking information, such as unique identifiers, account IDs, etc;
* configuration variables, which enable or disable certain functionality.

External variables are created in Formsort to capture parameters that are passed into a flow via the URL.&#x20;

{% hint style="info" %}
Consult the [URL parameters](/importing-data/url-parameters) page for more information on the proper format and other details.
{% endhint %}

If you're familiar with HTML form semantics, this is the equivalent of a `<input type="hidden">` field which is part of a form, but not provided by the user.

Much like a variable provided by a user, external variables can be used in [variable templating](/variable-templating), [conditional logic](/conditions-and-logic), and in functions for [locally-calculated variables](/response-data-collection-and-management/variable-schema/calculated-answers).&#x20;

## External variable setup

To use an external variable for URL parameter capture, in the **Variables** tab, select **Externally provided** > **Add External Variable**, and set the name of the expected URL parameter in the **Variable** name field.

For example, if a user is being directed into your Formsort flow with `utm_source` as a URL parameter, the URL will look something like this:&#x20;

`https://<YOUR_FLOW_URL>/?`**`utm_source={{example_utm_value}}`**

In order for Formsort to capture the value of `utm_source`, you need to create an external variable called `utm_source`. This variable will **automatically** store the value of the URL parameter when the flow loads.

<figure><img src="/files/3xpqB2vcS4Ey6KFaih5z" alt=""><figcaption><p>Create an external variable called utm_source. See settings below</p></figcaption></figure>

{% hint style="info" %}
External variables are automatically included in answers payload delivery, if they are provided in the URL at flow load. No further configuration is needed once the form is deployed with the update :raised\_hands:
{% endhint %}

## Settings

### Is array

Use the **Is array** option if the field passed into the flow has an array format. For more information on the accepted query parameter convention, see [URL parameters](/importing-data/url-parameters#setting-array-answers).

### Required

When the **Required** field is enabled, two things will happen when the external variable is not passed in to a user session:&#x20;

* A warning will be logged to the browser console *during the user session*; and&#x20;
* An error field called **externalVarIssues** will be submitted in the `flowLoaded` event answer payload. This second point requires having an [analytics integration](/integrations/getting-data-out#sending-analytics-events) set up.&#x20;

{% hint style="info" %}
The **externalVarIssues** field is an object; see example:

`"externalVarIssues":{"ext_var_name":"missing"}`
{% endhint %}

### Keep in URL if present on load

When a flow is loaded in Formsort, any URL parameters that match any variable defined in the flow will be placed into the variables and removed from the URL. To learn more about that, read about getting data in from [URL parameters](/importing-data/url-parameters).

If you would like instead for a variable value to remain in the URL, enable **Keep in URL if present on load**. This might be helpful if you are matching up campaigns on a URL string, and want to keep things like the `utm_source` consistent across pages.

{% hint style="warning" %}
Anything in the URL will most likely be recorded by any analytics scripts you include in the integrations, so avoid enabling this setting for variables that set personally-identifying information.
{% endhint %}

### Storing answers in cookies

Another option is to store answers in cookies. In cases where you are collecting more sensitive information, this might be a better option, since you might not want to pass sensitive information unencrypted via URL. In order to do so, you can enable **Read/Write in Cookie** option.&#x20;

{% hint style="info" %}
Configuring a domain is required to store answers in cookies.
{% endhint %}

Take care when "start each session as a new responder" is enabled in your [Variant Settings](/form-behavior-settings/variant-settings), as the setting will remove Formsort-related cookies by default. See the **Prevent Editing** option below to guard against this.&#x20;

### Default value

When a **default value** is set, if a value is not present when a flow is loaded, the default value will be used.

A common use for this is to define control variables, similar to constants, that govern what parts of a flow are enabled, or function as a setting parameter.

### Prevent Editing

By default, external variables passed in as query parameters can be overridden in the flow. If, for example, `zip_code` is passed in as a query parameter, but a question then asks the user to enter their zip code to a variable `zip_code`, the external answer will be replaced by the submitted answer. If you want to ensure external answers passed in as query params **cannot** be overriden, enable the **Prevent Editing?** option.&#x20;

{% hint style="info" %}
**Prevent editing** can also be used to persist external variables that are read from/written to cookies, when "**start each session as a new responder**" is enabled.&#x20;

Read more about **start each session as a new responder** [here](/form-behavior-settings/variant-settings#behavior).&#x20;
{% endhint %}


# Calculated variables

Create derived variables by writing functions optionally using other variables

**Calculated variables** allow you to define and calculate new answers based on other answers present within the flow, by writing simple functions that are evaluated within Formsort.

Common uses of calculated variables include:

* Formatting answers&#x20;
* Capturing conditional logic that cannot be easily expressed
* Performing math on numbers or dates

Calculated variables behave just like any other answer: they can be used for [conditions](/conditions-and-logic), [templated](/variable-templating) into most text, and are sent to your analytics and integrations.

## Adding a calculated answer

From the **Variable** tab, select **Calculated variables**, and click **Add calculated variable...**

### Variable type

Sets the expected [data type](/response-data-collection-and-management/variable-schema#data-types) that we are expecting the calculation to return: `string`, `number`, or `boolean`

### Is array?

If **Is array?** is enabled, then we will expect the response to contain multiple answers.

### Getter function body

The **Getter function body** contains the Typescript code function body for the calculated answer.

For example, the following getter function body will result in the date 6 months from the current date:

{% code title="// Getter function body" %}

```javascript
function myFunction(): string {
  const today = new Date();
  const d = today.getDate();
  const future = new Date(today.setMonth(today.getMonth() + 6));
  if (future.getDate() != d) {
    future.setDate(0);
  }
  return future.toLocaleDateString();
}
```

{% endcode %}

To use existing answers within a getter function body, add the answers as variables. The following calculated variable will calculate the length of the answer variable labelled `first_name`

![](/files/-MUtNUS2OsvYUDBbvz1D)

{% hint style="info" %}
The `myFunction() { ... }` part of the getter function body is auto-generated and cannot be modified.
{% endhint %}

For more examples on creating calculating variables, see [Useful calculated functions](#useful-calculated-variable-function-examples) at the bottom of this page.

### Optional Variables

If any of the parameters to `myFunction` are optional, such as in the case of API variables that may or may not be passed in, make sure to check the **Optional** field next to the variable.

{% hint style="warning" %}
If a variable that is *not* optional is *not* passed to the function, the Getter function will not execute correctly!&#x20;
{% endhint %}

## When are calculated variables evaluated?

If a calculated variable has no answer dependencies, it is evaluated at the time that the flow loads.

For calculated variables with dependencies, by default, calculated variables are only evaluated once all of their required (non-*optional*) input variables are defined. If any of the required input variables become undefined, the calculated variable is cleared.

Imagine the following calculated answer, which uses the javascript [Math.max](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max) function to find the larger of two numbers, and we'll call `largest_number`:

{% code title="// Getter function body" %}

```javascript
return Math.max({{number_a}}, {{number_b}});
```

{% endcode %}

If only one or none of the input answers are defined, `largest_number` will remain undefined.

```yaml
number_a: undefined
number_b: undefined

largest_number: undefined

---

number_a: 17
number_b: undefined

largest_number: undefined
```

Only when all of the input answers are defined will the calculation occur.

```yaml
number_a: 17
number_b: 48

largest_number: 48
```

{% hint style="info" %}
For calculated variables with optional dependencies, the calculated variables will be evaluated before the optional dependencies have a value, and will then be re-evaluated whenever an optional dependency receives a value, such as in the case of a responder providing a value for an answer variable.&#x20;
{% endhint %}

#### Blocks advancing from current step?

Calculated variables are handled asynchronously, and the default calculated variable configuration does not guarantee that the variable will be available by the time answer payloads are submitted. For instance, if a calculated variable finally receives the dependencies it needs on a step that will also send a payload (see [submission frequency](/integrations/getting-data-out/submission-frequencies)), the user might advance past the step before the calculated variable has time to finish it's calculations and attach the result to the payload, resulting in a seemingly missing calculated variable.&#x20;

Enabling **Blocks advancing from current step?** in the configuration menu of your calculated variable will prevent the user from advancing past a step until calculations are finished processing. If no calculations have started yet (i.e. not all the dependencies are in), the step will not wait for the variable and the user will proceed. This ensures that calculated variables are present as the user proceeds through the flow, which helps to prevent logic errors down the line, and maintains data fidelity in the answers sent to your endpoint.&#x20;

### Conditional evaluation

For more control over when the calculated variable is evaluated, you can set a [condition](/conditions-and-logic) using **Is conditional?**

{% hint style="warning" %}
When using conditional evaluation, you must take care to handle `undefined` answers within the getter function body.
{% endhint %}

Using the above example of `largest_number`, we could add a condition using [Advanced logic](/conditions-and-logic/advanced-logic) to run the calculation whenever either number is defined.

{% code title="// Condition for either number\_a OR number\_b being defined" %}

```javascript
{
  "$or": [
    "number_a": { "$exists": true },
    "number_b": { "$exists": true },
  }
}
```

{% endcode %}

Now that we're evaluating the calculation even when the inputs are undefined, we need to change the getter function body to handle this case.

{% code title="// Getter function body" %}

```javascript
if ({{number_a}} === undefined) {
  return {{number_b}};
} else if ({{number_a}} === undefined) {
  return {{number_a}};
}
return Math.max({{number_a}}, {{number_b}});
```

{% endcode %}

### Re-calculate on load

If a calculated variable was calculated in a previous session, and the responder returns to the flow, the default behavior is *not* to re-calculate the answer.

To always recalculate the answer on load, enable **Re-calculate on load**. This is useful for situations when calculated variables are not idempotent, meaning that repeated invocations do not result in the same result, such as answers depending on the current date or time.

{% hint style="info" %}
Unless you need the variables in the answer's payload, avoid using this option as it has performance implications. Step conditional logic waits for calculated variables by default. Don’t use this option to ensure the conditional logic within the form is applied correctly.
{% endhint %}

## Useful calculated variable function examples

#### Get age from DOB

We can calculate a responder's age based on the date they enter for answer variable `patient_dob` in this example.

```typescript
function myFunction(patient_dob: string): number { //readonly line
  const birthDate = new Date(patient_dob);
  const today = new Date();

  let age = today.getFullYear() - birthDate.getFullYear();

  // Check if the birthday has already occurred this year
  const hasBirthdayPassed =
    today.getMonth() > birthDate.getMonth() ||
    (today.getMonth() === birthDate.getMonth() && today.getDate() >= birthDate.getDate());

  if (!hasBirthdayPassed) {
    age--; // Subtract 1 if the birthday hasn't happened yet this year
  }

  return age;
}
```

#### Calculate amount of days from today

Using this function, we can calculate the amount of days that have (or will) elapse from a date before or after today.&#x20;

```typescript
function myFunction(user_defined_date: string): number { // readonly line
  // turns dates (user_defined_date, today) into ms
  const date1 = new Date(user_defined_date)
  const date2 = new Date()

  // one day in ms 
  const oneDay = 1000 * 60 * 60 * 24

  // calculates time difference between two dates, in ms
  const timeDiffInMs = date2.getTime() - date1.getTime()

  // converts ms to days
  const diffInDays = Math.round(timeDiffInMs / oneDay)

  // return absolute value of the number
  // prevents negative number returns 
  return Math.abs(diffInDays)
}
```

#### Check responder answer against a list&#x20;

This function will return a boolean true/false value, based on whether or not the responder's answer to `State` is included in a list.&#x20;

```typescript
function myFunction(State: string): boolean { // readonly line
  const ineligibleStates = ["DE","HI","LA","MD","MA","NV","NJ","NM","NC","OH","OR","RI","SC","SD","TX","UT"]
   if (ineligibleStates.includes(State)) {
    return true 
  }
  return false 
}
```

#### Calculate BMI&#x20;

This function requires a `height` and `weight` input, and will return a calculation of the users Body Mass Index.&#x20;

```typescript
function myFunction(
  height_ft: string, 
  height_in: string, 
  weight_lbs: string
): number { // readonly line
// Convert height to meters
const heightMeters = (parseInt(height_ft)*12 + parseInt(height_in)) * 0.0254;
//Calculate and round
const bmi = Math.round(parseInt(weight_lbs) * 0.45359237 / (heightMeters ** 2) * 10) / 10;
return bmi;
}
```


# API lookups

Store answers from API calls.

Using an **API lookup**, you can fetch an [answer](/integrations/getting-data-out#sending-answers) from a server that returns JSON responses. To make the call dynamic, the lookup can use answers collected with the flow as request headers or query parameters.

Some examples of common uses of API lookups include:

* Finding the current price of an item.
* Locating the closest store to a user, based on their zipcode.
* Looking up an account ID based on a user's email.

{% hint style="info" %}
You can also look up [select choices from an API](/adding-questions-and-content/question-reference/select#dynamic-choice-loading) to make your Select components dynamic! This is defined directly at the select component.
{% endhint %}

The answers fetched in an API lookup behave just like any other variable: they can be used for [condition](/conditions-and-logic)[al logic](/conditions-and-logic), [templated](/variable-templating) into other components, and are sent to your [answers and analytics integrations](/integrations/getting-data-out).

## When are API lookups dispatched?

If an API answer has no dependencies (there are no templated answers within its URL), the API lookup will happen as soon as the flow loads.

If an API answer has dependencies, the API lookup will happen as soon as all of its inputs are defined, and will be re-run whenever those inputs change. If any of the inputs become undefined, the API answer will be cleared.

For more control over when the API lookup happens, you can set a condition for its evaluation with **Fetch conditionally**, or you can choose to wait with [**Calculate on submit**](#calculate-on-submit).&#x20;

{% hint style="info" %}
**Doing a simple calculation with variables you already have present?**

If you don't need fetch data from a server, you can use a [Calculated answer](/response-data-collection-and-management/variable-schema/calculated-answers) to perform a calculation directly within the form flow.
{% endhint %}

### **CORS considerations**

API-lookup `http` requests come directly from the responder's browser. If your server enforces [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) restrictions (CORS), you will need to whitelist the following in the `Access-Control-Allow-Origin` response header:

* `<client-id>.formsort.app`

<figure><img src="/files/S06bafYVz9z67LLxM5XS" alt=""><figcaption><p>You can find your Client ID here, among other places. </p></figcaption></figure>

* `studio.formsort.com` (for testing within the studio)&#x20;

Additionally, if you are using [Custom domains](/workspace-management/custom-domains) to host your flows, you will need to whitelist those domains.

{% hint style="info" %}
Alternatively, you can enable the wildcard **`*`** origin in the `Access-Control-Allow-Origin` header if you would like to disable CORS restrictions.
{% endhint %}

***

## Creating an API lookup

API lookups are created inside of [variants](/flow-variant-management#variants) are local to those variants.&#x20;

To configure an API lookup, in any variant go to **Variables > API lookups** and click **Add API lookup.**

Take a look at our API lookup editor settings and configurations below.

<figure><img src="/files/GgEWpAibP21vUqQPgySm" alt=""><figcaption></figcaption></figure>

### Method

You can change the REST method of the API lookup. Currently available is GET, POST, and PUT.

### URL

Provide the URL of the endpoint you wish to hit in the **API URL** field.&#x20;

<figure><img src="/files/NF85ILsB8mW3nVzoqnyo" alt=""><figcaption><p>Creating a dynamic URL for a GET request</p></figcaption></figure>

#### Templating variables into URLs

You can use existing answers from the flow to template the API lookup URL with the standard [templating](/variable-templating) syntax. **The API lookup will not be dispatched unless all of the templated variables are defined**, unless the variable is made optional (see below).

For example, if you ask the responder for their postal code in an answer with variable name `user_zip`, you can use that answer to look up store locations with a URL like

`https://example.com/api/stores?zip_code={{user_zip}}`.

**Optional parameters**

The default template formatting function default values for an API variable's URL. If you have a lookup like

`https://example.com?age={{age}}&promo={{promo | default ""}}`&#x20;

then the API will be dispatched whenever the `age` variable is defined. `Promo` does not need to be defined by the user, since it has a default value of an empty string (`""`).

<figure><img src="/files/bRHA2VvdqkqLovCggwU1" alt=""><figcaption></figcaption></figure>

### Result processing

<div align="left"><figure><img src="/files/YT8htI8G2gOVFe2j9E8U" alt="" width="563"><figcaption></figcaption></figure></div>

If your API returns a payload with a shape that isn't directly the desired variable type, you can process the result within Formsort to avoid having to change the shape of your API responses.

For the following result processing examples, consider the below JSON response payload for a list of stores:

{% code title="Sample JSON response body " %}

```json
// Object 
Result: {
  "person": {
    "name": {
     firstName: "Frodo"
     lastName: "Baggins"
    }
    "location": "The Shire"
  }
}

// Array 
[
  {
    address: "123 Main St",
    isOpen: false,
  },
  {
    address: "4 Back St",
    isOpen: true,
  }
]
```

{% endcode %}

#### JSON accessor

If the answer you want to access can be accessed directly with keys and indices, you can write out the path to the desired data with the **JSON accessor**. Formsort opens an object upon receipt, so it is unnecessary to include the object name in the accessor function.&#x20;

For example, in the object above, ~~`Result.person.location`~~ `person.location` would return `"The Shire"`. You can drill down a level as well: `person.name.firstName` would return `"Frodo"`.

In the array example, `0.address` would return the address of the first result, which in the above example would be `"123 Main St"`.

<div align="left"><figure><img src="/files/bixIfEGiQUS3BGkjBMS2" alt=""><figcaption></figcaption></figure></div>

#### Mapping function

For more complex processing of results, you can write a mapping function using javascript. The editor here uses the same calculated answer's [getter function body](/response-data-collection-and-management/variable-schema/calculated-answers).

This function has an argument, `res`, which is the body of the raw JSON object returned from the server. It should `return` the desired answer.

For example, if we'd like to access the address of the first *open* address from the response above, the mapping function would be:

```javascript
const openStores = res.filter(function(store) { return store.isOpen });
if (openStores.length) {
  return openStores[0].address;
}
return "undefined";
```

... which would result in `"4 Back st"`.

<figure><img src="/files/wOdZYWXDpaUzZEDSA9u5" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
The getter function will only be evaluated for a successful response. If the server returns any kind of 400 or 500 response, it will not be evaluated.
{% endhint %}

***

## Basic configuration settings

<div align="left"><figure><img src="/files/FXT2MiUAonsymQyDXaCL" alt=""><figcaption></figcaption></figure></div>

### Variable type

**Variable type** sets the expected [data type](/response-data-collection-and-management/variable-schema#data-types) that we are expecting from the response: `string`, `number`, or `boolean`. This needs to be properly defined for the API lookup to return successfully.&#x20;

### Is array?

This needs to be set you expect the response to contain multiple answers in an array format. &#x20;

### Calculate on Submit

As stated earlier, the API lookup will be dispatched either immediately if there are no variable dependencies templated into the Request Headers or URL query parameters, or as soon as the templated variables are all defined by the user.&#x20;

To have the API lookup wait until the user proceeds from the past the step where the **last** variable is defined, enable **calculate on submit**.&#x20;

### Re-calculate on load

If an API answer was retrieved in a previous session, and the responder returns to the flow, the default behavior is *not* to re-fetch the answer.

To always fetch the answer on load, enable **Re-calculate on load**. This is useful for situations when calculated answers are not idempotent, meaning that repeated invocations do not result in the same result, such as answers depending on an API response that changes over time.

### Fetch conditionally

You can create [logic conditions ](/conditions-and-logic#simple-logic-editor)to fine-tune when the API lookup should be dispatched.&#x20;

### Blocks advancing from current step

This should be enabled if you'd like to wait until the API variable has completed the network call and any API variable functions have resolved. This is especially useful if the next step requires the return of the API for conditional logic or [variable templating](/core-concepts/formsort-quickstart-guides/template-your-variables).&#x20;

***

## Extracting multiple fields from API response

Sometimes, you may need to extract multiple fields from a JSON response body. Imagine an endpoint like `GET /userLookup?email={{email}}` . This endpoint takes the email address value from the `email` query param. And, if the email address is found in the database, returns the id and name associated with that email. In many cases, you may need to extract both fields -- `id` and `name`  -- from the response body.

```
// Sample JSON response body: GET /userLookup?email={{email}}
[
  {
    id: 1234,
    name: "Shinji Ikari",
  }
]
```

To extract multiple fields from a single JSON response object, you need to create multiple API variables: one API variable per field. Of course, you likely would prefer not to have to make a separate API call for each field. So, **to avoid making multiple API requests when using multiple API variables, simply give every API variable the same URL string and query params**.

By creating multiple API variables with the same URL string and query params, a single request to the given URL is made. However, the response is processed by every API variable with that URL string and query params signature. So, by using different **JSON accessors** or **Mapping Functions** (see above) for each API variable, you can extract different fields. Continuing the example above, we could use the set of API variables below to successfully retrieve the `id` and `name` fields. Note that both API variables are the same, except for their accessors. This will result in a single API request, but allow us to retrieve both fields.

```
// API variable 1:
Variable Name: responder_id
URL: /userLookup
Query params: email={{email}}
JSON accessor [0].id

// API variable 2:
Variable Name: responder_name
URL: /userLookup
Query params: email={{email}}
JSON accessor [0].name
```

{% hint style="info" %}
If the API variables have different conditions for the **Is Conditional?** field, whichever API variable has their conditions met first will be the API variable which gets to make the API request. Whatever response is received from that request will be used across all other API variables with matching API URL string and query params.
{% endhint %}

***

## Awaiting responses

If there is a pending API answer lookup, and the user is trying to advance to a step that uses the resultant answer, a loading indicator will be displayed while they await the result of the lookup.

{% hint style="info" %}
**Avoiding the loading spinner**

Consider interleaving steps that provide inputs for API answers with steps that contain static content, or content orthogonal to the API lookup.

That way, even if an API request is slow, it will not matter, as the user will be viewing other content while the request happens in the background.
{% endhint %}

## Handling errors

If an API answer request fails, or returns an invalid response, the answer will be undefined, and the answer will be marked as failed.&#x20;

You can use the [Had a loading error](/conditions-and-logic#logical-operator-reference) condition operator on a group, step, or question to affect the logic of a flow based on a failed answer, for example to put up a custom error message, or provide the responder an alternative way forward.


# System Library variables

Built-in answers about the responder's context and state.

<div align="left" data-full-width="false"><figure><img src="/files/4ZHtUzLUbSLTnVCU3f87" alt=""><figcaption><p>System Library can be found in any variant, under Variables -> System Library</p></figcaption></figure></div>

## Overview of the System Library

**System Library variables** allow access to built-in attributes about the responder's environment or context. They allow you to reach into the internals of Formsort, or the responder's browser, and pull out information that may be useful to you, like user device or OS, or the label of the [variant](broken://pages/131sNX7KpIPebQI6dDDT#variant) the user is visiting.

Any variables added here will automatically be sent along with your answers to any integrations/configurations you have set up, and do not need to be added in-flow in any way. They can, of course, be templated using [variable templating](/core-concepts/formsort-quickstart-guides/template-your-variables), or used in [conditional logic](/conditions-and-logic) statements at your discretion. &#x20;

## Available answer definitions

### Amplitude device ID

Sends the Amplitude device ID, if the device ID was set in the URL and the Amplitude integration is active with "Use device id from URL" enabled.&#x20;

### current group ID

The ID of the current [Group](/creating-flows/building-a-new-flow/organizing-using-groups) the user is in (falling back to the Group Index if a Group ID has not been set). &#x20;

### current step ID

The ID of the current step (falling back to the current step index if an ID is not set). The [Step ID](/creating-flows/building-a-new-flow/steps#step-id) is useful to include if using any analytics integrations, in order to analyze step-level performance. This id can be manually set in the Step Settings.&#x20;

### current step index

The index of the current step, which is the number automatically assigned to the step according to where it is in the flow (e.g. first step is step "0", second step is step "1", etc.). The step index cannot be manually set.&#x20;

### current step URL&#x20;

The URL for the current step, including the page index (`https://{flow-url}#1` for the first step of a page).

If "Use step ID instead of index" is enabled in [Variant Settings](/form-behavior-settings/variant-settings#behavior), the current step URL will instead look like `https://{flow-url}#step-id`.&#x20;

### **expected redirect url**

Where is the user getting redirected?&#x20;

Will be set even if not currently redirecting: this is where the responder would be redirected at any point in the flow.

When the flow is being finalized, this is the same as the URL they were finally redirected to.

### flow label

The string label identifying the flow that was loaded.

### **Formsort environment**

Which [environment](/publishing-and-deployment/environments) is the responder in?

### host

This variable will include the base flow url in the answer payload.

### **is Android?**

Is the responder on an Android device?

Useful as a condition for displaying an Play Store link if you have an app.

### is iOS?

Is the responder on an iOS device, such as an iPhone?

Useful as a condition for displaying an App Store link if you have an app.

### is Mac OS?

Is the responder on Mac OS?&#x20;

### **is mobile**

Is the responder on a mobile device, regardless of manufacturer?

### is returning responder

Has the responder been to this flow before?

### is Windows?

Is the responder on Windows OS?&#x20;

### Jornaya lead ID

The Jornaya lead ID. Will only be set if you are using the [Jornaya integration](/integrations/integration-reference/jornaya) in the active environment.&#x20;

### **previous step url**

The URL of the previous step, useful for back links.

### **referrer**

The URL that form user arrived from, corresponding to `document.referrer`.

May be undefined if the user navigated directly to the flow, or if the referring page does not send the referrer per their [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP).

{% hint style="warning" %}
Take care with collecting the **referrer**: it may contain personally-identifying information, if the referring page puts user data in its own URL and does not have a Content Security Policy defined.
{% endhint %}

### responder UUID

The [responder uuid](/core-concepts/how-data-works-in-formsort/responder-uuids) of the responder.

### session UUID&#x20;

The UUID of the session, unique for every time a flow is loaded. Reloading the browser or navigating back to the page will create a new UUID.&#x20;

### Trusted Form certificate URL&#x20;

The URL of the Trusted Form certificate issued for the session. Read more about the Trusted Form integration [here](/integrations/integration-reference/trustedform).&#x20;

### user agent

Navigator user agent, meaning the browser the responder is using. Includes version number.&#x20;

### variant label

The string label identifying the variant that was loaded.&#x20;

### variant revision UUID&#x20;

The UUID of the variant revision that was loaded for the responder.&#x20;

***

{% hint style="info" %}
If you have any suggestions for our System Library chat us in Intercom!&#x20;
{% endhint %}


# Orphaned variables

A home for all your unresolved variable dependencies.

<figure><img src="/files/m2m8c4xZ7skvyadwXiQG" alt=""><figcaption><p>An orphaned "dob" variable</p></figcaption></figure>

There are cases where an answer will no longer be collected within a flow, but it still has dependencies. For instance, you might delete a `date` question that collects a user's date of birth, however there is a [calculated variable](/response-data-collection-and-management/variable-schema/calculated-answers) function that requires that `date` input to calculate an age.&#x20;

In this case, the `date` answer will remain in the flow as an **orphaned** answer.&#x20;

{% hint style="info" %}
Generally, you don't want to have orphaned answers. We're working to make it harder and harder to generate them within flows.
{% endhint %}

## Resolving orphaned answers

### Re-use  variable&#x20;

<figure><img src="/files/qxWgeljG2wDuTCpvpi9b" alt=""><figcaption></figcaption></figure>

### Deleting orphaned variables

In order to delete an orphaned variable, you must remove it from any dependencies or "uses" it may have.&#x20;

Using the example at the top of this section, the `date` question that collects a dob would need to be removed as an input to the calculated variable that outputs an age.&#x20;

Once you see that **uses** are at 0, you will have the option to delete the variable.

<figure><img src="/files/7JFD2DyTNNvO8BUHQkVl" alt=""><figcaption></figcaption></figure>


# Saving & retrieving responses

Formsort securely stores form answers, allowing you to view, manage, and download them from the responses table. Responses are encrypted and stored in compliance with HIPAA standards.

### **Formsort answer storage**

All answers, including partial ones, are by default saved and accessible from the **Responses** tab in the Formsort dashboard. Once a responder starts filling out a form, their answers are saved by default, even if they don’t finish the form (moving it to a finalized state).

Answers **cannot be deleted** from this table but can be **archived** to remove them from the active response view.

<figure><img src="/files/9ErJucDEbFZXLgYxsIn9" alt=""><figcaption></figcaption></figure>

***

### **Returning responders and response storage**

The responses table shows the latest answer set for a specific `responder_uuid`.  As a result, answers are saved and displayed depending on your [flow settings](/form-behavior-settings/variant-settings#returning-responder-behavior).&#x20;

You can control whether returning responders' answers overwrite previous responses, create new responses, or are blocked from submitting again.

* **Replace previous responses with new answers**
  * Enable **"Restart once flow completed"** and disable **"Start each session as a new responder."**
  * Each time a responder returns, their new answers overwrite the previous response.
* **Save each new session as a separate response**
  * Enable both **"Restart once flow completed"** and **"Start each session as a new responder."**
  * A new `responder_uuid` is assigned for each session, storing submissions separately without overwriting previous responses.
* **Prevent responders from submitting again**
  * Disable both **"Restart once flow completed"** and **"Start each session as a new responder."**
  * Once a responder completes the form, their response remains stored, and they cannot submit another.

These settings determine how responses appear in your responses table and how new or returning responders’ data is recorded.<br>

***

### **Downloading responses and file uploads**

You can export **responses** from Formsort as a **CSV**, which support batch downloads with a limit of up to 100 responses per export.

<figure><img src="/files/fYQE4m09xt9MGIh8iyr7" alt=""><figcaption></figcaption></figure>

**CSV exports for file uploads**

The response CSV will show the **filename only** for any image or file upload fields. It does **not** include a download link or storage path.

{% hint style="info" %}
Filenames in the CSV are provided for reference and matching. Use the **Responses** UI to access the actual files.
{% endhint %}

**Downloading the actual files**

To view or download uploaded images/files, use the **Responses** tab in the Formsort Studio. Each response that contains an upload will display a **link** in the UI that lets you open (for supported formats) or download the file.

***

### Downloading responses as a PDF&#x20;

You can download an individual response set as a PDF directly from the **Responses** tab. To do this, select a response and expand its details panel, then click **Download PDF** in the top-right corner.

<figure><img src="/files/ibqW4rILqBBoMoRx4TBp" alt=""><figcaption></figcaption></figure>

PDF downloads are generated per response and do not support batching. Each PDF contains the full set of answers for that specific response.

***

### **Archiving answers**

If an answer is no longer needed in the active view, it can be **archived.** Archiving removes answers from the default table view while keeping them stored securely.

<figure><img src="/files/EZiaVawujiVLwMlxBwSz" alt=""><figcaption></figcaption></figure>

***

### **Email notifications for form answers**

You can receive email notifications when new responses are submitted. Choose from two options:

* **Every response** - Receive an email immediately when a form is completed.
* **Daily summary** - Get a single email at 5 PM UTC with a summary of all responses from the day.

Email notifications help you stay updated on new submissions without manually checking the **Responses** table.

These are personal notification settings - each teammate with access to this section needs to enable them individually to receive emails. Turning them on doesn't notify your entire team; it only applies to the user who enabled them. Notifications are sent to Formsort users, not form responders, and are intended for your internal team.

<div align="left"><figure><img src="/files/iANlkcDFtaWWPmmEmNSP" alt=""><figcaption></figcaption></figure></div>

{% hint style="info" %}

* Each notification includes a link to view the response in the Formsort dashboard (a Formsort account is required).
* Notifications are only available for responses stored in Formsort. If Formsort data storage is disabled, notifications will not be sent.
  {% endhint %}

***

### **Managing answer access with user groups and access policies**

Formsort allows you to control **who** can access answer data using [**user groups**](/workspace-management/accounts/roles-and-permissions#user-groups) and [**access policies**](/workspace-management/accounts/roles-and-permissions#access-policies).

***

### **Data security and retention**

All stored responses are **encrypted and comply with HIPAA standards**. Formsort does not delete responses, but users can manage response access and visibility through archiving.

{% hint style="info" %}
Enterprise users can disable response storage - Formsort will not store your form responses on the platform. Instead, responses are held in a secure, encrypted S3 bucket for 21 days only, after which they are permanently deleted. Use external integrations to manage your long-term data storage.
{% endhint %}


# Importing Data

See the following sub-sections:&#x20;

* [URL parameters](/importing-data/url-parameters)
* [POST body](/importing-data/post-body)
* [Embed query parameters](/importing-data/embed-query-parameters)&#x20;


# URL parameters

Passing answers directly in the URL.

Formsort forms are able to accept incoming data by passing that data in as URL query parameters. This can be leveraged to:&#x20;

* pre-fill answers to questions in your form; or, &#x20;
* pass data to an application that the user will be [redirected](/redirects-and-endings#redirects) to, using [external variables](/response-data-collection-and-management/variable-schema/external-answers).

## Setting answers

If your answer schema has a variable `first_name`, you could set a value for `first_name` by appending `?first_name=Olivia` to the URL of the flow. If you're passing multiple answers at once, separate the answers with an ampersand, for example:&#x20;

`?first_name=Olivia&last_name=Gretel`.

For data you'd like available within the flow's answers (such as `utm_source`) but don't correspond to in-form questions, you'll need to create an [external variable](/response-data-collection-and-management/variable-schema/external-answers) to capture the incoming value.&#x20;

{% hint style="info" %}
Answers passed with the wrong type, or to non-existent variable names, are ignored when loading the flow.&#x20;

Check the javascript console if you are having problems ingesting answers - a warning will be present if URL parameters don't match known answers, or the value you're providing is invalid.
{% endhint %}

### Setting date answers

Date answers should be passed in `YYYY-MM-DD` format. For example, if you want to pass in the `appointment_date` answer "Apr 25 2023", append `?appointment_date=2023-04-25` to the flow URL.

### Setting array answers

If the answer variable is of array type (for example, an answer labeled **countries** coming from a [select](/adding-questions-and-content/question-reference/select) question that allows multiple choices), you can pre-populate by creating a URL like following:

```
?countries[]=US&countries[]=CA
```

The above results in the `countries` answer being set to `["US", "CA"]`.

### Setting object answers

If an answer variable is of object type (for example, an [address](/adding-questions-and-content/question-reference/address) question's answer labelled `mailing_address`), you can set parts of that object using a URL like the following:

```
?mailing_address[postal_code]=12571&mailing_address[city]=Brooklyn
```

The above results in the `mailing_address` answer being set to `{ "postal_code": "12571", "city": "Brooklyn" }`.

### Setting a responder UUID&#x20;

{% hint style="info" %}
Setting a responder UUID is not required, since Formsort automatically generates one at the responder's first session.&#x20;
{% endhint %}

If you would like to provide your own UUID to use as an identifier, you can set the [responder UUID](/core-concepts/how-data-works-in-formsort/responder-uuids) using a URL like the following:&#x20;

`https://<FLOW-URL>?responderUuid=123e4567-e89b-12d3-a456-426614174000`.

Or, you may provide it within a [POST body](/importing-data/post-body):&#x20;

`<input name="responderUuid" value="123e4567-e89b-12d3-a456-426614174000" />`.

Manually setting `responderUuid` is useful for two reasons:&#x20;

* **You already have an identifier generated for a particular user**, like an account UUID or session UUID from your own application. Additionally, any `responderUUID` set in Formsort (manually or automatically) can be passed along with the user when being redirected out of the flow.&#x20;
* **You would like to force-load a session that is tied to a particular responder UUID**. Keep in mind, force-loading a session with the responder UUID will not work if "start each session as a new responder" is enabled in the [variant settings](/form-behavior-settings/variant-settings#start-each-session-as-a-new-responder), or if the user session has already been finalized.&#x20;

{% hint style="warning" %}
The `responderUuid` must be a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) - a 36 character hexadecimal string hyphenated with the pattern`8-4-4-4-12`, for example `123e4567-e89b-12d3-a456-426614174000`.
{% endhint %}

## Privacy

The contents of URLs are visible in the URL bars of browsers, and are often logged by third-party analytics that you might load within Formsort.

To prevent unnecessary answer exposure, we strip the URL of any properly ingested answers when your flow loads.&#x20;

For extra security, you should consider [POSTing data](/importing-data/post-body) into the flow instead.&#x20;


# POST body

Passing data through the body of a HTTP request.

In addition to pre-populating answers using [URL parameters](/importing-data/url-parameters), It's possible to `POST` form data into Formsort. This may be preferable, as answers will not appear within the URL at any point - POST bodies are part of the HTTP body and are encrypted with HTTPS.

To POST data, the user must navigate to the deployed URL of your flow with the answers as form data in the body of the form, using the answer variable name as the `name` of the input.

{% hint style="warning" %}
The POST must be a *navigation* request.

You cannot just POST form data to Formsort using AJAX and then redirect the user.
{% endhint %}

## Example: POSTing from a \<form>

Normally, when you access a page directly in a browser, or by clicking a link, the browser will send a GET request, which cannot contain a body.

On the other hand, when submitting a `<form>` in HTML, the browser will navigate with a `POST` method, which will actually redirect the user to that URL.

Here's a working example as a standalone html page, that would redirect the user to a flow with the text question (`cat_name`) set to `"Olivia"` and a Select question (`cat_color`) set to `gray`:

```html
<!DOCTYPE html>
<html>
  <body>
    <!-- TODO: Set the `action` to the URL of your deployed form -->
    <form
      method="POST"
      <!-- "/variant/VARIANT_ID/" path is optional -->
      action="https://CLIENT_ID.formsort.app/flow/FLOW_ID/variant/VARIANT_ID/..."
    >
      <input name="cat_name" value="Olivia" />
      <select name="cat_color" multiple>
        <option value="gray" selected>gray</option>
        <option value="brown" selected>brown</option>
        <option value="red" selected>red</option>
      </select>
      <input type="submit" value="Start flow" />
    </form>
  </body>
</html>
```

Of course, you could also use `<input>` attribute `type="hidden"` if the data you wish to pass should not be user editable.

In the cases where a question may be an object with nested fields, such as the [Address](/adding-questions-and-content/question-reference/address) component, or may be an array of objects, such as the [Question Group](/adding-questions-and-content/question-reference/question-group), you can use bracket notation for pathing. View two examples below.&#x20;

The Address component data shape is as follows,

```
"address": {
 "address_1": "123 William St",
 "city": "New York",
 "state": "NY",
 "postal_code": "10038",
 }
```

and can be POSTed using the following HTML format:&#x20;

```
<legend>Compound field</legend>
<input name="address[address_1]" value="123 William St" />
<input name=address[city]" value="New York" />
<input name="address[state]" value="NY" />
<input name="address[postal_code]" value="10038" />
```

Question Groups, which are an array of objects,

```
"question_group": [
      {
        "group_text": "first input"
      },
      {
        "group_text": "second input"
      }
    ]
```

can be POSTed as:&#x20;

```
<legend>Compound field:</legend>
<input name="question_group[0][group_text]" value="first input" />
<input name="question_group[1][group_text]" value="second input" /> 
```

In this Question Group example, notice that indices start from 0.&#x20;

## Example: POSTing in javascript

If you cannot have a `<form>`, or it doesn't make sense for your UX, you can create a `form` element on the fly and synthetically submit it:

1. Build up a `<form>` in your click handler
2. Add the answers:
   * For questions that accept a single answer, such as text questions, use `<input type="hidden">`. Assign the answer's *variable name* as the element's `name`.
     * For questions that accept multiple answers, use a `<select>` element, adding each answer as an `<option>`. Assign the answer's *variable name* as the `<select>` element's `name`. Make sure the `<select>` has the `multiple` property set to `true`, and each `<option>` has the `selected` property set to `true`.
3. Finally, `.submit()` the form instance.

Here's a working example as a standalone html page, that would redirect the user to a flow with `cat_name` set to `"Olivia"`:

{% code title="index.html" %}

```markup
<!doctype html>
<html>
  <head>
    <script>
      // TODO: Set these to your own values
      const clientId = 'YOUR_CLIENT';
      const flowId = 'YOUR_FLOW_LABEL';
      const variantId = 'YOUR_VARIANT_LABEL';
    
      let flow_url = "https://flow.formsort.com"
      flow_url += `/client/${clientId}`;
      flow_url += `/flow/${flowId}`;
      flow_url += `/variant/${variantId}`;
      
      const answers = {
        "cat_name": "Olivia",
        "cat_color": ["gray", "brown"]
      };
      
      const handleFormRedirect = (e) => {
        e.preventDefault();
        
        // 1. Build up a <form>
        const form = document.createElement('form');
        form.method = "POST"
        form.action = flow_url;
        
        // 2. Add the answers as <input type=hidden>
        Object.keys(answers).forEach((key) => {
          // If the answer is an array, add as <select> with <options>s
          if (Array.isArray(answers[key])) {
            const select = document.createElement("select");
            select.type === "hidden"
            select.name = key;
            select.multiple = true;

            answers[key].forEach((answer) => {
              const option = document.createElement("option");
              option.value = answer;
              option.selected = true;
              select.options.add(option);
            });
            form.appendChild(select);
          } else {
            // If the answer is a single value, add as an <input>
            const input = document.createElement("input");
            input.type = "hidden";
            input.name = key;
            input.value = answers[key];
            form.appendChild(input);
          }
        });
        document.body.appendChild(form);
        
        // 3. Submit the form instance
        form.submit();
      }
    </script>
  </head>
  <body>
    <a href="" onclick="handleFormRedirect(event)">Start flow</a>
  </body>
<html>
```

{% endcode %}

## Pre-posting data for a responder UUID

In the future, we'd like to support pre-posting data for a responder UUID, so that you can POST data asynchronously before you actually navigate the user to the flow, at which point the data will be retrieved.

{% hint style="info" %}
This is an unimplemented feature - chat us if you'd be interested in this.
{% endhint %}


# Embed query parameters

Passing data to an embed

You can also pass data into your form flows when they're configured as embeds using query parameters. Additional documentation is available in the [web embed page](https://github.com/formsort/oss/tree/master/packages/web-embed-api).

{% hint style="info" %}
**Using the embed snippet?**

If you are using the [embed snippet](/publishing-and-deployment/embedding#embed-snippet), you can enable the [Load with url parameters](/publishing-and-deployment/embedding#load-with-url-parameters) setting to have access to all URL parameters in the containing page.&#x20;
{% endhint %}

### Example: Passing in data to a React component

This example demonstrates how you can load a react component and pass in a `zipcode` of `01234`

```typescript
import React from "react";
import EmbedFlow from "@formsort/react-embed";
const EmbedFlowExample: React.FunctionComponent = () => (
  <div>
    <EmbedFlow
      clientLabel="formsort"
      flowLabel="onboarding"
      variantLabel="main"
      embedConfig={{
        style: {
          width: "100%",
          height: "100%"
        }
      }}
      queryParams={["zipcode", "01234"]}
    />
  </div>
);
```


# Built-in analytics

See performance of flow variants, as well as dropoff between steps.

Besides sending event data to your existing analytics tools, Formsort offers some built-in analytics tools to help identify performance bottlenecks and areas for improvement.

{% hint style="info" %}
Built-in analytics only measure traffic in production environments.&#x20;
{% endhint %}

## Variant performance

Accessing **Analytics** from the flow index page will show the conversion rate of the different variants within a flow.

Note that the notion of *conversion* is rather simple at this time: it is the measure of the ratio of the `FlowFinalized` to the `FlowLoaded`event, per responder UUID for a given variant, regardless of the path taken through the flow.

The default view shows this percentage.

![](/files/-MTwfubCfVZVBpqoN-q7)

Switching to the **raw** tab will show the raw numbers of users that have loaded variants within this flow:

![](/files/-MTwggYOBYlvAHHpp4OB)

The analytics are an aggregate: Completion rate is calculated on a per-responder basis. If the same responder loads the same flow twice, that is counted as a single flow load.

## Step performance

By accessing **Analytics** from *within a flow*, the performance of the steps in that flow will be shown in a table, including the step completion rate (aka the dropoff per-step).

![](/files/-MTxcHJXsi-5NeTzKIuI)


# Split testing

Use A/B testing to improve flow metrics.

## Splitting traffic from the Flow URL&#x20;

To get a quick start on traffic splitting within Formsort itself, you can assign **weights** to individual variants and direct your responders to the **flow** URL, which looks like `https://<client-id>.formsort.app/flow/<flow-name>`. This will serve a pseudo-random variant according to the weights given.&#x20;

#### Variant Weighting

<figure><img src="/files/WT1l8F0LyeIfccXQPVe8" alt=""><figcaption></figcaption></figure>

Assigning **weight** to deployed variants will distribute flow visitors according to the weight given to the variant. For instance, say a flow has two variants - variant A and variant B - and both have a weight of 50. When the flow URL is loaded, half of the responders will be sent to variant A and the other half to variant B.&#x20;

{% hint style="info" %}
Only *new* traffic is split among variants according to weight. Once the user has interacted with a specific variant, that [variant UUID](/core-concepts/versioning-in-formsort-deploying/variant-revisions#variant-revision-uuid) will be stored in their browser.&#x20;

If they revisit the form from the same browser at a later time, the cached variant will be loaded for them and weighted variants will no longer be randomly served.
{% endhint %}

## Split testing from Custom Domains

<figure><img src="/files/XcFALZEITOT9tnpzv95C" alt=""><figcaption></figcaption></figure>

Once you have your variants appropriately weighted and your [Custom Domain](/workspace-management/custom-domains) configured, you can point a flow to your domain and have random variants served to new traffic.&#x20;

In the **Domains** tab of your Studio, pick a flow from the **flow to load for this Hostname** option. Do not specify a variant.&#x20;

{% hint style="info" %}
The flow should have at least 1 deployed variant that can be served to users.&#x20;
{% endhint %}

This is the same process as using a **flow URL** to serve weighted variants, only with an extra layer of addresses. The custom domain points to the flow URL, and Formsort handles splitting incoming traffic.&#x20;

## Split testing via external tools

Another, more comprehensive pattern is to add unique **variant** URLs to an upstream testing tool such as Optimizely. These purpose-built tools are much better at handling traffic splits and can aggregate experiment data that spans across the user journey - either upstream, downstream, or inside of Formsort.

This pattern does not require weight to be added to individual variants, since traffic should be split upstream of Formsort. The only thing you'll need is the [deployed](/core-concepts/versioning-in-formsort-deploying) variants you want to use, and the URLs for them!  &#x20;

<figure><img src="/files/9g3jiHOU6wkPSeVb8Wh0" alt=""><figcaption><p>The variant URL</p></figcaption></figure>

{% hint style="info" %}
The variant URL looks the same as a flow URL, only with the path to the variant specified:

`https://<client-id>.formsort.app/flow/<flow-name>/variant/<variant-name>`
{% endhint %}

## Deprecating unused variants

In the process of testing different variants, there will come a point where you'll want to move off a particular variant entirely. The thing to be aware of is that returning responders will still get directed to the variants they have previously interacted with (due to [variant UUID caching](/core-concepts/versioning-in-formsort-deploying/variant-revisions#pinning)), so it's likely your analytics will pick up events on variants that you thought you deprecated long ago. This can be a problem!&#x20;

The solution here is to [archive](/flow-variant-management#archiving-flows) the variants you want to deprecate, and set a **redirect to URL**. This pulls old or unwanted variants out of circulation for *all* traffic, and ensures that returning visitors are pushed to the variant you want them to see.&#x20;

But wait, won't [superceding on deployment](/core-concepts/versioning-in-formsort-deploying#superceding-past-revisions-of-a-variant) push *all* users onto the most recent variant deployment?

For the most part, yes. Deploying a superceding update will:

* ensure *new* users have the most recent variant revision served and,&#x20;
* push users that have previously interacted *with the variant* onto the most recent variant revision.

The users that *will not* get pushed to the "superceded" revision are those that are returning after visiting *a different variant*.&#x20;

#### Example:

Let's say you you're ready to move on from variant A, so you:

* Push a superceding update to variant B.&#x20;
* Point all your links and custom domains to variant B.&#x20;
* Remove the weighting from variant A.

However, there are users have visited variant A already!&#x20;

The returning "A" variant users will still have variant A served when they return to finish their form, even if you've removed weigthing from variant A. This is because the UUID for the variant is still cached in the returning user's browser.&#x20;

At this point, it's time to deprecate variant A entirely:

* [Archive](/flow-variant-management#archiving-variants) variant A.&#x20;
* During archiving, set the **redirect to URL** to either variant B, [the **flow URL**](#splitting-traffic-from-the-flow-url), or your Custom Domain (which in turn may point to the flow URL).

Once A is archived, **no** users will be able to access it, and returning A variant users will be directed to variant B!&#x20;




---

[Next Page](/llms-full.txt/1)

