back-arrow

Developing Your First Headless WordPress Project with ACF + WPGraphQL

author icon

Author

Royson Rajan

Published

December 13, 2023

Category

Wordpress

Developing Your First Headless WordPress Project with ACF + WPGraphQL

The idea of separating the backend from the front end is gaining popularity, which makes traditional CMS quite restricted. You become dependent on the CMS without being able to improve it. 

Building a backend platform might be challenging, especially if you need the necessary skills. For this reason, we must utilize already-existing media during the development phase. The WordPress CMS platform is one of them.

Prerequisites

You will comprehend this tutorial better if you have the following prerequisites:

  •  Fundamental Knowledge about website construction.
  • Are aware of WordPress’ functionality and how to use it to build a website.

A headless CMS: Definition

WordPress is currently the most widely used CMS. Because they are so intently focused on the writing process, content writers love it. The customization’s extensibility is a favorite among developers.

The idea of a headless CMS for the WordPress platform then develops along with this extensibility. WordPress will serve as your backend platform and as the source of your data to be used by your selected frontend platform (JS, React, Vue, Angular, etc).

A CMS known as a “headless CMS” only serves as the backend and is intended to be accessed via API (REST or GraphQL). Because each Headless CMS device will respond to the API calls, frontend developers are free to create any template they want, regardless of the device they want to target.

WordPress already has a WordPress REST API feature that is built into the WordPress system. For this discussion, we will retrieve data from the WordPress platform using GraphQL.

GraphQL: Why Use It?

Facebook developed GraphQL to develop a more adaptable and efficient API. Developers’ issues with the REST API approach to API development can be resolved by using this method. However, using GraphQL to create an API also presents new problems for developers to work through.

GraphQL and REST API: Differences

As an illustration, consider the case in which we show all titles and links from WordPress “post” and “page” creations made by a specific user. 

Using the REST API to Retrieve Data

Let’s attempt to retrieve the data using a REST API strategy based on the aforementioned scenario. At the very least, we must access multiple endpoints to retrieve detailed post and page data. When we access the endpoint and request data, we will undoubtedly receive a JSON response. 

You can access various endpoints to collect data using the REST API.

To obtain useful information for the first time, use the /users/id> endpoint.

“ {

    “id”: 1,

    “name”: “Superadmin”,

    “url”: “<https://www.abc.com/>”,

    “description”: “,

    “link”: “<https://www.abc.com/author/superadmin/>”,

    “slug”: “admin”,

    “meta”: [],

    …

}”

The second endpoint is /posts?author=1 to retrieve all posts written by a specific user.

“ [

    {

        “id”: 1,

        “date”: “2019-02-02T08:26:17”,

        “date_gmt”: “2019-02-02T01:26:17”,

        “guid”: {

            “rendered”: “<https://www.abc.com/post-slug-1/>”

        },

        “modified”: “2019-02-02T08:26:17”,

        “modified_gmt”: “2019-02-02T01:26:17”,

        “slug”: “post-slug-1”,

        “status”: “publish”,

        “type”: “post”,

        “link”: “<https://www.abc.com/post-slug-1/>”,

        “title”: {

            “rendered”: “…”

        },

        “content”: {

            “rendered”: “…”,

            “Protected”: false

        },

        “excerpt”: {

            “rendered”: “…”,

            “Protected”: false

        },

        “author”: 1,

        “featured_media”: 6825,

        “comment_status”: “closed”,

        “ping_status”: “closed”,

        “sticky”: false,

        “template”: “”,

        “format”: “standard”,

        “meta”: [],

        “categories”: [

            865

        ],

        “tags”: [],

        …

    },

    {

        “id”: 2,

        “date”: “2019-02-02T08:26:17”,

        “date_gmt”: “2019-02-02T01:26:17”,

        “guid”: {

            “rendered”: “<https://www.abc.com/post-slug-2/>”

        },

        “modified”: “2019-02-02T08:26:17”,

        “modified_gmt”: “2019-02-02T01:26:17”,

        “slug”: “post-slug-2”,

        “status”: “publish”,

        “type”: “post”,

        “link”: “<https://www.abc.com/post-slug-2/>”,

        “title”: {

            “rendered”: “…”

        },

        “content”: {

            “rendered”: “…”,

            “Protected”: false

        },

        “excerpt”: {

            “rendered”: “…”,

            “Protected”: false

        },

        “author”: 1,

        “featured_media”: 6825,

        “comment_status”: “closed”,

        “ping_status”: “closed”,

        “sticky”: false,

        “template”: “”,

        “format”: “standard”,

        “meta”: [],

        “categories”: [

            865

        ],

        “tags”: [],

        …

    }

    …

]”

