Skip to main content

REST API
LitePro
New in 5.16+

The headless REST API ships with the Freeform plugin. Official npm clients are published under the Solspace npm org.

Most customers should use @solspace/freeform-react or @solspace/freeform-vue and never call these endpoints by hand. This page is the contract reference when you build a custom client or debug requests.

​Enable the API

Configure this in your Craft CMS project (where Freeform is installed), not in your frontend repo.

Edit or create config/freeform.php alongside Craft’s other config files (e.g. your-craft-project/config/freeform.php). Merge a headless section (keep any existing Freeform settings):

config/freeform.php
<?php

return [
'headless' => [
'enabled' => true,
'allowedOrigins' => [
'http://localhost:3000',
'https://app.example.com',
],
'forms' => [
'contact' => [
'exposeManifest' => true,
'allowSubmit' => true,
// Optional per-form CORS override
// 'allowedOrigins' => ['https://app.example.com'],
],
],
],
];
SettingPurpose
headless.enabledMaster switch. When false, headless routes return 404.
headless.allowedOriginsGlobal CORS allow-list (supports * wildcards like https://*.example.com).
forms.{handle}.exposeManifestAllow GET manifest for that form.
forms.{handle}.allowSubmitAllow POST submit for that form.

A config example ships with Freeform at packages/plugin/config/headless.example.php.

​Endpoints

URLs below are relative to your Craft site. Official clients resolve them with your baseUrl.

​Form manifest

GET /freeform/api/forms/{handle}/manifest
Accept: application/json

Returns layout, fields, conditionals, security metadata, and submit endpoint URLs.

​Submit

POST /freeform/api/forms/{handle}/submit
Content-Type: application/json
# or multipart/form-data

JSON body shape (simplified):

{
"values": {
"email": "jane@example.com",
"message": "Hello"
},
"intent": "submit",
"context": {
"draftToken": null,
"draftKey": null,
"stateToken": null,
"sourceUrl": "https://app.example.com/contact"
},
"meta": {
"honeypot": { "name": "freeform_form_handle", "value": "" },
"javascriptTest": { "name": "freeform_form_handle", "value": "" },
"captchas": [{ "name": "g-recaptcha-response", "value": "…" }]
}
}
IntentUse
submitFinal submit (or single-page forms)
next / backMulti-page navigation
validateValidate without completing
saveDraftSave progress (token + key returned in draft)

​Scoped Duplicate CheckingProNew in 5.17+

The form's Duplicate Check and Scope by Field settings also apply to headless submissions. Include the scope field in values, for example "values": { "productId": "123", "email": "jane@example.com" }. Keep submission storage enabled; a missing scope value or a duplicate is returned as a validation error.

For cookie-based modes, load the form before the first submission and retain the visitor cookie across requests. Cross-origin clients must send credentials and use matching CORS and cookie settings. Without the same cookie, requests cannot be recognized as the same guest browser. See Scoped Duplicate Checking for configuration and limitations.

​Limit SubmissionsNew in 5.17+

An enabled builder submission cap also applies to new headless submissions in Lite and Pro. Freeform rejects submissions once the total or current calendar-period limit is reached, using the normal submission error response. Stored submissions are required; spam and deleted submissions are excluded, and edits to existing submissions do not consume another slot.

Manifest requests still return form configuration. Handle the full-form display in your frontend; Twig's automatic limit-message rendering does not run for API requests. See Limit Submissions for counting rules and periods.

​Form SchedulingNew in 5.17+

Forms using Schedule Form reject submissions before their start or after their end time, including submissions from a form loaded while it was still open. The response uses the normal validation error structure. Scheduling is available in Lite and Pro.

Manifest requests still return form configuration outside the schedule; they do not automatically hide your frontend or run Twig's message rendering. Handle the availability display in your custom frontend. See Form Scheduling for setup, messages, and timezone behavior.

​Submit context allow-list

Client-supplied context is restricted to:

KeyPurpose
draftToken / draftKeyResume or update a saved draft (both required together)
stateTokenMulti-page session token from a prior submit response
sourceUrlBrowser page URL (e.g. Mollie return / submission source tracking)
tokenSigned context token when a profile sets requiresSignedToken

Anything else (for example disable, elementId, submissionId) is stripped before Freeform applies form context. Do not rely on passing internal form settings through submit context.

On draft_saved, the response includes:

{
"status": "draft_saved",
"draft": { "token": "…", "key": "…", "resumeUrl": null },
"state": { "values": { "…": "…" }, "pageIndex": 0 }
}

Pass context.draftToken and context.draftKey on later submits to update or resume the same draft.

In React, enable Save in the form builder, then follow the simple URL recipe in React JS → Save & Continue Later (draftToken / draftKey props + session-token / key query params).

Multipart submits use a _freeform JSON field for the payload plus file inputs, matching what @solspace/freeform-core sends automatically. Standalone file fields and table file cells both use this path (table cells use nested keys under the table handle).

​CSRF token

GET /freeform/tokens
Accept: application/json

When Craft CSRF is enabled, include the token on submit:

  • JSON: header X-CSRF-Token: {value}
  • Multipart: form field named with the Craft CSRF param

Official clients fetch and attach this for you when credentials: "include" is used (default).

​File Drag & Drop

POST /freeform/api/forms/{handle}/files/{fieldHandle}
POST /freeform/api/forms/{handle}/files/{fieldHandle}/delete

Used by the file-dnd extension. Requires CSRF and the upload token header provided by the client.

​Payment Fields

POST /freeform/api/forms/{handle}/payments/stripe/checkpoint
Content-Type: application/json

Used by the payment.stripe extension before Stripe confirmation. Validates the form, verifies the PaymentIntent belongs to this form’s Stripe field, and stores an encrypted checkpoint so Freeform’s existing Stripe callback / webhook can create the submission after payment (including 3DS redirects).

Official clients call this for you. Prefer @solspace/freeform-react or @solspace/freeform-vue rather than calling the endpoint by hand.

Related Stripe routes (also used by classic Freeform) remain under /freeform/payments/stripe/… (PaymentIntent create/update, callback, webhook).

​Named profiles (advanced)

GET /freeform/api/manifests/{profile}/manifest?properties[eventId]=123
POST /freeform/api/manifests/{profile}/submit

Profiles are explicit config entries under headless.profiles — never inferred from integrations. Use them for contextual manifests that need auth, signed tokens, or typed query properties.

Submit the same allow-listed properties on POST that you used for the manifest GET (JSON body properties, or nested under multipart _freeform). Required properties missing on submit are rejected.

When a context provider is configured, the manifesto may include:

  • context.defaultValues — seeded field values
  • context.hiddenFields / context.lockedFields — handles the client should hide or treat as read-only (official packages honor these; server also re-applies provider defaults for those handles on submit)

If the profile sets requiresSignedToken: true, include a non-empty token via:

  • header X-Freeform-Context: …, or
  • context.token in the submit body (or nested under multipart _freeform), or
  • query contextToken on the manifest request

Cryptographic verification of that token is planned; until then Freeform requires the token to be present so the flag is not a silent no-op.

​Field ConfigurationImproved in 5.17+

The manifest includes field-specific settings under fields.{handle}.frontend.config. The official React and Vue renderers use these settings to provide the corresponding controls.

Field or featureConfiguration keysPurpose
Range Slidermin, max, stepSlider bounds and increment. Its starting value is provided separately as field.defaultValue.
Summaryfields, hideEmpty, emptyValue, checkedLabel, uncheckedLabel, filesLabelSource field handles, empty-answer behavior, and translated display labels.
Searchable Dropdown and Multiple SelectsearchableAn object containing enabled, placeholder, noResults, toggleLabel, resultsLabel, and removeLabel.
Text and Textarea character countsshowCharacterCount, characterCountMessagesEnables the counter and supplies translated count and limit messages. The character limit is provided as field.validation.maxLength.
Auto-growing TextareaautoGrow, autoGrowMaxHeightEnables automatic resizing and optionally limits its height in pixels.
Email suggestionssuggestEmailCorrections, emailSuggestionLabelsEnables suggestions and supplies translated message and action labels.
Password toggleshowPasswordToggle, passwordToggleLabelsEnables the visibility toggle and supplies translated show and hide labels.
International Phoneinternational, defaultCountry, allowedCountries, examples, labelsEnables international input and supplies the initial country, an array of allowed country codes, example numbers, and translated country-selector labels.
File Upload and File Upload Drag & DropuploadRequirementsTranslated requirements text, included when Show Upload Requirements is enabled.

Optional feature keys may be omitted when the feature is disabled or its setting is unset. Custom renderers must implement the behavior represented by these settings.

Browser autofill is provided separately as fields.{handle}.attributes.input.autocomplete. A custom autocomplete input attribute takes precedence over the Browser Autofill setting.

Headless forms use their own themes and renderers. Twig formatting-template styling and JavaScript Plugin overrides do not automatically apply.

The form builder's processing overlay and focus-first-error settings are not currently included in the headless manifest. Implement these behaviors in your front end when needed.

​CORS and credentials

Browser apps that call Craft on another origin must:

  1. List that origin in headless.allowedOrigins (or per-form / per-profile overrides)
  2. Send requests with credentials (credentials: "include") so CSRF cookies work

Recommended: proxy /freeform/* through your frontend (Next.js rewrites, Vite proxy, etc.) so the browser stays same-origin.

Set explicit origins. A misconfigured fallback to GraphQL’s * is unsafe for credentialed requests.

​Security metadata

The manifest includes security for honeypot, javascript test, and captcha site keys. Official clients fill meta automatically. For public forms, enable a captcha integration in Freeform — honeypot / JS test alone are soft signals for direct API callers.

​Compatibility headers

Manifests include:

{
"schemaVersion": "1.0",
"pluginVersion": "5.15.19",
"minimumClientVersion": "1.0.0"
}
  • pluginVersion matches Freeform.
  • minimumClientVersion identifies the oldest compatible npm client version. Use that version or newer.

​Next steps