Skip to main content

GraphQL
LitePro

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 guide and Craft's GraphQL API.

Demos

Have a look at our headless demos to get a feel for what's possible with GraphQL:

Queries

Querying Freeform data

Freeform has a root freeform query, which contains all the possible queries nested inside it to avoid conflicts with other plugins.

Querying multiple Forms

To query all or some forms, you have to use the forms query:

query {
freeform {
forms {
name
handle
pages {
label
rows {
fields {
id
type
label
handle
required
rules
}
}
}
}
}
}

Example response data:

{
"data": {
"freeform": {
"forms": [
{
"name": "My Form",
"handle": "myForm",
"pages": [
{
"label": "First Page",
"rows": [
{
"fields": [
{
"type": "text",
"label": "First Name",
"handle": "firstName",
"required": false
},
{
"type": "text",
"label": "Last Name",
"handle": "lastName",
"required": true
}
]
},
{
"fields": [
{
"type": "submit",
"label": "Submit",
"handle": null,
"required": false,
"rules": "{\"show\":false,\"type\":\"any\",\"criteria\":[{\"tgt\":\"lastName\",\"o\":\"=\",\"val\":\"\"}]}"
}
]
}
]
}
]
}
]
}
}
}

Querying a single Form

You can query a single form directly by using the form query:

query {
freeform {
form (handle: "my-form") {
name
handle
pages {
# ...
}
}
}
}

Example response data:

{
"data": {
"freeform": {
"form": {
"name": "My Form",
"handle": "myForm",
"pages": [
{
"label": "First Page",
"rows": [
{
"fields": [
{
"type": "text",
"label": "First name",
"handle": "firstName",
"required": false
}
]
}
]
}
]
}
}
}
}

Querying a form field on a Craft Entry

You can query an Entry form field by using the form field name in the query: Assuming the form field's handle is myFormFieldHandle and the section is called Blog

query {
entries {
id
title
... on blog_blog_Entry {
myFormFieldHandle {
id
name
fields {
handle
label
type
}
}
}
}
}

Example response data:

{
"data": {
"entries": [
{
"id": "226",
"title": "Some Entry title",
"myFormFieldHandle": {
"id": 1,
"name": "Simple Form",
"fields": [
{
"handle": "firstName",
"label": "First Name",
"type": "text"
},
{
"handle": null,
"label": "Submit",
"type": "submit"
}
]
}
}
]
}
}

Form Query Arguments

The following arguments are available when querying single or multiple forms:

ArgumentTypeDescription
id[Int]Narrow the queried forms by one or more form ID's
uid[String]Narrow the queried forms by one or more form UID's
handle[String]Narrow the queried forms by one or more form handles
limitIntLimit the amount of returned form results
offsetIntOffset the returned form results
orderByStringOrder the forms by a specific property
sortStringSort by asc or desc order
query {
freeform {
# This is just an example
# For `id`, `uid` and `handle` - you can either pass a single value
# or an array of values for each argument
form(
id: 1
handle: "test"
limit: 1
offset: 2
orderBy: "name"
sort: "desc"
) {
id
}
}
}

Field Query Arguments

You can query fields by the following arguments:

ArgumentTypeDescription
id[Int]Narrow the queried fields by one or more field's ID's
hash[String]Narrow the queried fields by one or more field's UID's
handle[String]Narrow the queried fields by one or more field's handles
query {
freeform {
forms {
# This is just an example
# You can either pass a single value or an array of values for each argument
fields(
id: [1, 2, 3]
handle: ["firstName", "lastName"]
hash: "some-hash"
) {
id
handle
hash
}
}
}
}

Mutations4.1.0+

GraphQL mutations provide a way to modify data. The actual mutations will vary depending on the schema and what it allows. There are common mutations per GraphQL object type and additional type-specific mutations.

Creating a new Submission

To create a new submission we use form-specific mutations in the form of save_<formHandle>_Submission.

Mutations take the data as arguments. We pass the values we want to use in the query as query variables.