The third endpoint is pages?author=1, which retrieves all pages created by that user.

“ [

    {

        “id”: 1,

        “date”: “2022-06-27T10:46:52”,

        “date_gmt”: “2022-06-27T03:46:52”,

        “guid”: {

            “rendered”: “<https://www.abc.com/?page_id=1>”

        },

        “modified”: “2022-06-27T10:46:52”,

        “modified_gmt”: “2022-06-27T03:46:52”,

        “slug”: “blog”,

        “status”: “publish”,

        “type”: “page”,

        “link”: “<https://www.abc.com/blog/>”,

        “title”: {

            “rendered”: “Blog”

        },

        “content”: {

            “rendered”: “”,

            “Protected”: false

        },

        “excerpt”: {

            “rendered”: “”,

            “Protected”: false

        },

        “author”: 1,

        “featured_media”: 0,

        “parent”: 0,

        “menu_order”: 0,

        “comment_status”: “closed”,

        “ping_status”: “closed”,

        “template”: “”,

        “meta”: [],

        …

    },

    {

        “id”: 2,

        “date”: “2022-06-27T10:46:52”,

        “date_gmt”: “2022-06-27T03:46:52”,

        “guid”: {

            “rendered”: “<https://www.abc.com/?page_id=2>”

        },

        “modified”: “2022-06-27T10:46:52”,

        “modified_gmt”: “2022-06-27T03:46:52”,

        “slug”: “new-page”,

        “status”: “publish”,

        “type”: “page”,

        “link”: “<https://www.abc.com/new-page/>”,

        “title”: {

            “rendered”: “New Page”

        },

        “content”: {

            “rendered”: “”,

            “Protected”: false

        },

        “excerpt”: {

            “rendered”: “”,

            “Protected”: false

        },

        “author”: 1,

        “featured_media”: 0,

        “parent”: 0,

        “menu_order”: 0,

        “comment_status”: “closed”,

        “ping_status”: “closed”,

        “template”: “”,

        “meta”: [],

        …

    },

    …

]”

We have a tonne of data to sort through, hmm. To obtain the desired data using the REST API, you must submit three requests to three different endpoints. Because those endpoints return extra information that we don’t need, we will also experience over-fetching.

Data Retrieval Using GraphQL

What if we obtain the data using GraphQL, then? With GraphQL, we only require one query for which the purpose has been specified. In response, the server will deliver a JSON object to the requirements we previously specified. Let’s attempt to use GraphQL to obtain the data. The following is the query we will use to retrieve the data and the JSON response:

{ query NewQuery {

    user(id: “1”, idType: DATABASE_ID) {

      id

      name

      posts {

          edges {

          node {

              id

              title

              slug

          }

          }

      }

      pages {

          edges {

            node {

                id

                title

                slug

            }

          }

      }

    }

}}”

Data will be received in the below-mentioned format:

{

  “data”: {

    “user”: {

      “id”: “xxxyyy”,

      “name”: “admin”,

      “posts”: {

        “edges”: [

          {

            “node”: {

              “id”: “xxx”,

              “title”: “Post Slug 1”,

              “slug”: “post-slug-1”

            }

          },

          {

            “node”: {

              “id”: “yyy”,

              “title”: “Post Slug 2”,

              “slug”: “post-slug-2”

            }

          },

          …

        ]

      },

      “pages”: {

        “edges”: [

          {

            “node”: {

              “id”: “aaa”,

              “title”: “Blog”,

              “slug”: “blog”

            }

          },

          {

            “node”: {

              “id”: “bbb”,

              “title”: “New Page”,

              “slug”: “new-page”

            }

          },

      …

        ]

      }

    }

  }

}

Short, clear, and concise.

There will be no more overfetching, in which we download much more data than we need. 

Setting up the WpGraphQL plugin is the first step.

A free plugin called WPGraphQL is made to make it easier for you to get data out of Headless WordPress. This plugin includes an integrated development environment (IDE) for GraphQL that enables you to explore the GraphQL schema of your project and test queries and mutations. 

Your WordPress website will later be converted into a GraphQL API via WPGraphQL. This means that you can use any client that can send HTTP queries to GraphQL endpoints to communicate with this plugin.

  • Select Add New from the Plugins menu to start.
  • When using the WPGraphQL plugin, enter the keyword “wpgraphql” into the search box and select Install Now.
  • To activate the plugin, click Activate.
  • Your WordPress Admin menu will now have a new GraphQL menu.
  • You will be provided a Settings submenu to save your WPGraphQL plugin settings, as well as a GraphQL IDE submenu to explore the GraphQL queries on your WordPress website.

