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
}
}
}
}
}
}

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
}
]
}
]
}
]
}
]
}
}
}

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"
}
]
}
}
]
}
}

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
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"]) {
id
handle
}
}
}
}

Mutations

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. We also pass some form property overrides, setting dynamic notification recipients.

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(
$formProperties: FreeformFormPropertiesInputType,
$honeypot: FreeformHoneypotInputType,
$captcha: FreeformSubmissionCaptchaInputType,
$csrfToken: FreeformCsrfTokenInputType,
$firstName: String,
$lastName: String,
$email: String,
) {
save_contact_Submission(
formProperties: $formProperties
honeypot: $honeypot
captcha: $captcha
csrfToken: $csrfToken
firstName: $firstName
lastName: $lastName
email: $email
) {
submissionId
success
}
}

// Query Variables
{
"formProperties": {
"dynamicNotification": {
"recipients": ["support@solspace.com", "user@solspace.com"],
"template": "new_template_1.twig"
}
}
"honeypot": {
"name": "freeform_form_handle",
"value": ""
}
"captcha": {
"name": "g-recaptcha-response",
"value": "INSERT RECAPCTHA RESPONSE",
},
"csrfToken": {
"name": "INSERT CSRF TOKEN NAME",
"value": "INSERT CSRF TOKEN VALUE",
},
"firstName": "Solspace",
"lastName": "Freeform",
"email": "support@solspace.com"
}

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 Interface

These are the fields available for the FreeformFormInterface:

FieldTypeDescription
idIntID
uidStringUID
nameStringName
handleStringHandle
typeStringType
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
ipCollectingEnabledBooleanAre the IP addresses being stored
ajaxRenamed in 5.0+BooleanIs the ajax enabled for this form
showProcessingSpinnerBooleanShould the submit button show a spinner when submitting
showProcessingTextBooleanShould the submit button change the button label while submitting
processingTextStringThe 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?
honeypotFreeformHoneypotInterfaceA fresh honeypot instance
csrfTokenFreeformCsrfTokenInterfaceA fresh csrf token
captchaRenamed in 5.0+FreeformFormCaptchaInterfaceThe captcha for this form
fields[FreeformFieldInterface]A list of Form's Field
pages[FreeformPageInterface]A list of Form's Page entities
successMessageStringThe specified success message
errorMessageStringThe specified error message
disableSubmitBooleanShould the form’s submit button be disabled when the form is submitted
disableResetBooleanShould the form’s submit button be disabled state be reset
enctypeStringThe form’s enctype
rulesFreeformRulesInterfaceThe form’s rules

Field Interface

Use FreeformFieldInterface.

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

FieldTypeDescription
idIntID
typeStringType
labelStringLabel
handleStringHandle
instructionStringInstructions
requiredBooleanIs the field required
pageIndexIntThe page index this field is assigned to
attributesNew in 5.0+[FreeformAttributesInterface]Field's attributes

Page Interface

Use FreeformPageInterface.

Each page contains a list of FreeformRowInterface objects:

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

Row Interface

Use FreeformRowInterface.

Each row contains a list of fields:

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

