diff --git a/craft-freeform/headless/getting-started.mdx b/craft-freeform/headless/getting-started.mdx
new file mode 100644
index 00000000..28a1d406
--- /dev/null
+++ b/craft-freeform/headless/getting-started.mdx
@@ -0,0 +1,167 @@
+---
+title: Getting Started
+sidebar_position: 1
+description: Install and enable Freeform's official headless React packages for Craft CMS.
+---
+
+import { Badge, BadgeGroup } from '@site/src/components/utils';
+import { VerticalStepWrapper, StepMarkdown } from '@site/src/components/docs';
+
+# Getting Started (React Headless)
+
+Freeform’s recommended headless path for React and Next.js is:
+
+1. Use a Freeform plugin build that includes the **headless REST API**
+2. Enable headless in Freeform config
+3. Install the official npm packages
+4. Render forms with `` or build your own UI with `useFreeform()`
+
+GraphQL and older AJAX demos remain available, but new React/Next.js projects should start here.
+
+:::tip Example app
+Clone the [Freeform Headless React Demo](https://github.com/solspace/freeform-headless-react-demo) to try the packages against your Craft site (REST, GraphQL tab, Save & Continue, table, signature, calculation, and more).
+:::
+
+## Requirements
+
+| Piece | Version / notes |
+| --- | --- |
+| Craft CMS | 4.17+ or 5.9+ |
+| Freeform plugin | Build that includes headless REST |
+| npm packages | Official `@solspace/freeform-*` packages ([npm org](https://www.npmjs.com/org/solspace)) |
+| React | 18 or 19 |
+| Node | Modern LTS with `npm` / `pnpm` / `yarn` |
+
+## Quick setup
+
+
+
+
+Headless is **off by default**. Configure it in your **Craft CMS project** (the site where Freeform is installed) — not in your React / Next.js app repo.
+
+Edit or create:
+
+```text
+your-craft-project/config/freeform.php
+```
+
+That file lives next to Craft’s other config files (`general.php`, `routes.php`, etc.), for example:
+
+```text
+~/Sites/my-craft-site/config/freeform.php
+```
+
+Merge a `headless` section into that file (keep any existing Freeform settings):
+
+```php title="config/freeform.php" showLineNumbers
+ [
+ // Master switch — endpoints return 404 when false
+ 'enabled' => true,
+
+ // CORS origins for browser apps that call Craft directly
+ 'allowedOrigins' => [
+ 'http://localhost:3000',
+ 'https://app.example.com',
+ ],
+
+ // Per-form exposure (keyed by form handle from the Freeform CP)
+ 'forms' => [
+ 'contact' => [
+ 'exposeManifest' => true,
+ 'allowSubmit' => true,
+ ],
+ ],
+ ],
+];
+```
+
+:::tip Same-origin proxy
+If your frontend proxies `/freeform/*` to Craft (recommended for Next.js), CSRF cookies stay same-origin and you can keep `allowedOrigins` tight.
+:::
+
+See the full [REST API](./rest-api.mdx) reference for profiles, CSRF, and submit details.
+
+
+
+
+```bash
+npm install @solspace/freeform-core \
+ @solspace/freeform-react \
+ @solspace/freeform-extensions \
+ @solspace/freeform-react-theme-default
+```
+
+| Package | Purpose |
+| --- | --- |
+| `@solspace/freeform-core` | Manifest client, form state, conditionals, submit |
+| `@solspace/freeform-react` | `` component and `useFreeform()` hook |
+| `@solspace/freeform-extensions` | Captchas, datetime, file drag & drop, calculation, table, signature |
+| `@solspace/freeform-react-theme-default` | Default light/dark theme CSS |
+
+
+
+
+```tsx title="ContactForm.tsx" showLineNumbers
+import { Freeform } from '@solspace/freeform-react';
+import { recommendedExtensions } from '@solspace/freeform-extensions';
+import '@solspace/freeform-react-theme-default/styles.css';
+
+export function ContactForm() {
+ return (
+ {
+ console.log('Submitted', response);
+ }}
+ />
+ );
+}
+```
+
+- **`handle`** — Freeform form handle (must be enabled under `headless.forms`)
+- **`baseUrl`** — Craft site origin, **or** `""` / same origin when you proxy `/freeform`
+- **`extensions`** — register captchas and advanced fields when the form needs them
+
+
+
+
+## Choose your guide
+
+| Guide | When to use it |
+| --- | --- |
+| [React JS](./reactjs.mdx) | Vite, CRA, Remix, or any React SPA |
+| [Next.js](./nextjs.mdx) | App Router / Pages Router + proxy |
+| [REST API](./rest-api.mdx) | Endpoints, CSRF, CORS, profiles |
+| [GraphQL](./graphql.mdx) | Headless adapters (`freeformHeadlessManifest` / `freeformHeadlessSubmit`) or legacy form GraphQL |
+| [Example demo](https://github.com/solspace/freeform-headless-react-demo) | Cloneable Vite app (REST + GraphQL tab) |
+
+## What’s included
+
+- Manifest load + CSRF + submit (JSON and multipart)
+- Multi-page forms and conditionals (client UX)
+- Captchas via `@solspace/freeform-extensions`
+- File upload and File Drag & Drop
+- **Save & Continue Later** (draft token + key; see [React JS](./reactjs.mdx#save--continue-later))
+- Calculation fields
+- Table fields (row limits, required columns, file cells)
+- Signature fields (canvas pad + clear)
+- Default theme with light / dark / system color schemes
+- Headless GraphQL adapters (`freeformHeadlessManifest` / `freeformHeadlessSubmit`)
+
+## Coming later
+
+- Payment fields (Stripe, etc.)
+- Official Vue adapter
+- Bootstrap and Tailwind theme packages
+## Security checklist
+
+1. Keep headless **disabled** until you intentionally enable forms.
+2. Set explicit `headless.allowedOrigins` for cross-origin apps.
+3. Enable a **captcha** on public forms (do not rely on honeypot alone for API callers).
+4. Leave **`allowRawHtml` off** unless HTML / rich-text field content is trusted CMS content.
+5. Do not treat client-side conditional hiding as a server access-control boundary yet.
diff --git a/craft-freeform/headless/graphql.mdx b/craft-freeform/headless/graphql.mdx
index ffd6295f..5a5964db 100644
--- a/craft-freeform/headless/graphql.mdx
+++ b/craft-freeform/headless/graphql.mdx
@@ -1,7 +1,7 @@
---
title: GraphQL
-sidebar_position: 1
-description: Integrate Freeform 5.x with GraphQL in Craft CMS for headless form handling and submissions.
+sidebar_position: 6
+description: Freeform headless GraphQL adapters plus legacy form queries and submission mutations for Craft CMS.
---
import Tabs from '@theme/Tabs';
@@ -12,13 +12,86 @@ import { VerticalStepWrapper, StepMarkdown } from '@site/src/components/docs';
# GraphQL
-Freeform supports querying form layouts and form submission mutations via GraphQL. This guide assumes you have some GraphQL experience. To learn more about GraphQL and Craft, please check out the [Fetch content with GraphQL](https://craftcms.com/docs/getting-started-tutorial/build/graphql.html) guide and [Craft's GraphQL API](https://craftcms.com/docs/5.x/development/graphql.html).
+Freeform supports GraphQL in two ways:
+
+1. **Headless adapters** (recommended when you want GraphQL) — `freeformHeadlessManifest` and `freeformHeadlessSubmit`, matching the [REST API](./rest-api.mdx) contract
+2. **Legacy form queries / `save_{handle}_Submission` mutations** — still supported for existing integrations (documented below)
+
+This guide assumes some GraphQL experience. See Craft’s [Fetch content with GraphQL](https://craftcms.com/docs/getting-started-tutorial/build/graphql.html) and [GraphQL API](https://craftcms.com/docs/5.x/development/graphql.html).
+
+:::tip New React / Next.js projects
+Prefer the official packages with the [REST API](./rest-api.mdx) for `` / `useFreeform()` — start with [Getting Started](./getting-started.mdx). Use the headless GraphQL adapters when your app already speaks Craft GraphQL and you want the same manifest/submit payload shape.
+:::
+
+## Headless GraphQL adapters
+
+These adapters reuse Freeform’s headless `ManifestService` and `HeadlessSubmitService`. Enable headless for the form first (same `config/freeform.php` as REST).
+
+### Requirements
+
+| Requirement | Notes |
+| --- | --- |
+| Headless config | `headless.enabled` + per-form `exposeManifest` / `allowSubmit` |
+| Craft GraphQL schema | Freeform **form read** + **submission create** for that form |
+| Site access | Enable your Craft **site** on the schema (missing this returns *Schema doesn’t have access to the “Site” site*) |
+| Schema token | Pass as `Authorization: Bearer ` |
+
+Typical endpoint: `POST /actions/graphql/api` (or your Craft GraphQL URL).
+
+### Manifest query
+
+Returns the same manifest object as REST `GET /freeform/api/forms/{handle}/manifest` (the `data` payload).
+
+```graphql showLineNumbers
+query {
+ freeformHeadlessManifest(handle: "contact")
+}
+```
+
+### Submit mutation
+
+Returns the same structured submit response as REST (`success`, `status`, `errors`, `draft`, etc.).
+
+```graphql showLineNumbers
+mutation {
+ freeformHeadlessSubmit(
+ handle: "contact"
+ intent: "submit"
+ values: { email: "jane@example.com", message: "Hello" }
+ meta: {}
+ context: {}
+ )
+}
+```
+
+| Argument | Description |
+| --- | --- |
+| `handle` | Form handle (required) |
+| `intent` | `submit`, `next`, `back`, `validate`, or `saveDraft` (default `submit`) |
+| `values` | Field values by handle (`FreeformJson`) |
+| `meta` | Honeypot, captcha, javascriptTest, etc. |
+| `context` | Draft tokens and other submit context |
+| `csrfToken` | Optional; when omitted, cookie CSRF is skipped (schema token is the gate) |
+
+### File uploads
+
+Multipart / File Drag & Drop uploads remain on the **REST** endpoints. Use GraphQL for JSON values; upload files via REST (or the official React packages on REST).
+
+### Try it
+
+The [Freeform Headless React Demo](https://github.com/solspace/freeform-headless-react-demo) includes a **GraphQL** tab. Set `VITE_GRAPHQL_TOKEN` (and optional `VITE_GRAPHQL_PATH`) in `.env`.
+
+---
+## Legacy GraphQL (form queries and submission mutations)
+
+The sections below document Freeform’s original GraphQL form layout queries and per-form `save_{handle}_Submission` mutations.
+
## Demos
Have a look at our headless demos to get a feel for what's possible with GraphQL:
diff --git a/craft-freeform/headless/index.mdx b/craft-freeform/headless/index.mdx
index fc5170e8..ff9377a5 100644
--- a/craft-freeform/headless/index.mdx
+++ b/craft-freeform/headless/index.mdx
@@ -4,34 +4,55 @@ import Icons from '@site/static/icons/cards';
# Headless
-Freeform supports headless website architecture making it easy to use JavaScript-based front-end frameworks such as Vue.js, Next.js, React JS and more! Freeform also supports querying form layouts and using mutations to create submissions via GraphQL.
+Freeform supports headless website architecture for JavaScript front ends such as React, Next.js, and Vue.js.
+
+**Recommended for new React / Next.js projects:** the official npm packages and the headless REST API. Prefer [headless GraphQL adapters](./graphql#headless-graphql-adapters) when your app already uses Craft GraphQL.
+
+Start with [Getting Started](./getting-started) to enable headless and install `@solspace/freeform-react`, or try the [example React demo](https://github.com/solspace/freeform-headless-react-demo).
or build your own UI with useFreeform().',
icon: Icons.ReactIcon,
fullCardLink: 'reactjs',
},
{
title: 'Next.js',
- description: 'Full support for implementation Freeform with Next.js.',
+ description:
+ 'App Router Client Components, rewrites proxy, and CSRF-friendly setup.',
icon: Icons.NextjsIcon,
fullCardLink: 'nextjs',
filterIcon: true,
},
+ {
+ title: 'Vue.js',
+ description:
+ 'Headless demos for Vue.js (official Vue package coming later).',
+ icon: Icons.VuejsIcon,
+ fullCardLink: 'vuejs',
+ },
{
title: 'GraphQL',
description:
- 'With Freeform, you can easily query form layouts and submit form mutations using GraphQL.',
+ 'Headless manifest/submit adapters, plus legacy form queries and mutations.',
icon: Icons.GraphqlIcon,
fullCardLink: 'graphql',
},
diff --git a/craft-freeform/headless/nextjs.mdx b/craft-freeform/headless/nextjs.mdx
index 4fb897b5..11adf1bd 100644
--- a/craft-freeform/headless/nextjs.mdx
+++ b/craft-freeform/headless/nextjs.mdx
@@ -1,33 +1,166 @@
---
title: Next.js
sidebar_position: 4
-description: Build headless forms with Freeform 5.x and Next.js in Craft CMS.
+description: Build headless Freeform forms with Next.js and the official React packages for Craft CMS.
---
import { Badge, BadgeGroup } from '@site/src/components/utils';
+import { VerticalStepWrapper, StepMarkdown } from '@site/src/components/docs';
-# Next.js Implementations
+# Next.js
-:::warning
-Please see the [How to Render a Form](./graphql.mdx#how-to-render-a-form) guide for detailed steps for being able to render your own form in the front-end using GraphQL data and Next.js.
-:::
+Use the official `@solspace/freeform-react` packages. Complete [Getting Started](./getting-started.mdx) (enable headless + install packages) first.
-## GraphQL Demo
+Freeform’s React packages are client-side. In the App Router, render them from a **Client Component** and proxy `/freeform` so CSRF cookies stay on your Next.js origin.
-The below demo link shows how Freeform is implemented with [Next.js](https://react.dev/) and [GraphQL](./graphql.mdx).
+## Install
-:::info
-Please note that the paths in the demos are specific for the Solspace demo site and server. Please make sure your code uses paths that match your server setup.
-:::
+```bash
+npm install @solspace/freeform-core \
+ @solspace/freeform-react \
+ @solspace/freeform-extensions \
+ @solspace/freeform-react-theme-default
+```
-https://github.com/solspace/craft-freeform-demo-nextjs-graphql
+## Setup
-## AJAX Demo
+
+
-The below demo link shows how Freeform is implemented with [Next.js](https://react.dev/) and AJAX.
+```ts title="next.config.ts" showLineNumbers
+import type { NextConfig } from 'next';
-:::info
-Please note that the paths in the demos are specific for the Solspace demo site and server. Please make sure your code uses paths that match your server setup.
-:::
+const nextConfig: NextConfig = {
+ async rewrites() {
+ return [
+ {
+ source: '/freeform/:path*',
+ destination: `${process.env.CRAFT_URL}/freeform/:path*`,
+ },
+ ];
+ },
+};
-https://github.com/solspace/craft-freeform-demo-nextjs-ajax
+export default nextConfig;
+```
+
+Set `CRAFT_URL` to your Craft site (for example `https://cms.example.com`). Requests from the browser hit `/freeform/...` on the Next.js origin; Next.js forwards them to Craft.
+
+
+
+
+```tsx title="app/contact/contact-form.tsx" showLineNumbers
+'use client';
+
+import { Freeform } from '@solspace/freeform-react';
+import { recommendedExtensions } from '@solspace/freeform-extensions';
+import '@solspace/freeform-react-theme-default/styles.css';
+
+export function ContactForm() {
+ return (
+ {
+ console.log(response);
+ }}
+ />
+ );
+}
+```
+
+```tsx title="app/contact/page.tsx" showLineNumbers
+import { ContactForm } from './contact-form';
+
+export default function ContactPage() {
+ return (
+
+ Contact
+
+
+ );
+}
+```
+
+
+
+
+In `config/freeform.php` on Craft:
+
+```php
+'headless' => [
+ 'enabled' => true,
+ 'forms' => [
+ 'contact' => [
+ 'exposeManifest' => true,
+ 'allowSubmit' => true,
+ ],
+ ],
+],
+```
+
+With the same-origin proxy you usually do **not** need to add the Next.js origin to `allowedOrigins`. Add it only if the browser calls Craft directly.
+
+
+
+
+## Environment variables
+
+| Variable | Where | Example |
+| --- | --- | --- |
+| `CRAFT_URL` | Next.js server (rewrites) | `https://cms.example.com` |
+| `NEXT_PUBLIC_CRAFT_URL` | Optional public Craft URL | Only if you skip the proxy |
+
+Prefer the proxy + `baseUrl=""` pattern over exposing Craft’s origin to the browser.
+
+## Cross-origin (no proxy)
+
+If the browser must call Craft directly:
+
+1. Set `baseUrl={process.env.NEXT_PUBLIC_CRAFT_URL}`
+2. Add your Next.js origin to `headless.allowedOrigins`
+3. Keep credentials enabled (default in the Freeform client)
+
+```tsx
+
+```
+
+## Headless markup
+
+`useFreeform()` works the same as in a Vite React app — still inside a Client Component:
+
+```tsx
+'use client';
+
+import { useFreeform } from '@solspace/freeform-react';
+
+export function HeadlessContactForm() {
+ const form = useFreeform({
+ handle: 'contact',
+ baseUrl: '',
+ });
+
+ // …build your own fields with form.getFieldProps(), etc.
+}
+```
+
+See [React JS](./reactjs.mdx) for themes, captchas, custom renderers, and the full hook API.
+
+## File uploads
+
+Official packages handle multipart submits and File Drag & Drop via `@solspace/freeform-extensions`. You do not need the older GraphQL Base64 upload guides for new projects.
+
+## Legacy demos
+
+Older Next.js GraphQL / AJAX demos:
+
+- [Next.js + GraphQL](https://github.com/solspace/craft-freeform-demo-nextjs-graphql)
+- [Next.js + AJAX](https://github.com/solspace/craft-freeform-demo-nextjs-ajax)
+
+Recommended path for new work: this page + [Getting Started](./getting-started.mdx).
diff --git a/craft-freeform/headless/reactjs.mdx b/craft-freeform/headless/reactjs.mdx
index d8cd8dbf..70a9e9a4 100644
--- a/craft-freeform/headless/reactjs.mdx
+++ b/craft-freeform/headless/reactjs.mdx
@@ -1,33 +1,310 @@
---
-title: React.js
+title: React JS
sidebar_position: 3
-description: Build headless forms with Freeform 5.x and React.js in Craft CMS.
+description: Build headless Freeform forms with the official React packages for Craft CMS.
---
import { Badge, BadgeGroup } from '@site/src/components/utils';
-# React JS Implementations
+# React JS
-:::warning
-Please see the [How to Render a Form](./graphql.mdx#how-to-render-a-form) guide for detailed steps for being able to render your own form in the front-end using GraphQL data and React JS.
-:::
+Use the official `@solspace/freeform-react` packages. See [Getting Started](./getting-started.mdx) for enabling the headless API.
-## GraphQL Demo
+## Install
-The below demo link shows how Freeform is implemented with [React JS](https://react.dev/) and [GraphQL](./graphql.mdx).
+```bash
+npm install @solspace/freeform-core \
+ @solspace/freeform-react \
+ @solspace/freeform-extensions \
+ @solspace/freeform-react-theme-default
+```
-:::info
-Please note that the paths in the demos are specific for the Solspace demo site and server. Please make sure your code uses paths that match your server setup.
-:::
+Import the default theme CSS once in your app entry:
+
+```tsx
+import '@solspace/freeform-react-theme-default/styles.css';
+```
+
+## Render with ``
+
+The easiest path — Freeform loads the manifest, manages state, renders fields, handles CSRF, and submits:
+
+```tsx title="ContactForm.tsx" showLineNumbers
+import { Freeform } from '@solspace/freeform-react';
+import { recommendedExtensions } from '@solspace/freeform-extensions';
+import '@solspace/freeform-react-theme-default/styles.css';
+
+export function ContactForm() {
+ return (
+ {
+ // response.success, response.submission, etc.
+ }}
+ onError={(response) => {
+ // Field / form errors from Freeform
+ }}
+ />
+ );
+}
+```
+
+| Prop | Description |
+| --- | --- |
+| `handle` | Form handle (must be allowed in `headless.forms`) |
+| `baseUrl` | Craft origin. Use `""` or your SPA origin when `/freeform` is proxied. |
+| `extensions` | Captcha / datetime / file-dnd / calculation / table / signature |
+| `draftToken` / `draftKey` | Resume a saved draft (from a prior `saveDraft` response) |
+| `theme` | Optional theme object (`lightTheme`, `darkTheme`, or `createTheme()`) |
+| `allowRawHtml` | Default `false`. Set `true` only if HTML/rich-text fields are trusted CMS content. |
+| `loadingFallback` / `errorFallback` | Custom loading and error UI |
+
+### Light / dark theme
+
+```tsx
+import { Freeform } from '@solspace/freeform-react';
+import {
+ darkTheme,
+ lightTheme,
+} from '@solspace/freeform-react-theme-default';
+import '@solspace/freeform-react-theme-default/styles.css';
+
+;
+// or theme={lightTheme}
+```
+
+By default the theme follows the visitor’s OS preference (`system`).
+
+## Headless control with `useFreeform()`
+
+Own the markup while Freeform still loads the form, tracks values, evaluates conditionals, and submits:
+
+```tsx title="HeadlessContactForm.tsx" showLineNumbers
+import { useFreeform } from '@solspace/freeform-react';
+import { recommendedExtensions } from '@solspace/freeform-extensions';
+
+export function HeadlessContactForm() {
+ const form = useFreeform({
+ handle: 'contact',
+ baseUrl: 'https://cms.example.com',
+ extensions: recommendedExtensions,
+ });
+
+ if (form.loading) {
+ return Loading…
;
+ }
+
+ if (form.error) {
+ return {form.error.message}
;
+ }
+
+ return (
+
+ );
+}
+```
+
+Useful helpers on the hook result:
+
+| API | Purpose |
+| --- | --- |
+| `getFieldProps(handle)` | `name`, `id`, `onChange`, `onBlur`, etc. |
+| `values` / `setValue` | Read / write field values |
+| `fieldErrors` / `formErrors` | Validation messages |
+| `isFieldVisible(handle)` | Conditional visibility |
+| `handleSubmit` / `goNext` / `goBack` / `saveDraft` | Submit, multi-page, and save progress |
+
+## Save & Continue Later
+
+Enable **Save** on the form’s Button Layout in Freeform. React shows the Save button automatically.
+
+After Save, Freeform returns a `token` and `key`. Put those in your page URL so the visitor can come back later (email the link, bookmark it, or copy it):
+
+```tsx title="ContactFormWithSave.tsx" showLineNumbers
+import { Freeform } from '@solspace/freeform-react';
+import { recommendedExtensions } from '@solspace/freeform-extensions';
+
+function readDraft() {
+ const params = new URLSearchParams(window.location.search);
+ return {
+ draftToken: params.get('session-token'),
+ draftKey: params.get('key'),
+ };
+}
+
+function writeDraft(token: string, key: string) {
+ const url = new URL(window.location.href);
+ url.searchParams.set('session-token', token);
+ url.searchParams.set('key', key);
+ window.history.replaceState({}, '', url);
+}
+
+export function ContactFormWithSave() {
+ const { draftToken, draftKey } = readDraft();
+
+ return (
+ {
+ if (
+ response.status === 'draft_saved' &&
+ response.draft?.token &&
+ response.draft?.key
+ ) {
+ writeDraft(response.draft.token, response.draft.key);
+ // Optional: show “Progress saved — bookmark this page”
+ }
+ }}
+ />
+ );
+}
+```
+
+**Customer flow**
+
+1. Enable Save in Freeform → set Save label (Redirect URL optional).
+2. Visitor clicks **Save** → your app stores `token` + `key` in the URL (as above).
+3. Visitor returns via that URL → pass `draftToken` / `draftKey` → fields restore.
+4. Visitor clicks **Submit** → real submission; draft is removed.
+
+Optional: set the form’s Save **Redirect URL** to
+`https://your-app.com/contact?session-token={token}&key={key}`
+so the API also returns `draft.resumeUrl` for emails or custom UI.
+
+## Captchas and advanced fields
+
+Pass `recommendedExtensions` (or a subset) so Freeform can mount captchas and advanced fields:
+
+```tsx
+import {
+ captchaExtensions,
+ datetimeExtension,
+ fileDndExtension,
+ calculationExtension,
+ tableExtension,
+ signatureExtension,
+ recommendedExtensions,
+} from '@solspace/freeform-extensions';
+
+// Recommended preset (captchas + datetime + file DnD + calculation + table + signature):
+
+
+// Or pick what you need:
+
+```
+
+| Extension | Covers |
+| --- | --- |
+| Captchas | Turnstile, reCAPTCHA, hCaptcha, Friendly Captcha |
+| `datetimeExtension` | Flatpickr / native datetime fields |
+| `fileDndExtension` | File Drag & Drop uploads |
+| `calculationExtension` | Live calculation fields |
+| `tableExtension` | Table rows (min/max/exact limits, required columns, file cells) |
+| `signatureExtension` | Signature pad (draw, clear, required validation) |
+
+Configure captcha integrations in the Freeform control panel. Site keys are exposed on the form manifest; tokens are submitted automatically through `meta.captchas`.
+
+For **table** fields: enable the field in Freeform as usual. Row limits and required columns come from the form builder. File columns upload through the same headless multipart path as standalone file fields.
+
+For **signature** fields: visitors draw on the canvas; the value is stored as a PNG data URL. Clear resets the pad.
+
+## Custom field renderers
+
+Override rendering by handle, frontend renderer key, or field type:
+
+```tsx
+
+```
+
+## Same-origin proxy (recommended)
+
+Point your Vite (or other) dev server at Craft so CSRF cookies stay same-origin:
+
+```ts title="vite.config.ts"
+export default {
+ server: {
+ proxy: {
+ '/freeform': {
+ target: 'https://cms.example.com',
+ changeOrigin: true,
+ secure: false, // local TLS only
+ },
+ },
+ },
+};
+```
+
+Then use `baseUrl=""` (or `window.location.origin`) in the browser.
+
+For Next.js, see the [Next.js](./nextjs.mdx) guide.
+
+## Security notes
+
+- Leave **`allowRawHtml`** at the default (`false`) unless HTML fields are trusted.
+- Enable a **captcha** on public forms.
+- Set **`headless.allowedOrigins`** when calling Craft cross-origin.
+- See [REST API](./rest-api.mdx) for CORS and CSRF details.
+
+## Example demo
+
+- [Freeform Headless React Demo](https://github.com/solspace/freeform-headless-react-demo) — Vite app using the official npm packages (REST tabs + **GraphQL** tab)
+
+## GraphQL
-https://github.com/solspace/craft-freeform-demo-reactjs-graphql
+Prefer the [headless GraphQL adapters](./graphql.mdx#headless-graphql-adapters) when you want Craft GraphQL with the same manifest/submit contract as REST. Legacy form queries and `save_{handle}_Submission` mutations are documented on the same page.
-## AJAX Demo
+## Legacy GraphQL / AJAX demos
-The below demo link shows how Freeform is implemented with [React JS](https://react.dev/) and AJAX.
+Older demos that query Freeform via GraphQL or custom AJAX still work, but are **not** the recommended path for new projects:
:::info
Please note that the paths in the demos are specific for the Solspace demo site and server. Please make sure your code uses paths that match your server setup.
:::
-https://github.com/solspace/craft-freeform-demo-reactjs-ajax
+- [React + GraphQL demo](https://github.com/solspace/craft-freeform-demo-reactjs-graphql)
+- [React + AJAX demo](https://github.com/solspace/craft-freeform-demo-reactjs-ajax)
\ No newline at end of file
diff --git a/craft-freeform/headless/rest-api.mdx b/craft-freeform/headless/rest-api.mdx
new file mode 100644
index 00000000..a5fec589
--- /dev/null
+++ b/craft-freeform/headless/rest-api.mdx
@@ -0,0 +1,181 @@
+---
+title: REST API
+sidebar_position: 2
+description: Freeform headless REST API for manifests, submissions, CSRF, CORS, and file uploads in Craft CMS.
+---
+
+import { Badge, BadgeGroup } from '@site/src/components/utils';
+
+# REST API
+
+The headless REST API ships with the Freeform plugin. Official npm clients are published under the [Solspace npm org](https://www.npmjs.com/org/solspace).
+
+Most customers should use [`@solspace/freeform-react`](./reactjs.mdx) 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):
+
+```php title="config/freeform.php" showLineNumbers
+ [
+ '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'],
+ ],
+ ],
+ ],
+];
+```
+
+| Setting | Purpose |
+| --- | --- |
+| `headless.enabled` | Master switch. When `false`, headless routes return 404. |
+| `headless.allowedOrigins` | Global CORS allow-list (supports `*` wildcards like `https://*.example.com`). |
+| `forms.{handle}.exposeManifest` | Allow `GET` manifest for that form. |
+| `forms.{handle}.allowSubmit` | Allow `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
+
+```http
+GET /freeform/api/forms/{handle}/manifest
+Accept: application/json
+```
+
+Returns layout, fields, conditionals, security metadata, and submit endpoint URLs.
+
+### Submit
+
+```http
+POST /freeform/api/forms/{handle}/submit
+Content-Type: application/json
+# or multipart/form-data
+```
+
+JSON body shape (simplified):
+
+```json
+{
+ "values": {
+ "email": "jane@example.com",
+ "message": "Hello"
+ },
+ "intent": "submit",
+ "meta": {
+ "honeypot": { "name": "freeform_form_handle", "value": "" },
+ "javascriptTest": { "name": "freeform_form_handle", "value": "" },
+ "captchas": [{ "name": "g-recaptcha-response", "value": "…" }]
+ }
+}
+```
+
+| Intent | Use |
+| --- | --- |
+| `submit` | Final submit (or single-page forms) |
+| `next` / `back` | Multi-page navigation |
+| `validate` | Validate without completing |
+| `saveDraft` | Save progress (token + key returned in `draft`) |
+
+On `draft_saved`, the response includes:
+
+```json
+{
+ "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](./reactjs.mdx#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
+
+```http
+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
+
+```http
+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.
+
+### Named profiles (advanced)
+
+```http
+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.
+
+## 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.
+
+:::warning Do not use a wildcard CORS origin in production
+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:
+
+```json
+{
+ "schemaVersion": "1.0",
+ "pluginVersion": "5.15.19",
+ "minimumClientVersion": "0.1.0"
+}
+```
+
+`pluginVersion` matches Freeform. `minimumClientVersion` is the oldest npm client line the plugin expects to work with.
+
+## Next steps
+
+- [Getting Started](./getting-started.mdx) — install packages
+- [React JS](./reactjs.mdx) — `` and `useFreeform()`
+- [Next.js](./nextjs.mdx) — App Router + proxy
+- [GraphQL](./graphql.mdx#headless-graphql-adapters) — headless manifest/submit via Craft GraphQL
diff --git a/craft-freeform/headless/vuejs.mdx b/craft-freeform/headless/vuejs.mdx
index 53b1472b..27116676 100644
--- a/craft-freeform/headless/vuejs.mdx
+++ b/craft-freeform/headless/vuejs.mdx
@@ -1,6 +1,6 @@
---
title: Vue.js
-sidebar_position: 2
+sidebar_position: 5
description: Build headless forms with Freeform 5.x and Vue.js in Craft CMS.
---
@@ -8,6 +8,10 @@ import { Badge, BadgeGroup } from '@site/src/components/utils';
# Vue.js Implementations
+:::info Official Vue package — coming later
+Freeform’s first-class headless packages currently target **React / Next.js**. An official `@solspace/freeform-vue` adapter is planned. Until then, use GraphQL or custom AJAX as shown below, or call the [REST API](./rest-api.mdx) from your own Vue client.
+:::
+
:::warning
Please see the [How to Render a Form](./graphql.mdx#how-to-render-a-form) guide for detailed steps for being able to render your own form in the front-end using GraphQL data and Vue.js.
:::
diff --git a/craft-freeform/intro.mdx b/craft-freeform/intro.mdx
index 7dc50f20..6afd6a27 100644
--- a/craft-freeform/intro.mdx
+++ b/craft-freeform/intro.mdx
@@ -1377,7 +1377,7 @@ import Icons from '@site/static/icons/cards';
or useFreeform().',
icon: Icons.ReactIcon,
linkWithDescription: 'headless/reactjs',
},
{
title: 'Next.js',
- description: 'Full support for implementation Freeform with Next.js.',
+ description: 'App Router Client Components and CSRF-friendly proxy setup.',
icon: Icons.NextjsIcon,
filterIcon: true,
linkWithDescription: 'headless/nextjs',
},
{
title: 'GraphQL',
- description: 'With Freeform, you can easily query form layouts and submit form mutations using GraphQL.',
+ description: 'Query form layouts and create submissions with GraphQL mutations.',
icon: Icons.GraphqlIcon,
linkWithDescription: 'headless/graphql',
},
diff --git a/craft-freeform/setup/betas.mdx b/craft-freeform/setup/betas.mdx
index d4e29212..d8cb4667 100644
--- a/craft-freeform/setup/betas.mdx
+++ b/craft-freeform/setup/betas.mdx
@@ -10,30 +10,22 @@ import { Photo } from '@site/src/components/utils';
As part of our commitment to developing and releasing reliable and stable software, we thoroughly beta test all major and minor feature releases.
-:::info
-Freeform 5 is now available!
-:::
-
## Current Beta
-Stay tuned for the next Freeform beta!
+There is **no active Freeform plugin or npm package beta** at this time.
-If you wish to join this beta, please follow the instructions and guide below.
-
-:::danger
-While we carefully develop new features and put all of them through rigorous alpha and beta testing, we cannot guarantee that there will be no major issues in beta software. It's also possible that specific edge case scenarios may have been missed, etc. For this reason, we strongly advise you **not** to use the current beta version in production environments.
-:::
+Official headless React packages ship as **`0.1.0`** on npm — see [Headless → Getting Started](../headless/getting-started.mdx).
## Providing Feedback
We would greatly appreciate it if you are able to provide us with routine feedback throughout your testing and usage of the beta. Even if you aren't using any of the new features or changes, providing feedback allows us to get a better idea of how things are going for all kinds of websites.
-To make this even easier, we've included a Beta Feedback widget that displays in the Freeform control panel. Just click a star rating (_5/5_ to indicate if everything is going well, or less if you are experiencing any issues) and if you have anything to clarify, fill in a quick note in the text area and click submit. You can optionally include your email address, which will also help us get in touch with you to get more information about any issues and/or assist you as necessary.
+To make this even easier, we've included a Beta Feedback widget that displays in the Freeform control panel during plugin betas. Just click a star rating (_5/5_ to indicate if everything is going well, or less if you are experiencing any issues) and if you have anything to clarify, fill in a quick note in the text area and click submit. You can optionally include your email address, which will also help us get in touch with you to get more information about any issues and/or assist you as necessary.
Feel free to also just use a [Private Support Ticket](/support/) or report an issue via [GitHub Issues](https://github.com/solspace/craft-freeform/issues).
:::info
-**This widget will only display during betas.** If you wish to disable it from displaying, add `FREEFORM_DISABLE_BETA_FEEDBACK_WIDGET=1` to your `.ENV` file.
+**This widget will only display during plugin betas.** If you wish to disable it from displaying, add `FREEFORM_DISABLE_BETA_FEEDBACK_WIDGET=1` to your `.ENV` file.
:::
=24.14.0 <25"
},
"pnpm": {
"overrides": {
diff --git a/vercel.json b/vercel.json
index 6017c5c0..4a734736 100644
--- a/vercel.json
+++ b/vercel.json
@@ -1,6 +1,7 @@
{
"cleanUrls": true,
"trailingSlash": true,
+ "installCommand": "corepack enable && pnpm install",
"redirects": [
{
"destination": "/craft/freeform/v5/",