Skip to main content

Caching FormsImproved in 5.0+

Forms and pages can be cached in a variety of ways, but you need to ensure that your CSRF token is being refreshed in order for the form to continue working.

Here are some solutions, depending on your caching approach:

Craft Caching

When using simple Twig-based Craft Caching, you'll need to make sure that you are refreshing the CSRF token.

There are two approaches you can take:

If you're using Freeform 5.11+ and AJAX, enable Craft's asyncCsrfInputs setting and let Freeform handle CSRF auto-fetch (no custom endpoint needed).Recommended

If you're running an older version of Freeform, or have custom JavaScript submitting POST requests outside of Freeform's request layer, you'll need to implement a CSRF token refresh strategy. You can do this using either of the following approaches:

  • Craft's /actions/users/session-info endpoint (simplest to implement)
  • A custom Twig-based JSON endpoint (offers more control and a smaller payload)

Template Code

{% cache %}
{{ freeform.form("myFormHandle").render }}
{% endcache %}

Craft Config

->asyncCsrfInputs(true)

Static Caching / Blitz

When using full static page caching with services like CloudFlare, Craft Cloud or the Blitz Craft plugin, you'll need to make sure that you are refreshing the CSRF token.

There are a few different approaches you can take:

If using a single-page form and you're using Freeform 5.11+ and AJAX, enable Craft's asyncCsrfInputs setting and let Freeform handle CSRF auto-fetch (no custom endpoint needed).Recommended

If you're running an older version of Freeform, or have custom JavaScript submitting POST requests outside of Freeform's request layer, you'll need to implement a CSRF token refresh strategy. You can do this using either of the following approaches:

  • Craft's /actions/users/session-info endpoint (simplest to implement)
  • A custom Twig-based JSON endpoint (offers more control and a smaller payload)

Craft Config

->asyncCsrfInputs(true)

Stripe Payment Forms

When using the built-in Stripe integration, Freeform will trigger Payment Intent requests automatically upon page load. These requests use the initial form data, meaning the original CSRF token.

The assumption is that payment forms should never be cached, so if your form has a Stripe integration enabled and you are using Blitz caching and refreshing your CSRF tokens, we suggest injecting your payment form into your template using Blitz dynamic content or Sprig.

There is then no need to refresh CSRF tokens as the dynamic content is injected after the page loads. Otherwise, you risk a 400 Bad Request - Unable to verify your data submission error.

Craft Cloud + URL Tracking Parameters

When using static caching in Craft Cloud, you may encounter an issue where tracked URL parameters (such as UTM values like ?utm_source=google) become unintentionally cached into the page output.

Craft Cloud's edge caching may cache the fully rendered HTML of a page after Freeform has already populated tracked URL parameter values into the form.

For example:

  1. A visitor arrives via a campaign link such as: /contact?utm_source=google
  2. Freeform tracks the utm_source value and populates it into the form.
  3. Craft Cloud caches the rendered page, including that tracked value.
  4. Subsequent visitors (without any UTM parameters) may now receive the cached version of the page containing the original visitor's tracked values.

This can result in incorrect tracking data being submitted by other users.

There are a few different approaches available depending on your performance needs:

  1. Prevent the Form Page from Being Cached
  2. Use Edge-Side Includes (Recommended)
  3. Ignore Tracking Parameters in the Cache Key

Option 1: Prevent the Form Page from Being Cached

The simplest solution is to prevent Craft Cloud from statically caching the page that contains your form.

You can do this by adding the following Twig tag anywhere in the template that renders your Freeform form:

{% expires %}

This ensures the page is always rendered dynamically and prevents tracked URL parameter values from being cached into the HTML.

Craft Cloud supports Edge-Side Includes (ESI), which allow you to keep the main page cached while rendering specific portions dynamically.

This provides the best balance of performance and accuracy by keeping your form output uncached while the rest of the page remains statically cached.

1

Update Your Main Template

Replace your form include with an ESI call:

{{ cloud.esi('contact-form.twig') }}
2

Create a Dedicated Form Template

Move your Freeform form rendering into a separate template (e.g. contact-form.twig) and add the following:

{% expires %}
{{ freeform.form('contact').render() }}

This ensures that only the form is dynamically rendered at request time, preventing tracked URL parameters from being cached and reused across visitors.

Option 3: Ignore Tracking Parameters in the Cache Key

In addition to rendering your form dynamically, you may also wish to configure Craft Cloud to ignore common marketing query parameters (such as UTM values) when generating the cache key.

Without this, crawlers or campaign visitors may unintentionally generate cached versions of your page that include tracking parameters, even if they never submit the form.

By configuring Craft Cloud to ignore parameters such as:

utm_source
utm_medium
utm_campaign
utm_term
utm_content
gclid
fbclid

You can help prevent multiple cached variants of the same page from being created based on marketing or referral data. This reduces the risk of cached tracking values being embedded into statically cached HTML and served to other visitors. Refer to the Craft Cloud documentation for details on configuring ignored query parameters for caching.