Submission Interface

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
captchaRenamed in 5.0+FreeformSubmissionCaptchaInterfaceThe Captcha (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 behavior of the submission
returnUrlStringThe return URL of the submission
submissionIdIntThe ID of the submission
duplicateRenamed in 5.0+BooleanWhether the form submission is a duplicate 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 Interface

Use FreeformOptionInterface.

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

Opinion Scale Interface

Use FreeformOpinionScaleInterface.

FieldTypeDescription
valueStringValue
labelStringLabel

Attributes InterfaceNew in 5.0+

Use FreeformAttributesInterface.

FieldTypeDescription
inputFreeformAttributeInterfaceInput attributes
labelFreeformAttributeInterfaceLabel attributes
instructionsFreeformAttributeInterfaceInstructions attributes
containerFreeformAttributeInterfaceContainer attributes
errorFreeformAttributeInterfaceError attributes

Attribute Interface

Use FreeformAttributeInterface.

FieldTypeDescription
attributeStringName of the Attribute
valueStringValue of the Attribute

Honeypot Interface

Use FreeformHoneypotInterface.

FieldTypeDescription
nameStringName of Honeypot
valueStringValue of Honeypot

CSRF Token Interface

Use FreeformCsrfTokenInterface.

FieldTypeDescription
nameStringName of the CSRF token
valueStringValue of the CSRF token

Form Captcha InterfaceRenamed in 5.0+

Use FreeformFormCaptchaInterface.

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

Submission Captcha Interface

Use FreeformSubmissionCaptchaInterface.

FieldTypeDescription
nameStringName of the Captcha
valueStringValue of the Captcha

Buttons InterfaceNew in 5.0+

Use FreeformButtonsInterface.

FieldTypeDescription
layoutStringThe button layout
submitLabelStringThe button submit label
backBooleanWhether the button has back button or not
backLabelStringThe button back label
saveBooleanWhether the button has save button or not
saveLabelStringThe button save label
saveRedirectUrlStringThe button save redirect URL
emailFieldFreeformFieldInterfaceThe button email notification recipient
notificationTemplateFreeformNotificationTemplateInterface
attributesFreeformButtonsAttributesInterfaceThe button attributes

Buttons Attributes InterfaceNew in 5.0+

Use FreeformButtonsAttributesInterface.

FieldTypeDescription
containerFreeformAttributeInterfaceContainer attributes
columnFreeformAttributeInterfaceColumn attributes
buttonWrapperFreeformAttributeInterfaceButton Wrapper attributes
submitFreeformAttributeInterfaceSubmit button attributes
backFreeformAttributeInterfaceBack button attributes
saveFreeformAttributeInterfaceSave button attributes

Notification Template InterfaceNew in 5.0+

Use FreeformNotificationTemplateInterface.

FieldTypeDescription
idIntThe ID of the notification template
nameStringThe name of the notification template
handleStringThe file name of the notification template
descriptionStringThe description of the notification template
fromEmailStringThe from email that will appear in the notification template
fromNameStringThe from name that will appear in the notification template
replyToNameStringThe reply to name that will appear in the notification template
replyToEmailStringThe reply to email that will appear in the notification template
ccStringThe email address(es) you would like to be CC'd in the notification template
bccStringThe email address(es) you would like to be BCC'd in the notification template
includeAttachmentsBooleanWhether to include attachments in the notification template or not
presetAssets[String]Assets included as attachments in the notification template
subjectStringThe subject of the notification template
bodyStringThe HTML content of the notification template
textBodyStringThe text content of the notification template
autoTextBooleanWhether Freeform will automatically provide a Text-only version of the notification based on the HTML version

Rules InterfaceNew in 5.0+

Use FreeformRulesInterface.

FieldTypeDescription
pages[FreeformPageRuleInterface]The page rules
fields[FreeformFieldRuleInterface]The field rules

Page Rule InterfaceNew in 5.0+

Use FreeformPageRuleInterface.

FieldTypeDescription
idIntThe ID for the page rule
uidStringThe UID for the page rule
pageFreeformPageInterfaceThe page for the rule
combinatorStringThe combinator for the page rule
conditions[FreeformRuleConditionInterface]The conditions for the page rule

Field Rule InterfaceNew in 5.0+

Use FreeformFieldRuleInterface.

FieldTypeDescription
idIntThe ID for the field rule
uidStringThe UID for the field rule
fieldFreeformFieldInterfaceThe field for the field rule
combinatorStringThe combinator for the field rule
conditions[FreeformRuleConditionInterface]The conditions for the field rule

Rule Condition InterfaceNew in 5.0+

Use FreeformRuleConditionInterface.

FieldTypeDescription
uidStringThe UID for the rule condition
fieldFreeformFieldInterfaceThe field for the rule condition
operatorStringThe operator for the rule condition
valueStringThe value for the rule condition

Form Properties InterfaceNew in 5.0+

Use FreeformFormPropertiesInterface.

FieldTypeDescription
dynamicNotificationFreeformDynamicNotificationInterface`Allows using a dynamic template level notification for a more fine-grained control.

Dynamic Notification InterfaceNew in 5.0+

Use FreeformDynamicNotificationInterface.

FieldTypeDescription
recipients[String]The recipients of the notification
templateStringThe name of the notification template

Field Types

Text

FieldTypeDescription
valueStringThe default value
placeholderStringInput's placeholder attribute
fields {
... on FreeformField_Text {
value
placeholder
}
}

Textarea

FieldTypeDescription
valueStringThe default value
rowsIntNumber of rows shown for this textarea
placeholderStringInput's placeholder attribute
fields {
... on FreeformField_Textarea {
value
rows
placeholder
}
}
FieldTypeDescription
valueStringThe default value
options[FreeformOptionInterface]The assigned options
fields {
... on FreeformField_Dropdown {
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
}
}
}

Checkboxes

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

Radios

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

Email

FieldTypeDescription
placeholderStringInput's placeholder attribute
fields {
... on FreeformField_Email {
placeholder
}
}

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
}
}

How to Render a Form

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 captcha 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, captcha, 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/v5/templates/queries/form/#parameters #}
{% set formProperties = {} %}
{% set form = freeform.form('your_form_handle', 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();
}
}

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'],
];
2

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
{
$form = Freeform::getInstance()->forms->getFormById($formId);

if ($form) {
return $this->asJson($form->toArray());
}

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

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',
];
3

Example JSON Response

The JSON response looks like this:

{
"id": 1,
"hash": "xxxxxxxx-xxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "My Form Handle",
"handle": "my-form-handle",
"class": "Solspace\\Freeform\\Form\\Types\\Regular",
"enctype": "application\/x-www-form-urlencoded",
"properties": {
"hash": "YNkW4Jzye-O2Gd90zeo-tY6a9U8ihSaw0zdJ9yUKne7P7OiLNVnuMVi9Nf5v",
"init-time": 1694603762,
"secret": "F2okR1p8To6y1nAZaY5n"
},
"attributes": {
"row": {},
"success": {},
"errors": {}
},
"settings": {
"behavior": {
"ajax": true,
"showProcessingSpinner": true,
"showProcessingText": true,
"processingText": "Its Processing...",
"successBehavior": "reload",
"successTemplate": "successsample.twig",
"returnUrl": "/",
"successMessage": "Form has been submitted successfully!",
"errorMessage": "Sorry, there was an error submitting the form. Please try again.",
"duplicateCheck": "limit_once_per_email",
"stopSubmissionsAfter": "2023-09-15T23:00:00.000000Z"
},
"general": {
"name": "My Form Handle",
"handle": "my-form-handle",
"type": "Solspace\\Freeform\\Form\\Types\\Regular",
"submissionTitle": "{{ dateCreated|date(\"Y-m-d H:i:s\") }}",
"formattingTemplate": "basic-dark/index.twig",
"description": "Test Form",
"color": "#d0021b",
"postForwardingUrl": "",
"postForwardingErrorTriggerPhrase": "",
"storeData": true,
"defaultStatus": 3,
"collectIpAddresses": true,
"allowUsersToOptIn": true,
"optInCheckbox": null
}
},
"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 Captcha:
"captcha": {
"enabled": true,
"handle": "captcha",
"name": "g-recaptcha-response" // or h-captcha-response
}
}
Finished!

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