Step 2: Setting Up Headless WordPress

Let’s go on to setting up the WordPress website itself so that WordPress may function as your data source backend once you have a basic understanding of how to use GraphQL.

In the wp-content/themes directory, create the following three new files: index.php, functions.php, and style.css. Open the index.php file in your editor and enter the following code: 

<?PHP

/**

  * The main template file

  *

  * This is the most generic template file in a WordPress theme

  * and one of the two necessary files for a theme (the other being style.css).

  * It is used to display a page when nothing more specific matches a question.

  * E.g., it puts together the home page when no home.php file exists.

  *

  * link <https://developer.wordpress.org/themes/basics/template-hierarchy/>

  *

  * package WordPress

  * subpackage Headless

  * since Headless 1.0

  */

Exit;

In the functions.php file, click “Open,” and then type the following code:

<?PHP

/**

  * Headless functions and definitions

  *

  * @link <https://developer.wordpress.org/themes/basics/theme-functions/>

  *

  * package WordPress

  * subpackage Headless

  * since Headless 1.0

  */

if (!defined(‘ABSPATH’))

{

    exit;

} // Exit if accessed directly

Finally, open the style.css file in the theme directory and insert the following code:

/*

Theme Name: Headless

Text Domain: headless

Version: 1.0

Requires at least: 4.7

Requires PHP: 5.6

Add a Description: Headless theme for using WP as a REST API only

Tags: headless

Author: ARWebz Web Development

Author URI: <https://www.arwebz.com/>

Theme URI: <https://github.com/arwebz>

License URI: <http://www.gnu.org/licenses/gpl-2.0.html>

*/

To start a new theme in WordPress, those are the necessary parameters. I find it to be minimalistic. Save your work, then let’s attempt to enter our WordPress website. We will then see this.

Nothing, empty.

You don’t need to display anything on your WordPress website, which is the reason headless WordPress is built. Let’s go on to optimizing headless WordPress now.

Optimizing Headless WordPress: Step 2.2

If there isn’t an a.htaccess file already, you can create one by opening the root directory of WordPress and entering the following codes:

Options -Indexes

# Block wp-includes folder and files

<IfModule mod_rewrite.c>

RewriteEngine On

RewriteBase /

RewriteRule ^wp-admin/includes/ – [F,L]

RewriteRule !^wp-includes/ – [S=3]

RewriteRule ^wp-includes/.*\\\\.php$ – [F, L]

RewriteRule ^wp-includes/js/tinymce/langs/.+\\\\.php – [F,L]

RewriteRule ^wp-includes/theme-compat/ – [F,L]

</IfModule>

<files wp-config.php>

order allow, deny

deny from all

</files>

# BEGIN WordPress

<IfModule mod_rewrite.c>

RewriteEngine On

