A WordPress plugin that provides a GraphQL API for interacting with Gravity Forms. This is currently an unfinished work in progress that is being actively developed.
Using WordPress as a headless CMS with a separate JavaScript-powered frontend single-page app is an increasingly popular tech stack. Traditionally, REST APIs have been used for the purpose of sending data back & forth between the frontend and backend in setups like this but the REST architecture has its limitations.
Using GraphQL means that if your frontend app needs to fetch data for a number of different resources, all of that data can be fetched from the server with a single request. Your frontend app can even define which fields it requires for each of the resources, giving it full control over which pieces of data are fetched and included in the response.
Fortunately, a GraphQL implementation exists for WordPress - WPGraphQL.
WPGraphQL for Gravity Forms extends the WPGraphQL plugin, allowing frontend apps to interact with the Gravity Forms data stored in a headless WordPress backend. This plugin couples the great forms functionality of Gravity Forms with the powerful WordPress-specific GraphQL implementation that WPGraphQL provides.
Our hope for this open source project is that it will enable more teams to leverage GraphQL for building fast, interactive frontend apps that source their data from WordPress and Gravity Forms.
- Use Composer to require the plugin as a dependency of your project. Alternatively, you can download it into your
plugins
directory, just like any other WordPress plugin. - Activate the plugin, along with the WPGraphQL and Gravity Forms plugins that it depends on.
- Use a tool like GraphiQL to view the schema and send a few test requests to your
/graphql
endpoint to interact with Gravity Forms data, and start sending requests from your frontend app.
The example query below shows how you can get a form and its fields.
If you want to get the form with an ID of 1
, you need to generate a global ID for that object and pass the global ID in as the id
input. This can be done in JavaScript using the btoa()
function like this, where GravityFormsForm
is the GraphQL type and 1
is the form ID:
const globalId = btoa(`GravityFormsForm:1`); // Results in "R3Jhdml0eUZvcm1zRm9ybTox"
For fields
, pass in first:300
, where 300
is the maximum number of fields you want to query for.
Inside of fields
, you must include query fragments indicating what data you'd like back for each field, as shown below. You'll want to make sure that you have a fragment for every type of field that your form has.
{
gravityFormsForm(id: "R3Jhdml0eUZvcm1zRm9ybTox") {
formId
cssClass
cssClassList
fields(first: 300) {
nodes {
... on TextField {
type
id
label
cssClass
cssClassList
}
... on TextAreaField {
type
id
label
cssClass
cssClassList
}
... on SelectField {
type
id
label
cssClass
cssClassList
}
}
}
}
}
The form entry submission process works like this:
- Send a
createGravityFormsDraftEntry
mutation to create a new draft form entry. This gives you back theresumeToken
, which is the unique identifier for the draft entry that you need to pass in to all the mutations below. - Send as many "update" mutations (such as
updateDraftEntryTextFieldValue
,updateDraftEntrySelectFieldValue
, etc.) as you need to update the values of the draft entry. - When ready, send a
submitGravityFormsDraftEntry
that turns the draft entry into a permanent entry.
If you're wondering why several mutations are required to submit a form entry rather than just a single mutation, please read the comments in this issue.
For large forms, #2 on the list above could potentially result in the need to send a large number of "update" mutations to the backend to update form entry field values. Using something like Apollo Client's apollo-link-batch-http is recommended so that your app will be able to send a large number of mutations to the backend all within a single HTTP request to update the draft entry.
As of Dec. 23, 2019, updating draft entries with file upload field data is not yet supported, along with a few other field types.
mutation {
createGravityFormsDraftEntry(
input: { clientMutationId: "123abc", formId: 2 }
) {
resumeToken
}
}
The example below shows updateDraftEntryTextFieldValue
, which can be used for updating the value of a text field. Similar mutations exist for other field types, such as updateDraftEntrySelectFieldValue
, updateDraftEntryAddressFieldValue
, etc.
Use the resumeToken
from the createGravityFormsDraftEntry
mutation's response. It is this draft entry's unique identifier.
mutation {
updateDraftEntryTextFieldValue(
input: {
clientMutationId: "123abc"
resumeToken: "524d5f3a30c845b29a8db35c9f2aaf29"
fieldId: 5
value: "new text field value"
}
) {
resumeToken
entry {
entryId # This will be null, since draft entries don't have an ID yet.
resumeToken # This will be the same resumeToken that was passed in.
isDraft # This will be set to true.
fields(first: 300) {
edges {
node {
... on TextField {
id
type
}
}
fieldValue {
... on TextFieldValue {
value
}
}
}
}
}
errors {
message
}
}
}
If the field is updated successfully, the errors
field will be null
, and the entry
in the response will be the newly updated entry, with the new field value.
If the field is NOT updated successfully, such as when a field validation error occurs, the entry
object in the response will be unchanged (the new field value will NOT have been applied), and the errors
field will provide data about the error. Example:
"errors": [
{
"message": "The text entered exceeds the maximum number of characters."
}
]
Once all updates have been performed, the submitGravityFormsDraftEntry
mutation shown below can be run to submit the draft entry so that it becomes a permanent entry.
The entry
field in the response contains data for the newly created entry.
mutation {
submitGravityFormsDraftEntry(
input: {
clientMutationId: "123abc"
resumeToken: "5df948218f40484d81e808d0ebc8651b"
}
) {
entryId # This will be the ID of the newly created entry.
entry {
entryId # This will be the ID of the newly created entry.
resumeToken # This will be null, since the entry is no longer a draft.
isDraft # This will be set to false.
fields {
edges {
node {
__typename
... on TextField {
type
id
}
}
fieldValue {
__typename
... on TextFieldValue {
value
}
}
}
}
}
}
}
The mutation below shows how to delete a draft entry. The resumeToken
of the deleted draft entry will be in the response.
mutation {
deleteGravityFormsDraftEntry(
input: {
clientMutationId: "123abc"
resumeToken: "524d5f3a30c845b29a8db35c9f2aaf29"
}
) {
resumeToken
}
}
The gravityFormsEntry
query supports both entries and draft entries. See the "Get a Single Entry" section below.
The code comments in the example below explain how you can get a filtered list of entries.
The plugin supports first/after cursor-based pagination, but does not yet support before/last pagination.
Inside of fields
, you must include query fragments indicating what data you'd like back for each field, as shown below. You'll want to make sure that you have a fragments inside of node { ... }
and inside of fieldValue { ... }
for every type of field that your form has.
{
gravityFormsEntries(
first: 20
after: "eyJvZmZzZXQiOjAsImluZGV4Ijo0fQ==" # Or pass null to start from the beginning.
where: {
# List of all the form IDs to include.
formIds: [1]
# Find entries between this start & end date.
dateFilters: {
startDate: "2019-09-22 02:26:23"
endDate: "2019-10-25 02:26:23"
}
fieldFiltersMode: "all"
fieldFilters: [
# Find entries created by user ID 1.
{ key: "created_by", intValues: [1], operator: "in" }
# Find entries where field 5 has a value of "somevalue".
{ key: "5", stringValues: ["somevalue"], operator: "in" }
]
}
) {
nodes {
entryId
formId
isDraft
status
dateCreated
createdBy {
node {
userId
name
}
}
fields(first: 300) {
edges {
node {
... on TextField {
type
label
}
... on SelectField {
type
label
}
... on AddressField {
type
label
}
}
fieldValue {
... on TextFieldValue {
value
}
... on SelectFieldValue {
value
}
... on AddressFieldValue {
street
lineTwo
city
state
zip
country
}
}
}
}
}
}
}
The example query below shows how you can get a single entry by ID, and data about the fields and their values.
If you want to get the form with an ID of 1
, you need to generate a global ID for that object and pass the global ID in as the id
input. This can be done in JavaScript using the btoa()
function like this, where GravityFormsEntry
is the GraphQL type and 1
is the form ID:
const globalId = btoa(`GravityFormsEntry:1`); // Results in "R3Jhdml0eUZvcm1zRW50cnk6MQ=="
The id
input field can be the entry's ID, or the resumeToken
if it is a draft entry.
For fields
, pass in first: 300
, where 300
is the maximum number of fields you want to query for.
Inside of fields
, you must include query fragments indicating what data you'd like back for each field, as shown below. You'll want to make sure that you have a fragments inside of node { ... }
and inside of fieldValue { ... }
for every type of field that your form has.
{
gravityFormsEntry(id: "R3Jhdml0eUZvcm1zRW50cnk6MQ==") {
id
entryId
formId
isDraft
resumeToken
fields(first: 300) {
edges {
node {
... on TextField {
type
label
}
... on SelectField {
type
label
}
... on AddressField {
type
label
}
}
fieldValue {
... on TextFieldValue {
value
}
... on SelectFieldValue {
value
}
... on AddressFieldValue {
street
lineTwo
city
state
zip
country
}
}
}
}
}
}
The example query below shows how you can fetch data for multiple forms at once.
Cursor-based pagination is supported. You can use the first
, last
, before
and after
fields, along with the data inside of pageInfo
and the cursors returned by the API to get each page of forms data.
Filtering is also supported. For the where
field, you can specify a status
to get forms that are active, inactive, in the trash, etc.
For fields
, pass in first: 300
, where 300
is the maximum number of fields you want to query for.
Inside of fields
, you must include query fragments indicating what data you'd like back for each field, as shown below. You'll want to make sure that you have a fragment for every type of field that your forms have.
{
gravityFormsForms(first: 10, after: null, where: { status: ACTIVE }) {
pageInfo {
startCursor
endCursor
hasPreviousPage
hasNextPage
}
edges {
cursor
node {
formId
title
fields(first: 300) {
nodes {
... on TextField {
type
id
label
cssClass
cssClassList
}
... on TextAreaField {
type
id
label
cssClass
cssClassList
}
... on SelectField {
type
id
label
cssClass
cssClassList
}
}
}
}
}
}
}
- Add support for pagination of lists of entries.
- Ability to query for lists of draft entries, or both entries and draft entries
- Add support for updating draft entries with file upload data.
- Ability to get the total count for a list of entries.
- Ability to delete an individual Gravity Forms entry.
- Ability to update an individual Gravity Forms entry.
- Create & update integration tests.
- Ability to create an individual Gravity Form.
- Ability to update an individual Gravity Form.
- Ability to delete an individual Gravity Form.