---
title: How Forms cookies work
slug: cms-12/docs/how-cookie-works
docTags: 
createdAt: 2026-08-10T15:55:56.000Z
---

## Description of cookies in Forms

Like other web applications, Optimizely Forms uses cookies to implement business rules and provide visitors with a better experience. The following cookies are used in Optimizely Forms.

- `.EPiForm_BID` – This cookie is for distinguishing one browser from other browsers a visitor uses while surfing the internet. If a user visits an Optimizely site for the first time, Optimizely Forms automatically assigns a random GUID to the visitor's browser. The GUID is stored in a cookie and when it has expired, Optimizely Forms creates one the next time the user visits the website.
- `.EPiForm_VisitorIdentifier` – This cookie is used to identify who is interacting with the application. It is created by combining the value of `.EPiForm_BID` with the current user name in case the user is already logged in. A colon is used as a separator. If the user is not logged in, the value of `.EPiForm_VisitorIdentifier` is the value of `.EPiForm_BID` concatenated with a colon. The sample value of this cookie is `0f8fad5b-d9cb-469f-a165-70867728950e:admin`.
- To keep track of which forms a user is interacting with and if they are completed or uncompleted, Optimizely Forms uses a cookie name **progressive cookie**. The format of the cookie key is `EPiForm_{FormGUID}_{VisitorIdentifier}`. `FormGUID` is the primary key of a form instance. Each form instance is given a GUID as a unique identifier right after it is created, and `VisitorIdentifier` is the value of the cookie `.EPiForm_VisitorIdentifier` mentioned above. A sample progressive cookie key is
  `EPiForm_1g9kfad5z-d9bb-964f-a165-7086772895e_0f8fad5b-d9cb-469f-a165-70867728950e:admin`

The value of the progressive cookie is a JSON object consisting of the following properties:

- `FormGUID` – Already mentioned earlier.
- `SubmissionID` – The ID of the stored submission data (both finalized and unfinalized) associated with the `FormGUID`.
- `IsFinalized` – The status of the form submission. The value is **true** if the user finalizes the form by clicking the **Finalize Submit** button or finishing all steps of a form instance.  The **Submit** button form element has a property name **Finalized**. If, for example, a form has three steps and you want to let users finish the form at the second step, you can drag a submit button into the form and set the value of that property to **true**. Users can go through the three steps or finish the form at the second step.

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/ZNHlDVw8XYhKpelsKRc1s_d553307-finalize-property-of-submit-button-2.png)

## Process for handling cookies

After submitting a form or navigating to another form step, Optimizely Forms checks if the progressive cookie related to the form instance (and current `VisitorIdentifier`) exists or not:

1. **Progressive cookie exists**
   If the cookie does exist, the server retrieves the `IsFinalized` value from that cookie. If the value is **true**, Optimizely Forms gets the Form `SubmissionID` from the submitted data (through a hidden field); otherwise, the  `SubmissionID` is obtained from the progressive cookie. If the value of `IsFinalized` is true; it means that the form allows multiple submissions from the same IP address/cookie and that the user has completed submitting data at least once plus this time of submitting.
2. **Progressive cookie does not exist**
   If the progressive cookie does not exist, Optimizely Forms gets the `SubmissionID` from the submitted data. Next, Optimizely Forms checks if the `SubmissionID` is null. The submission data is saved to the storage as a record if null. If it is not null, it means that the form instance has multi-steps and the user has already interacted with that form but has not completed the whole steps. The submitted data is updated to an existing record by the `SubmissionID`.

After saving or updating submission data, the output of the process is `SubmissionID` and progressive cookie is set (creating one if no one exists or updating existing cookie). The last process is to load the form elements of the next step along with the `SubmissionID` and display on the page. The `SubmissionID`is used in a hidden field for the next navigation or submission.

Because an Optimizely Forms application may have multiple instances of a form (for example, Form\_1 and Form\_2), many progressive cookies may exist. For example, if a user visits an Optimizely site and finishes step 1 out of three steps on Form\_1, and then directly jumps to Form\_2 and starts entering information, then two progressive cookies are created. Those cookies tell the server that the user interacted with two different form instances, whether they were completed or not.

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/WUHuLUcpJhxljvAbmuJ3x-20260722-170226.jpg "episerver-forms-cookie.jpg")

### JavaScript enabled

If JavaScript is enabled, navigating between steps (previous and next step) is performed with an AJAX request, and the client uses the session storage to store submission data temporarily. Thus, if the form has multiple steps and a user fills in form data in the last step but wants to return to the previous step, the entered data is still retained. The user does not have to re-insert data, and the server does not need to hit the database to fetch data.

### JavaScript disabled

If JavaScript is disabled, navigating between steps causes the browser to get redirected to a certain page to display form steps. Optimizely has to hit the database to retrieve already entered data, and fill in form elements.

## Manage cookie expiration time

For consistency, the cookies mentioned above are set to the same expiration time.

The `IVisitorIdentifyProvider` interface is responsible for handling the form cookie expiration time of visitors. `DefaultVisitorIdentifyProvider` is the default implementation. There are a few points to note:

- The function `SetVisitorIdentifier` is used to set the `.EPiForm_VisitorIdentifier` cookie for identifying visitors.
- Currently, the expiration time unit is based on day, and the default expiration time is 90 days. You can modify this setting in the `Forms.config` file with the key `visitorSubmitTimeout`.

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/igWHBPtRwoxzLNiKkrc_B_db8c7fe-episerver-forms-visitor-timeout.png)

- The property `Order`: Optimizely Forms scans assemblies to find out which implementation of `IVisitorIdentifyProvider` interface is executed based on Ascending Order. If there are multiple implementations, the implementation with the lowest order is executed. The default order is 1000 in the `DefaultVisitorIdentifyProvider` class.
- If you want to customize the expiration time of Optimizely Forms, you need to implement the `IVisitorIdentifyProvider` interface by inheriting the `DefaultVisitorIdentifyProvider` class and overriding desired functions or creating a class and implementing functions in `IVisitorIdentifyProvider` from scratch. The recommendation is to inherit the `DefaultVisitorIdentifyProvider` class for simplicity. Example code:

```csharp
using EPiServer.Forms.Core;
using EPiServer.Forms.Core.Internal;
using EPiServer.Forms.Core.Internal.VisitorIdentify;
using EPiServer.Logging;
using EPiServer.ServiceLocation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using EPiServer.Forms.Core.Models.Internal;

namespace OptimizelySiteLM.CustomForms {
  /// <summary>
  /// Handling form cookie expiration time.
  /// </summary>
  [ServiceConfiguration(typeof (IVisitorIdentifyProvider))]
  public class CustomVisitorIdentifyProvider: DefaultVisitorIdentifyProvider {
    private static readonly object _lock = new object();
    private static readonly ILogger _logger = LogManager.GetLogger(typeof (VisitorIdentifyService));
    private
    const int _expirationTimeInMinute = 20;

    /// <summary>
    /// Optimizely Container will scan in all assemblies to find out which Implementation will get executed.
    /// If there are multi-implementations, class with lowest Order will be executed. The Order of DefaultVisitorIdentifyProvider class is 1000.
    /// So we have to set this value to 1 (as long as lower than 1000)
    /// </summary>
    public override int Order {
      get {
        return 1;
      }
    }

    /// <summary>
    /// Set form cookie. We keep everything as default in base class except modifying the expiration time.
    /// </summary>
    /// <param name="visitorIdentifier"></param>
    public override void SetVisitorIdentifier(string visitorIdentifier) {
      // TECH NOTE: When access cookies from multi threads, sometime it throw exceptions even we use lock for synchronizing.
      // So that we need surround code with try/catch to make sure the exception does not break the request.

      HttpCookie cookieBrowserID;
      HttpCookie cookieVI;

      lock(_lock) {
        try {
          cookieBrowserID = Context.Request.Cookies[CookieName_Forms_BrowserID] ?? new HttpCookie(CookieName_Forms_BrowserID);
        } catch (Exception) {
          _logger.Warning($"Cannot get cookies: {CookieName_Forms_BrowserID}");
          cookieBrowserID = new HttpCookie(CookieName_Forms_BrowserID);
        }

        try {
          cookieVI = Context.Request.Cookies[CookieName_Forms_VisitorIdentifier] ?? new HttpCookie(CookieName_Forms_VisitorIdentifier);
        } catch (Exception) {
          _logger.Warning($"Cannot get cookies: {CookieName_Forms_VisitorIdentifier}");
          cookieVI = new HttpCookie(CookieName_Forms_VisitorIdentifier);
        }
      }

      string browserID = string.Empty, newVI = string.Empty;

      if (string.IsNullOrWhiteSpace(visitorIdentifier)) {
        browserID = GenerateBrowserId();
        newVI = BuildVisitorIdentifier(browserID, GetCurrentUserID());
      } else {
        var arrSplit = visitorIdentifier.Split(new string[] {
          SEPARATOR
        }, StringSplitOptions.RemoveEmptyEntries);
        browserID = arrSplit[0];
        newVI = visitorIdentifier;
      }

      cookieBrowserID.Value = browserID;

      // The default time unit is based on day. 
      //cookieBrowserID.Expires = DateTime.Now.AddDays(_formConfig.Service.VisitorSubmitTimeout);

      // If you want to use minute as unit then use this code
      cookieBrowserID.Expires = DateTime.Now.AddMinutes(_expirationTimeInMinute); // will expire in 20 minutes

      cookieBrowserID.Path = "/";
      cookieVI.Value = newVI;
      // The default time unit is based on day. 
      //cookieVI.Expires = DateTime.Now.AddDays(_formConfig.Service.VisitorSubmitTimeout);

      // If you want to use minute as unit then use this code
      cookieVI.Expires = DateTime.Now.AddMinutes(_expirationTimeInMinute); // will expire in 20 minutes

      cookieVI.Path = "/";

      lock(_lock) {
        try {
          if (Context.Response.Cookies.Keys.OfType < string > ().Contains(cookieBrowserID.Name)) {
            Context.Response.Cookies.Set(cookieBrowserID);
          } else {
            Context.Response.Cookies.Add(cookieBrowserID);
          }
        } catch (Exception) {
          _logger.Warning($"Cannot set cookies: {cookieBrowserID.Name}");
        }

        try {
          if (Context.Response.Cookies.Keys.OfType < string > ().Contains(cookieVI.Name)) {
            Context.Response.Cookies.Set(cookieVI);
          } else {
            Context.Response.Cookies.Add(cookieVI);
          }
        } catch (Exception) {
          _logger.Warning($"Cannot set cookies: {cookieVI.Name}");
        }
      }
    }
  }
}
```