RewriteRule .* – [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

RewriteBase /

RewriteRule ^index\\\\.php$ – [L]

RewriteCond %{REQUEST_FILENAME} !-f

RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule. /index.php [L]

</IfModule>

# END WordPress

Setting the wp-config.php file, which is optional: Step 2.3.

The following codes should be added before the /* That’s all, stop editing!

define(‘AUTOMATIC_UPDATER_DISABLED’, true);

define(‘AUTOSAVE_INTERVAL’, 300); // seconds

define(‘DISABLE_WP_CRON’, false);

define(‘DISALLOW_FILE_EDIT’, true);

define(‘DISALLOW_FILE_MODS’, true);

define(‘EMPTY_TRASH_DAYS’, 7);

define(‘FS_CHMOD_DIR’, (0755 &~ umask()));

define(‘FS_CHMOD_FILE’, (0644 &~ umask()));

define(‘FS_METHOD’, ‘direct’);

define(‘WP_MEMORY_LIMIT’, ‘512M’);

define(‘WP_POST_REVISIONS’, 0);

@ini_set(‘display_errors’, ‘Off’); // Off OR On

@ini_set(‘error_reporting’, 0); // E_ALL OR 0

@ini_set(‘log_errors’, ‘Off’); // Off OR On

@ini_set(‘allow_url_fopen’, ‘Off’);

@ini_set(‘allow_url_include’, ‘Off’);

@ini_set(‘asp_tags’, ‘Off’);

@ini_set(‘date. timezone’, ‘Asia/Jakarta’);

@ini_set(‘enable_dl’, ‘Off’);

@ini_set(‘file_uploads’, ‘On’);

@ini_set(‘max_execution_time’, 0);

@ini_set(‘max_input_time’, -1);

@ini_set(‘max_input_vars’, 3000);

@ini_set(‘memory_limit’, ‘512M’);

@ini_set(‘post_max_size’, ‘128M’);

@ini_set(‘session.gc_maxlifetime’, 1440);

@ini_set(‘Zlib.output_compression’, ‘Off’);

/* 

Compatibility with Additional Plugins: Step 2.4.

Your WordPress website still allows for creative expression. With other WordPress plugins, the WPGraphQL plugin works reasonably well, including

To enhance the results of your GraphQL queries, use WPGraphQL SEO.

WPGraphQL for Advanced Custom Fields provides support for the plugin for Advanced Custom Fields. WPGraphQL Meta Query and WPGraphQL Tax Query are plugins for WordPress that provide support for Meta Query and Tax Query, respectively.

Bonus 1: Custom Post Type UI for WP GraphQL Plugin Configuration

From the WordPress admin, install and activate the Custom Post Type UI (CPT UI) plugin. Enter the new CPT UI menu once the CPT UI plugin has been installed and turned on.

For instance, we will develop a Custom Post Type Product and then complete the following columns. To make it simpler to fill out all other inputs, click the tab that says “Populate additional labels based on chosen labels.”

Remember to choose True for Show in GraphQL at the very bottom and enter the GraphQL Single Name and GraphQL Plural Name exactly as they are displayed above.

In the GraphQL IDE, you can later try running GraphQL queries against the product and product endpoints.

Add Taxonomies as well, such as Product Categories, Product Brands, and Product Tags, for our Post Type.

Don’t forget to enter the GraphQL Single Name and GraphQL Plural Name for each Taxonomy and to choose True in the Show in GraphQL section.

Each Taxonomy should be linked to a Post Type Product.

Let’s attempt to update the data in our Custom Post Type Product.

Remember to include the information from our Custom Taxonomy.

Bonus 2: WPGraphQL Plugin Setup with Advanced Custom Fields (ACF)

Using the Plugins > Add New menu, add the WPGraphQL for Advanced Custom Fields plugin.

Install the WPGraphQL for Advanced Custom Fields plugin after downloading the latest version’s source code (zip) file from this link.

Enter the new Custom Fields menu when the ACF and WPGraphQL for Advanced Custom Fields plugins have been installed and turned on. Custom Fields will be added to our Post Type offering. For our products, we might add information about regular prices, sale prices, and featured status. To add new Custom Fields, click Add New.

Choose Post Type Product under Rules.

Remember to select Yes for Show in GraphQL and enter the GraphQL Field Name of our GraphQL endpoint at the very bottom.

Query in GraphQL using CPT and ACF

The Custom Field products linked to the Post Type Product as well as its Custom Taxonomy (Product Brand, Product Category, and Product Tags) can now be queried.

query NewQuery {

  products {

    edges {

      node {

        id

        slug

        title

        myProducts {

          regularPrice

          salePrice

          featured

        }

        productBrands {

          edges {

            node {

              id

              name

              slug

            }

          }

        }

        productCategories {

          edges {

            node {

              id

              name

              slug

            }

          }

        }

        productTags {

          edges {

            node {

              id

              name

              slug

            }

          }

        }

      }

    }

  }

}

These results will be provided by the query:

“data”: {

  “products”: {

    “edges”: [

      {

        “node”: {

          “id”: “cG9zdDoxODc=”,

          “slug”: “asus-tuf-gaming-f15”,

          “title”: “ASUS TUF Gaming F15”,

          “myProducts”: {

            “regularPrice”: 800,

            “salePrice”: 750,

            “featured”: true

          },

          “productBrands”: {

            “edges”: [

              {

                “node”: {

                  “id”: “dGVybTo1”,

                  “name”: “ASUS”,

                  “slug”: “asus”

                }

              }

            ]

          },

          “productCategories”: {

            “edges”: [

              {

                “node”: {

                  “id”: “dGVybTo0”,

                  “name”: “Laptop”,

                  “slug”: “laptop”

                }

              }

            ]

          },

          “productTags”: {

            “edges”: [

              {

                “node”: {

                  “id”: “dGVybTo3”,

                  “name”: “gaming”,

                  “slug”: “gaming”

                }

              }

            ]

          }

        }

      }

    ]

  }

}

Final Thoughts

data, we will undoubtedly receive a JSON response. 

      You may also like

      cta img
      Ready To Get Started
      Let's Discuss Your Project
      calendar