In this example, we're using a form with the handle of contact which contains form fields for first name, last name and email. Our form also has Honeypot and ReCaptcha V3 enabled.

You can grab these values using form-specific GraphQL queries or by creating an custom endpoint on your site.

See How to Render a Form.

// Query
mutation SaveContactSubmission(
$honeypot: FreeformHoneypotInputType,
$reCaptcha: FreeformSubmissionReCaptchaInputType,
$csrfToken: FreeformCsrfTokenInputType,
$firstName: String,
$lastName: String,
$email: String,
$mailingList_1bkqk7kOQ: Int,
$mailingList_kj1l1a9qg: Int,
) {
save_contact_Submission(
honeypot: $honeypot
reCaptcha: $reCaptcha
csrfToken: $csrfToken
firstName: $firstName
lastName: $lastName
email: $email
mailingList_1bkqk7kOQ: $mailingList_1bkqk7kOQ
mailingList_kj1l1a9qg: $mailingList_kj1l1a9qg
) {
submissionId
success
}
}

// Query Variables
{
"honeypot": {
"name": "freeform_form_handle",
"value": ""
}
"reCaptcha": {
"name": "g-recaptcha-response",
"value": "INSERT RECAPCTHA RESPONSE",
},
"csrfToken": {
"name": "INSERT CSRF TOKEN NAME",
"value": "INSERT CSRF TOKEN VALUE",
},
"firstName": "Solspace",
"lastName": "Freeform",
"email": "[email protected]",
"mailingList_1bkqk7kOQ": 0,
"mailingList_kj1l1a9qg": 1
}

Example response data:

{
"data": {
"save_contact_Submission": {
"submissionId": 1982,
"success": true
}
}
}

Submission Mutation Arguments

ArgumentTypeDescription
...Arguments depending on the field layout for the form

Interfaces

Form InterfaceRevised 4.1.0+

These are the fields available for the FreeformFormInterface:

FormInterface is deprecated. Please use FreeformFormInterface instead. ajax is deprecated. Please use ajaxEnabled instead.

FieldTypeDescription
idIntID
uidStringUID
nameStringName
handleStringHandle
hashStringThe form's hash needed to submit forms
colorStringColor
descriptionStringDescription
returnUrlStringThe specified return URL
storeDataBooleanAre submissions being stored for this form
submissionTitleFormatStringTitle format used for new submission titles
submissionMutationNameStringThe forms GraphQL mutation name for submissions
extraPostUrlStringAn URL that will get a POST call with the submitted data
extraPostTriggerPhraseStringA keyword or phrase Freeform should check for in the output of the POST URL to know if and when there’s an error to log, e.g. ‘error’ or ‘an error occurred’.
ipCollectingEnabledBooleanAre the IP addresses being stored
ajaxEnabledBooleanIs the ajax enabled for this form
showSpinnerBooleanShould the submit button show a spinner when submitting
showLoadingTextBooleanShould the submit button change the button label while submitting
loadingTextStringThe submit button loading label text
defaultStatusIntThe assigned default status ID of new submissions
formTemplateStringThe assigned default formatting template
gtmEnabledBooleanIs the Google Tag Manager enabled for this form?
gtmIdStringThe Google Tag Manager ID
gtmEventNameStringThe name of the Event that will be added to Google Tag Manager's data layer
honeypotFreeformHoneypotInterfaceA fresh honeypot instance
csrfTokenFreeformCsrfTokenInterfaceA fresh csrf token
reCaptchaFreeformFormReCaptchaInterfaceThe ReCaptcha for this form
fields[FreeformFieldInterface]A list of Form's Field
pages[FreeformPageInterface]A list of Form's Page entities
mailingListName4.1.6+, Revised in 4.1.12+[String]The form's mailing list field handles. Use mailingList_ + unique handle/hash for mailing list field, e.g. mailingList_1bkqk7kOQ.
successMessage4.1.6+StringThe specified success message
errorMessage4.1.6+StringThe specified error message
disableSubmit4.1.6+BooleanShould the form’s submit button be disabled when the form is submitted
disableReset4.1.6+BooleanShould the form’s submit button be disabled state be reset
enctype4.1.6+StringThe form’s enctype

