
Optimizely Forms sets cookies, and it can be a bit difficult to understand what they do and how to control them. For me recently while doing a cookie audit for a CMS 12 project, it came up how to change the lifetime of each cookie, and the official documentation sent me down a bit of a dead end. Let me save you some time.
What cookies does Optimizely Forms set?
When a visitor lands on a page containing an Optimizely Form, two cookies are set:
EPiForm_BID- a browser ID that identifies the visitor's browser
EPiForm_VisitorIdentifier- a visitor identifier that ties together the browser ID and the current user
And after submit:
EPiForm_{formGuid}_{VisitorIdentifier}- to keep track of which forms a user is interacting with and whether they are completed or incomplete
These are used by Forms to track whether a visitor has already submitted a form, enforce submission limits, and save form progress. They're functional cookies, so depending on your cookie policy setup they may need to be declared and consented to.
By default, the expiration of these cookies is controlled by the VisitorSubmitTimeout configuration value in the Forms settings, which defaults to 90 days.
Disabling all form cookies
Optimizely gives a possibility to disable all form cookies. This is done with the DisableFormCookies-flag, as described here. With this you also have to accept that:
- The visitor has no identity from Forms' perspective
- Every submission is treated as anonymous
- Forms cannot enforce submission limits (like "only allow one submission per visitor")
- Forms cannot save progress / resume a partially filled form
- You lose all visitor tracking across form interactions
This was not really a good solution for the solution I was working with.
Making them session cookies
In my case I wanted all three to behave as session cookies, deleted when the visitor closes their browser, rather than persistent cookies with a fixed expiry. From a privacy perspective this is a cleaner outcome, since session cookies generally require less justification in cookie declarations. Having them as session cookies means that:
- The visitor still has an identity during their session
- Submission limits still work
- Form progress is still saved within the session
- The cookies just don't persist to the next visit
The cleanest solution for all three cookies is a single middleware that uses Response.OnStarting to rewrite the Set-Cookie headers before they are sent to the browser, stripping the expires and max-age attributes.
public class FormsSessionCookieMiddleware
{
private readonly RequestDelegate _next;
public FormsSessionCookieMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
context.Response.OnStarting(() =>
{
var cookieHeaders = context.Response.Headers["Set-Cookie"];
if (cookieHeaders.Count == 0) return Task.CompletedTask;
var rewritten = cookieHeaders
.Select(cookie =>
{
if (cookie != null && cookie.Contains("EPiForm_", StringComparison.OrdinalIgnoreCase))
{
var parts = cookie.Split(';')
.Select(p => p.Trim())
.Where(p => !p.StartsWith("expires=", StringComparison.OrdinalIgnoreCase)
&& !p.StartsWith("max-age=", StringComparison.OrdinalIgnoreCase));
return string.Join("; ", parts);
}
return cookie;
})
.ToArray();
context.Response.Headers["Set-Cookie"] = rewritten;
return Task.CompletedTask;
});
await _next(context);
}
}
A couple of things worth noting:
Response.OnStartingregisters a callback that fires just before the response headers are flushed to the client. If you try to modify headers afterawait _next(context)on an HTTP/2 connection, the headers have already been sent and your changes will have no effect.OnStartingsolves this correctly.- The check uses
Containsrather thanStartsWithbecause the third cookie (EPiForm_{formGuid}_{VisitorIdentifier}) doesn't start with.EPiForm_- it starts withEPiForm_without the dot prefix that the other cookies have. UsingContainscatches all variants reliably.
Then in Startup.cs or Program.cs, register the middleware early in the pipeline:
app.UseMiddleware<FormsSessionCookieMiddleware>();
That's it. All three Optimizely Forms cookies will now be session cookies, leaving no trace after the session has ended.