Field InterfaceRevised 4.1.0+

FieldInterface is deprecated. Please use FreeformFieldInterface instead.

The base FreeformFieldInterface implemented by all field types. You can see specific Field Type fields here.

FieldTypeDescription
idIntID
typeStringType
labelStringLabel
hashStringGenerated unique field hash
handleStringHandle
instructionStringInstructions
requiredBooleanIs the field required
pageIndexIntThe page index this field is assigned to
inputAttributes[FreeformAttributeInterface]Field's input attributes
labelAttributes[FreeformAttributeInterface]Field's label attributes
errorAttributes[FreeformAttributeInterface]Field's error attributes
instructionAttributes[FreeformAttributeInterface]Field's instruction attributes
rules4.1.5+StringField's rules as a JSON string

Conditional Rules4.1.5+

The rules JSON string contains the following properties:

  • show Show or hide this item
  • type when all or any of its criteria match.
  • criteria will contain an array with the target handle, operand and value.
    • The example below would result in hiding the email field when its value is blank/empty:
      {\"show\":false,\"type\":\"any\",\"criteria\":[{\"tgt\":\"email\",\"o\":\"=\",\"val\":\"\"}]}

Page InterfaceRevised 4.1.0+

PageInterface is deprecated. Please use FreeformPageInterface instead.

Each page contains a list of FreeformRowInterface objects:

FieldTypeDescription
indexIntIndex of the page
labelStringPage's label
rows[FreeformRowInterface]A list of FreeformRowInterface objects

Row InterfaceRevised 4.1.0+

RowInterface is deprecated. Please use FreeformRowInterface instead.

Each row contains a list of fields:

FieldTypeDescription
idStringA unique hash generated for each row
fields[FreeformFieldInterface]A list of FreeformFieldInterface objects

Submission InterfaceRevised 4.1.0+

These are the fields available for the FreeformSubmissionInterface:

FieldTypeDescription
idIntThe ID of the submission
finishedBooleanWhether the submission is finished or not
freeformPayloadStringThe payload of the submission
hashStringThe generated hash for the submission
reCaptchaFreeformSubmissionReCaptchaInterfaceThe ReCaptcha (name and value) of the submission
csrfTokenFreeformCsrfTokenInterfaceThe CSRF token (name and value) of the submission
honeypotFreeformHoneypotInterfaceThe Honeypot (name and value) of the submission
htmlStringThe generated HTML for the submission
multiPageBooleanWhether the submission has multiple pages or not
onSuccessStringThe success behaviour of the submission
returnUrlStringThe return URL of the submission
submissionIdIntThe ID of the submission
submissionLimitReachedBooleanWhether the form submission rate limit has been reached or not
submissionTokenStringThe generated token for the submission
successBooleanWhether the submission is a success or not
dateCreatedDateTimeThe created date for the submission
dateUpdatedDateTimeThe updated date for the submission
isSpamBooleanWhether the submission is a spam or not
spamReasonsStringSpam reasons for the submission
userUserThe author of the submission
assets[Asset]The assets of the submission

Option InterfaceRevised 4.1.0+

OptionsInterface is deprecated. Please use FreeformOptionInterface instead.

FieldTypeDescription
valueStringOption's value
labelStringOption's label
checkedBooleanIs the option checked

Opinion Scale InterfaceRevised 4.1.0+

ScalesInterface is deprecated. Please use FreeformOpinionScaleInterface instead.

FieldTypeDescription
valueStringValue
labelStringLabel

Attribute InterfaceRevised 4.1.0+

KeyValueMapInterface is deprecated. Please use FreeformAttributeInterface instead.

FieldTypeDescription
attributeStringName of the Attribute
valueStringValue of the Attribute

Honeypot Interface4.1.0+

Use FreeformHoneypotInterface.

hash and timestamp are deprecated. Please do not use.

FieldTypeDescription
nameStringName of Honeypot
valueStringValue of Honeypot

CSRF Token Interface4.1.0+

Use FreeformCsrfTokenInterface.

FieldTypeDescription
nameStringName of the CSRF token
valueStringValue of the CSRF token

Form ReCaptcha Interface4.1.0+

Use FreeformFormReCaptchaInterface.

FieldTypeDescription
nameStringThe forms GraphQL mutation name for submissions
handleStringThe forms GraphQL mutation handle for submissions
enabledBooleanIs ReCaptcha enabled for this form

Submission ReCaptcha Interface4.1.0+

Use FreeformSubmissionReCaptchaInterface.

FieldTypeDescription
nameStringName of the ReCaptcha
valueStringValue of the ReCaptcha

Field Types

Text

FieldTypeDescription
valueStringThe default value
maxLengthIntMaximum length of chars allowed for this field
placeholderStringInput's placeholder attribute
fields {
... on FreeformField_Text {
value
maxLength
placeholder
}
}

Textarea

FieldTypeDescription
valueStringThe default value
maxLengthIntMaximum length of chars allowed for this field
rowsIntNumber of rows shown for this textarea
placeholderStringInput's placeholder attribute
fields {
... on FreeformField_Textarea {
value
maxLength
rows
placeholder
}
}

Select

FieldTypeDescription
valueStringThe default value
options[FreeformOptionInterface]The assigned options
fields {
... on FreeformField_Select {
value
options {
value
label
checked
}
}
}

Checkbox

FieldTypeDescription
valueStringThe default value
checkedBooleanShould the checkbox be checked by default
fields {
... on FreeformField_Checkbox {
value
checked
}
}

Multi-Select

FieldTypeDescription
values[String]The default values
options[FreeformOptionInterface]The assigned options
fields {
... on FreeformField_MultipleSelect {
values
options {
value
label
checked
}
}
}

Checkbox Group

FieldTypeDescription
values[String]The default values
oneLineBooleanShould this be shown in a single line
options[FreeformOptionInterface]The assigned options
fields {
... on FreeformField_CheckboxGroup {
values
oneLine
options {
value
label
checked
}
}
}

Radio Group

FieldTypeDescription
valueStringThe default value
options[FreeformOptionInterface]The assigned options
fields {
... on FreeformField_RadioGroup {
value
options {
value
label
checked
}
}
}

Email

FieldTypeDescription
notificationIdStringThe ID or the filename of the email template
placeholderStringInput's placeholder attribute
fields {
... on FreeformField_Email {
notificationId
placeholder
}
}

Dynamic Recipients

FieldTypeDescription
values[String]The default values
notificationIdStringThe ID or the filename of the email template
showAsRadioBooleanShould the field be shown as a list of radio inputs
showAsCheckboxesBooleanShould the field be shown as a list of checkbox inputs
oneLineBooleanShould radios or checkboxes be displayed in a single line
options[FreeformOptionInterface]The assigned options
fields {
... on FreeformField_DynamicRecipients {
values
notificationId
showAsRadio
showAsCheckboxes
oneLine
options {
valuel
label
checked
}
}
}

Number

FieldTypeDescription
valueStringThe default value
minLengthIntMinimum length of the number
minValueIntMinimum value of the number
maxValueIntMaximum length of the number
decimalCountIntNumber of decimals
decimalSeparatorStringThe decimal separator
thousandsSeparatorStringThe thousands separator
allowNegativeBoolAllow negative numbers
placeholderStringInput's placeholder attribute
fields {
... on FreeformField_Number {
value
minLength
minValue
maxValue
decimalCount
decimalSeparator
thousandsSeparator
allowNegative
placeholder
}
}

File

FieldTypeDescription
fileKinds[String]Allowed file kinds
maxFileSizeKBIntMaximum allowed filesize in KB
fileCountIntNumber of allowed simultaneous file uploads
fields {
... on FreeformField_File {
fileKinds
maxFileSizeKB
fileCount
}
}

Confirmation

FieldTypeDescription
targetFieldHashStringHash of the field that has to be confirmed
fields {
... on FreeformField_Confirmation {
targetFieldHash
}
}

Date & Time

FieldTypeDescription
valueStringThe default value
dateTimeTypeStringType of the date field. ("date", "time", "both")
generatePlaceholderBooleanShould a placeholder be auto-generated for this field
dateOrderStringOrder of the date chunks.
date4DigitYearBooleanDetermines if the year should be displayed with 4 digits or two
dateLeadingZeroBooleanDetermines if the dates should use a leading zero
dateSeparatorStringDate separator
clock24hBooleanShould the clock use a 24h format
clockSeparatorStringClock separator
clockAMPMSeparateBooleanShould the AM/PM be separated from the time by a space
useDatepickerBooleanShould the built-in datepicker be used
minDateStringSpecifies the minimum allowed date that can be picked
maxDateStringSpecifies the maximum allowed date that can be picked
fields {
... on FreeformField_Datetime {
value
dateTimeType
generatePlaceholder
dateOrder
date4DigitYear
dateLeadingZero
dateSeparator
clock24h
clockSeparator
clockAMPMSeparate
useDatepicker
minDate
maxDate
}
}

Opinion Scale

FieldTypeDescription
valueStringThe default value
legends[String]A list of all the assigned Legends
scales[FreeformOpinionScaleInterface]The assigned FreeformOpinionScaleInterface
fields {
... on FreeformField_OpinionScale {
value
legends
scales {
value
label
}
}
}

Phone

FieldTypeDescription
valueStringThe default value
patternStringPhone number pattern
useJsMaskBooleanShould the built-in pattern matcher javascript be enabled
fields {
... on FreeformField_Phone {
value
pattern
useJsMask
}
}

Rating

FieldTypeDescription
valueStringThe default value
maxValueIntPhone number pattern
colorIdleStringColor of the unselected, unhovered rating star
colorHoverStringColor of the hovered rating star
colorSelectedStringColor of the selected rating star
fields {
... on FreeformField_Rating {
value
maxValue
colorIdle
colorHover
colorSelected
}
}

Regex

FieldTypeDescription
valueStringThe default value
patternStringRegex pattern
messageStringThe error message to be displayed
fields {
... on FreeformField_Regex {
value
pattern
message
}
}

Signature

FieldTypeDescription
widthIntWidth in pixels
heightIntHeight in pixels
showClearButtonBooleanDetermines if the "clear" button should be displayed
borderColorStringSignature field border color
backgroundColorStringSignature field background color
penColorStringSignature field pen color
penDotSizeFloatThe size of the pen dot
fields {
... on FreeformField_Signature {
width
height
showClearButton
borderColor
backgroundColor
penColor
penDotSize
}
}

Table

FieldTypeDescription
useScriptBooleanShould the built-in javascript for handling table rows be used
maxRowsIntNumber of maximum allowed rows this table can have
addButtonLabelBooleanThe label for the "add row" button
addButtonMarkupStringCustom html for the "add row" button
removeButtonLabelStringThe label for the "delete row" button
removeButtonMarkupStringCustom html for the "delete row" button
tableLayoutStringJSON of the table layout
fields {
... on FreeformField_Table {
useScript
maxRows
addButtonLabel
addButtonMarkup
removeButtonLabel
removeButtonMarkup
tableLayout
}
}

Email Marketing

FieldTypeDescription
hiddenBooleanShould this field be a hidden field or a checkbox
fields {
... on FreeformField_MailingList {
hidden
}
}

Submit

FieldTypeDescription
labelNextStringLabel for the "next" button
labelPrevStringLabel for the "previous" button
disablePrevBooleanIs the "previous" button disabled
positionStringPosition of the buttons
fields {
... on FreeformField_Submit {
labelNext
labelPrev
disablePrev
position
}
}

How to Render a Form Revised 4.1.0+

Below are the steps for being able to render your own form in the front-end using GraphQL data and another headless solution such as (but not limited to) Vue.js, Next.js, or React JS.

Overview

To be able to make a form submittable you will need several things:

  • An action entry in the POST payload that points to freeform/submit.
  • A formHash entry which you will have to retrieve from an endpoint you create.
  • A honeypot entry if you're using the Freeform Honeypot.
  • A reCaptcha entry if you're using the Freeform Captchas.
  • A csrfToken entry if you're using Craft's CSRF tokens.
  • A freeform_payload entry if you're using Encrypted Payload as the session storage type.

To get a valid formHash, honeypot, reCaptcha, csrfToken and freeform_payload, you will have to create an endpoint on your site.

Twig

If you're using Twig, here's how to return the form properties data:

{# You can attach custom form properties #}
{# https://docs.solspace.com/craft/freeform/v4/templates/queries/form/#parameters #}
{% set formProperties = {} %}
{% set form = craft.freeform.form('your_form_handle') %}
{{ form.json(formProperties) }}

PHP

If you're making your own endpoint via PHP, then we've outlined some simple steps to follow in order to set up a custom Freeform Module.

Make sure to read How to Build a Module if you are new to extending Craft with custom modules.

1

Create a Module

Below is an example of what your /modules/FreeformModule.php file should look like:

<?php

namespace modules;

use Craft;
use yii\base\Module;

class FreeformModule extends Module
{
public function init()
{
Craft::setAlias('@modules', __DIR__);

if (Craft::$app->getRequest()->getIsConsoleRequest()) {
$this->controllerNamespace = 'modules\\console\\controllers';
} else {
$this->controllerNamespace = 'modules\\controllers';
}

parent::init();
}
}
2

Update the Application Config

Add your module to your project's application configuration by listing it in the /config/app.php file:

<?php

use modules\FreeformModule;

return [
'modules' => [
'freeform-module' => FreeformModule::class,
],
'bootstrap' => ['freeform-module'],
];
3

Add Controller

Create a new controller for Freeform located at /modules/controllers/FormController.php:

<?php

namespace modules\controllers;

use craft\web\Controller;
use Solspace\Freeform\Freeform;
use yii\web\Response;

class FormController extends Controller
{
protected array|bool|int $allowAnonymous = true;

public function actionIndex(): string
{
return 'Welcome to the Craft Freeform Form Controller';
}

public function actionProperties(int $formId): Response
{
$formProperties = [];

$formModel = Freeform::getInstance()->forms->getFormById($formId);

if ($formModel) {
$form = $formModel->getForm()->json($formProperties);

return $this->asJson(json_decode($form));
}

return $this->asJson(['message' => 'Form was not found']);
}
}
4

Update the Routes

Update the /config/routes.php with your new module controller actions:

<?php

return [
'graphql/api' => 'graphql/api',
'freeform/form' => 'freeform-module/form/index',
'freeform/form/properties/<formId:\d+>' => 'freeform-module/form/properties',
];
5

Example JSON Response

The JSON response looks like this:

{
"hash": "xxxxxxxx-xxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"handle": "my-form-handle",
"ajaxEnabled": true,
"disableSubmit": true,
"disableReset": false,
"showSpinner": false,
"showLoadingText": false,
"loadingText": "Loading...",
"class": "",
"method": "post",
"enctype": "application\/x-www-form-urlencoded",
"successMessage": "Form has been submitted successfully!",
"errorMessage": "Sorry, there was an error submitting the form. Please try again.",
"honeypot": {
"name": "freeform_form_handle",
"value": ""
},
// If using encrypted payloads:
"freeform_payload": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"anchor": "xxxxxx-form-xxxxxxxx-xxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"csrf": {
"name": "CRAFT_CSRF_TOKEN",
"token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
"action": "freeform\/submit"
// If using ReCaptcha:
"reCaptcha": {
"enabled": true,
"handle": "reCaptcha",
"name": "g-recaptcha-response" // or h-captcha-response
},
// If using mailing lists:
"mailingListName": ["mailingList_xxxxxx"]
}
Finished!

Have a look at our headless demos to get a feel for what's possible with GraphQL: