Superpower your WordPress site with GraphQL

Use Gato GraphQL as multiple plugins all in one:

  • ✅ APIs
  • ✅ Automator
  • ✅ Bulk editing
  • ✅ Code snippets
  • ✅ Content distribution
  • ✅ Email notifications
  • ✅ HTTP client
  • ✅ Import/export
  • ✅ Search & replace
  • ✅ Translation
  • ✅ Webhooks

Superpower #1: GraphQL as an API

Expose your data like a PRO

Add dynamic features to your site / Go headless

Decouple your app: Use WordPress on the server-side to manage your data, and your framework of choice on the client-side to render your site.

Remain in control of your data

Be able to sleep at night with plenty of safety features.

Speed is on your side

The server is engineered to be fast, very fast.

Superpower #2: GraphQL as engine for your app

Fetch data for your blocks, themes and plugins

Power your Gutenberg blocks

Fetch data to render your Gutenberg blocks on all different contexts:

Superpower #3: GraphQL as dev/admin tool

Manage admin tasks, Bulk edit & update content

Transform content

Create and update posts in bulk:

Handle large data sets with ease

Superpower #4: GraphQL as translation tool

Go multilingual

Translate content

Translate your posts to any language using Google Translate.

Superpower #5: GraphQL as HTTP client

Interact with the cloud

Post data to (and receive data from) any service

Interact with services via their APIs:

Superpower #6: GraphQL as notifications system

“You've got mail!”

Send notifications by email

Communicate with your users, send notifications to the admin:

Superpower #7: GraphQL as data synchronization tool

Import/Export content, Sync data across sites

Import content

Import any content (posts, users, tags, etc) from multiple sources:

Distribute content across a network

Manage content in a WordPress multisite, or in a network of independent sites:

Superpower #8: GraphQL as automation tool

Automate all the things

Automate tasks

Trigger the execution of Persisted Queries on action hooks and WP-Cron tasks, to:

Acquire your superpowers with a bundle

Purchase any product with a 30-day money back guarantee

Do everything with the “All in One Toolbox for WordPress” Bundle

  1. “All in One Toolbox for WordPress” Bundle

    Achieve all superpowers: All of Gato GraphQL extensions, in a single plugin

Or choose a Bundle for your specific needs

  1. “Automated Content Translation & Sync for WordPress Multisite” Bundle

    Automatically create a translation of a newly-published post using the Google Translate API, for every language site on a WordPress multisite

  2. “Better WordPress Webhooks” Bundle

    Easily create webhooks to process incoming data from any source or service using advanced tools, directly within the wp-admin

  3. “Easy WordPress Bulk Transform & Update” Bundle

    Transform hundreds of posts with a single operation (replacing strings, adding blocks, adding a thumbnail, and more), and store them again on the DB

  4. “Private GraphQL Server for WordPress” Bundle

    Use GraphQL to power your application (blocks, themes and plugins), internally fetching data without exposing a public endpoint

  5. “Responsible WordPress Public API” Bundle

    Enhance your public APIs with additional layers of security, speed, power, schema evolution and control

  6. “Selective Content Import, Export & Sync for WordPress” Bundle

    Import hundreds of records into your WordPress site from another site or service (such as Google Sheets), and selectively export entries to another site

  7. “Simplest WordPress Content Translation” Bundle

    Translate your content into over 130 languages using the Google Translate API, without adding extra tables or inner joins to the DB

  8. “Tailored WordPress Automator” Bundle

    Create workflows to automate tasks (to transform data, automatically caption images, send notifications, and more)

  9. “Unhindered WordPress Email Notifications” Bundle

    Send personalized emails to all your users, and notifications to the admin, without constraints on what data can be added to the email

  10. “Versatile WordPress Request API” Bundle

    Interact with any external API and cloud service, posting and fetching data to/from them

Incredibly detailed documentation

👨🏻‍🏫

Queries Library: Ready-to-use solutions to multiple problems

Guides: Learn how to configure and use the plugin

Tutorial: Step by step explanation of all elements in the GraphQL schema

Augment WordPress search capabilities

Sometimes searching for data within WordPress is limited, as with custom fields: Searching in WordPress for posts that contain some keyword will not search within meta values.

Gato GraphQL can complement these capabilities, allowing us to search for posts (and also users, comments, and taxonomies) by meta key and value (including via regex expressions).

Query dynamic data

Gato GraphQL provides "function" fields, allowing us to compute logic already within the GraphQL query, thus avoiding the need to code a client application.

This way, we can dynamically compute data, input it back into the query, and affect the response with granular control. For instance, we can compute the date "24hs ago" and search for comments added from this date onwards.

Complement WP-CLI

Gato GraphQL can empower WP-CLI, by helping us find the WordPress data we need with granular control, and then injecting into the WP-CLI command (to update a post or user, reply to a comment, delete an option, etc).

We can even retrieve the data from multiple resources at once, and execute WP-CLI on all of them.

For instance, we can select all users with any Spanish locale, and update their locale to Spanish from Argentina.

Send personalized emails

Gato GraphQL allows us to execute mutations under any type from the GraphQL schema (i.e. not only under the Root type).

We can then iterate the list of users, obtain their data (name, email and a meta value with the number of remaining credits), dynamically compose a message using Markdown, and send a personalized email to the user.

Power blocks with DRY logic (for CSR/SSR)

Rendering a dynamic (Gutenberg) block on the client (for the WordPress editor) or on the server-side (for printing the blog post) typically requires fetching the block's data in different ways: using JavaScript to connect to the API endpoint, and using PHP to call WordPress functions, respectively.

Gato GraphQL allows to have a single source of truth to fetch data for both the client and server-sides, making this logic DRY (Don't Repeat Yourself).

Map JavaScript components to blocks

Gato GraphQL provides fields to query the properties from (Gutenberg) blocks.

This allows us to use block data when building a headless (or decoupled) application, mapping each block with a custom JavaScript component.

Duplicate a blog post

Duplicating a post is an example of Gato GraphQL's ability to retrieve, manipulate and store again data in the site.

You have the ultimate control on how the post will be duplicated. Change the author or post status, append "(Copy)" to the title, or any other.

Customize content for different users

We can retrieve a different response in a field depending on some piece of queried data, such as the roles of the logged-in user.

For instance, the post content can append an "Edit this post" link at the bottom of the content for the admin user only.

Adapt content in bulk

Gato GraphQL allows us to execute a mutation on hundreds or even thousands of resources at once.

Migrate the domain, or a post or page slug, in all content

After migrating the site to a new domain, or changing the slug of a post or page, we can convert all content throughout the site to point to the new URL.

Insert/remove a block in bulk

We can update posts by modifying their (Gutenberg) block's HTML content, and we can do it in bulk.

This is useful for promoting campaigns, inserting a custom block with our Call To Action to all posts in the website, and removing it from everywhere right after the campaign ends.

Retrieve structured data from blocks

We can iterate the (Gutenberg) blocks in the post and extract the attributes from deep within the block structure, unlocking these attributes to be fed into any functionality in our site, and to be exposed via an API to power our other applications (mobile app, newsletter, etc).

For instance, by extracting all the image URLs contained in the core/image blocks in a post, we can create an image carousel with all these images.

Automate tasks

Gato GraphQL can help us automate tasks in the application, such as sending a notification email when a comment is added, automatically adding a mandatory block to a new post, pinging external services on some activity, and others.

Gato GraphQL can also be integrated with WP-Cron, allowing us to execute GraphQL queries that run some admin task on a timely basis.

Interact with external services

Gato GraphQL provides fields to execute HTTP requests against a webserver, allowing us to interact with external services and APIs.

For instance, we can retrieve the list of subscribers from a Mailchimp list, combine those records with the user data in the site, and execute an action with the augmented data.

Filter data from an external API

If the external API does not allow filtering by a certain property that we need, we can use Gato GraphQL to iterate over the entries in the API response, and remove those ones that do not satifsy our condition.

query {
postsWithThumbnail: posts( filter: {
metaQuery: {
key: "_thumbnail_id",
compareBy: {
key: { operator: EXISTS }
}
}
} ) {
id
title
featuredImage {
src
}
}

usersWithSpanishLocale: users( filter: {
metaQuery: {
key: "locale",
compareBy: { stringValue: {
operator: REGEXP
value: "es_[A-Z]+"
} }
}
} ) {
id
name
locale: metaValue(key: "locale")
}
}
query {
timeNow: _time
time24HsAgo: _intSubstract(
substract: 86400,
from: $__timeNow
)
date24HsAgo: _date(
format: "Y-m-d\\TH:i:sO",
timestamp: $__time24HsAgo
)
commentsAddedInLast24Hs: comments(
filter: {
dateQuery: {
after: $__date24HsAgo
}
}
) {
date
content
author {
name
}
}
}
GRAPHQL_QUERY='
query RetrieveData {
users( filter: {
metaQuery: {
key: "locale",
compareBy: { stringValue: {
value: "es_[A-Z]+"
operator: REGEXP
} }
}
} ) {
id @export(
as: "userIDs",
type: LIST
)
}
}

query FormatAndPrintData @depends(on: "RetrieveData") {
spanishLocaleUserIDs: _arrayJoin(
array: $userIDs,
separator: " "
)
}
'

GRAPHQL_BODY="{\"operationName\": \"FormatAndPrintData\", \"query\": \"$(echo $GRAPHQL_QUERY | tr '\n' ' ' | sed 's/"/\\"/g')\"}"
GRAPHQL_RESPONSE=$(curl -X POST -H "Content-Type: application/json" -d $GRAPHQL_BODY https://mysite.com/graphql/)
SPANISH_LOCALE_USER_IDS=$(echo $GRAPHQL_RESPONSE | grep -E -o '"spanishLocaleUserIDs\":"((\d|\s)+)"' | cut -d':' -f2- | cut -d'"' -f2- | rev | cut -d'"' -f2- | rev)
for USER_ID in $(echo $SPANISH_LOCALE_USER_IDS); \
do wp user update "$(echo $USER_ID)" --locale=es_AR; \
done
mutation {
users {
email
displayName
remainingCredits: metaValue(key: "credits")

emailMessageTemplate: _strConvertMarkdownToHTML(
text: """
Hello %s,

Your have **%s remaining credits** in your account.

Would you like to [buy more](%s)?
"""

)
emailMessage: _sprintf(
string: $__emailMessageTemplate,
values: [ $__displayName, $__remainingCredits, "https://mysite.com/buy-credits" ]
)

_sendEmail( input: {
to: $__email
subject: "Remaining credits alert"
messageAs: {
html: $__emailMessage
}
} ) {
status
}
}
}
/** JavaScript: Fetch data to render the block on the client (CSR) */
import dryGraphQLQuery from './graphql-documents/fetch-posts-for-my-block.gql';

const response = await fetch(endpoint, {
body: JSON.stringify({
query: dryGraphQLQuery
)
} );
// Do something with the data
// ...

use GatoGraphQL\InternalGraphQLServer\GraphQLServer;

/** PHP: Fetch data to render the block on the server (SSR) */
$block = [
'render_callback' => function(array $attributes, string $content): string {
$dryGraphQLQuery = __DIR__ . '/blocks/my-block/graphql-documents/fetch-posts-for-my-block.gql';
$response = GraphQLServer::executeQueryInFile($dryGraphQLQuery);
$responseContent = json_decode($response->getContent(), true);
if (isset($responseContent["errors"]) {
return __('Oops, executing the query produced errors');
}
$data = $responseContent["data"];
// Do something with the data
// $content = $this->useGraphQLData($content, $data);
return $content;
},
];
register_block_type("namespace/my-block", $block);
<script type="module">
import { h, render } from 'https://esm.sh/preact';
renderPost(1);
async function renderPost(postId) {
const response = await fetch( "https://mysite.com/graphql/", {
body: JSON.stringify( {
query: `{ post(by: { id: ${ postId } }) { blockDataItems } }`
} )
} );
const json = await response.json();
if (json.errors) { return; }
const blocks = json.data.post?.blockDataItems;
const App = h('div', {}, blocks.map(mapBlockToComponent));
render(App, document.body);
}
function mapBlockToComponent(block) {
if (block.name === 'core/heading') return Heading(block);
if (block.name === 'core/paragraph') return Paragraph(block);
if (block.name === 'core/image') return Image(block);
return null;
}
function Heading(props) {
return h('h2', { dangerouslySetInnerHTML: { __html: props.attributes.content } });
}
function Paragraph(props) {
return h('p', { dangerouslySetInnerHTML: { __html: props.attributes.content } });
}
function Image(props) {
return h('img', { src: props.attributes.url });
}
</script>
query GetPostAndExportData($postId: ID!) {
post(by: { id : $postId }) {
author {
id @export(as: "authorID")
}
categories {
id @export(as: "categoryIDs", type: LIST)
}
rawContent @export(as: "rawContent")
rawExcerpt @export(as: "excerpt")
featuredImage {
id @export(as: "featuredImageID")
}
tags {
id @export(as: "tagIDs", type: LIST)
}
rawTitle @strAppend(string: " (Copy)") @export(as: "title")
}
}

mutation DuplicatePost @depends(on: "GetPostAndExportData") {
createPost(input: {
authorBy: { id: $authorID },
categoriesBy: { ids: $categoryIDs },
contentAs: { html: $rawContent },
excerpt: $excerpt
featuredImageBy: { id: $featuredImageID },
tagsBy: { ids: $tagIDs },
title: $title
}) {
status
}
}
query ExportConditionalVariables {
me {
roleNames @remove
isAdminUser: _inArray(value: "administrator", array: $__roleNames)
@export(as: "isAdminUser")
}
}

query RetrieveContentForAdminUser($postId: ID!)
@include(if: $isAdminUser)
{
post(by: { id : $postId }) {
originalContent: content @remove
wpAdminEditURL @remove
content: _sprintf(
string: "%s<p><a href=\"%s\">%s</a></p>",
values: [ $__originalContent, $__wpAdminEditURL, "(Admin only) Edit post" ]
)
}
}

query RetrieveContentForNonAdminUser($postId: ID!)
@skip(if: $isAdminUser)
{
post(by: { id : $postId }) {
content
}
}

query ExecuteAll
@depends(on: ["ExportConditionalVariables", "RetrieveContentForAdminUser", "RetrieveContentForNonAdminUser"]
) {
id @remove
}
query TransformAndExportData($replaceFrom: [String!]!, $replaceTo: [String!]!, $limit: Int! = 5, $offset: Int! = 0) {
posts: posts( pagination: { limit: $limit, offset: $offset } ) {
title
excerpt
@strReplaceMultiple(
search: $replaceFrom
replaceWith: $replaceTo
affectAdditionalFieldsUnderPos: 1
)
@deferredExport(
as: "postInputs"
type: DICTIONARY
affectAdditionalFieldsUnderPos: 1
)
}
}

mutation UpdatePost($limit: Int! = 5, $offset: Int! = 0)
@depends(on: "TransformAndExportData")
{
adaptedPosts: posts( pagination: { limit: $limit, offset: $offset } ) {
id
postInput: _objectProperty(
object: $postInputs,
by: { key: $__id }
) @remove
update(input: $__postInput) {
status
}
}
}
query ExportData($oldPageSlug: String!, $newPageSlug: String!) {
siteURL: optionValue(name: "siteurl")
oldPageURL: _strAppend(after: $__siteURL, append: $oldPageSlug)
@export(as: "oldPageURL")
newPageURL: _strAppend(after: $__siteURL, append: $newPageSlug)
@export(as: "newPageURL")
}

mutation ReplaceOldWithNewURLInPosts
@depends(on: "ExportData")
{
posts( filter: { search: $oldPageURL } ) {
id
rawContent
adaptedRawContent: _strReplace(
search: $oldPageURL
replaceWith: $newPageURL
in: $__rawContent
)
update(input: {
contentAs: { html: $__adaptedRawContent }
}) {
status
post {
title
excerpt
}
}
}
}
mutation InjectBlock($limit: Int! = 5, $offset: Int! = 0) {
posts: posts( pagination: { limit: $limit, offset: $offset } ) {
rawContent
adaptedRawContent: _strRegexReplace(
in: $__rawContent,
searchRegex: "#(<!-- /wp:paragraph -->[\\s\\S]+<!-- /wp:paragraph -->[\\s\\S]+<!-- /wp:paragraph -->)#U",
replaceWith: "$1<!-- mycompany:black-friday-campaign-video -->\n<figure class=\"wp-block-video\"><video controls src=\"https://mysite.com/videos/BlackFriday2023.mp4\"></video></figure>\n<!-- /mycompany:black-friday-campaign-video -->",
limit: 1
)
update(input: { contentAs: { html: $__adaptedRawContent } }) {
status
}
}
}

mutation RemoveBlock {
posts(filter: { search: "\"<!-- /mycompany:black-friday-campaign-video -->\"" } ) {
rawContent
adaptedRawContent: _strRegexReplace(
in: $__rawContent,
searchRegex: "#(<!-- mycompany:black-friday-campaign-video -->[\\s\\S]+<!-- /mycompany:black-friday-campaign-video -->)#",
replaceWith: ""
)
update(input: { contentAs: { html: $__adaptedRawContent } }) {
status
}
}
}
query GetImageBlockImageURLs($postID: ID!) {
post(by: { id: $postID } ) {
coreImageURLs: blockFlattenedDataItems(
filterBy: { include: "core/image" }
)
@underEachArrayItem(
passValueOnwardsAs: "blockDataItem"
)
@applyField(
name: "_objectProperty"
arguments: {
object: $blockDataItem,
by: {
path: "attributes.url"
}
}
setResultInResponse: true
)
@arrayUnique
}
}
use \GatoGraphQL\InternalGraphQLServer\GraphQLServer;

add_action(
'new_to_publish',
function (WP_Post $post) {
if ($post->post_type !== 'post') {
return;
}
// Provide the query to execute
$query = '{ ... }';
$variables = [
'postTitle' => $post->post_title,
'postContent' => $post->post_content,
'postURL' => get_permalink($post->ID),
]
GraphQLServer::executeQuery($query, $variables, 'SendEmail');
}
);

wp_schedule_event(
time(),
'daily',
'gatographql__execute_persisted_query',
[
'daily-stats-by-email-number-of-comments',
[ 'to' => ['admin@mysite.com'] ],
'SendDailyStatsByEmailNumberOfComments',
1 // This is the admin user's ID
]
);
query GetDataFromMailchimp {
mailchimpListMembersJSONObject: _sendJSONObjectItemHTTPRequest(input: {
url: "https://us7.api.mailchimp.com/3.0/lists/{LIST_ID}/members",
method: GET,
options: {
auth: {
username: "{ USER }",
password: "{ API_TOKEN }"
}
}
})
@underJSONObjectProperty(by: { key: "members"})
@underEachArrayItem
@underJSONObjectProperty(by: { key: "email_address"})
@export(as: "mailchimpListMemberEmails")
}

query GetUsersUsingMailchimpSubscriberEmails
@depends(on: "GetDataFromMailchimp")
{
users(filter: { searchBy: { emails: $mailchimpListMemberEmails } } ) {
id
name
email
}
}
query {
usersWithWebsiteURL: _sendJSONObjectCollectionHTTPRequest(
input: {
url: "https://some-wp-rest-api.com/wp-json/wp/v2/users/?_fields=id,name,url"
}
)
@underEachArrayItem(
passValueOnwardsAs: "userDataEntry"
affectDirectivesUnderPos: [1, 2, 3]
)
@applyField(
name: "_objectProperty"
arguments: {
object: $userDataEntry
by: {
key: "url"
}
}
passOnwardsAs: "websiteURL"
)
@applyField(
name: "_isEmpty"
arguments: {
value: $websiteURL
}
passOnwardsAs: "isWebsiteURLEmpty"
)
@if(condition: $isWebsiteURLEmpty)
@setNull
@arrayFilter
}

Plugin screenshots

Private GraphiQL client

Execute your GraphQL queries in the wp-admin via a private GraphiQL client.

Private Interactive Schema client

Interactively browse the private GraphQL schema in the wp-admin, exploring all connections among entities.

Public GraphiQL client

The GraphiQL client for the single endpoint is exposed to the Internet.

Public Interactive Schema client

Interactively browse the public GraphQL schema exposed for the single endpoint.

Persisted Queries

Persisted queries are pre-defined and stored in the server.

Executing Persisted Queries

Requesting a persisted query URL will retrieve its pre-defined GraphQL response.

Custom Endpoints

We can create multiple custom endpoints, each for a different target.

Schema Configurations

All endpoints (the single endpoint, custom endpoints, persisted queries, and internal endpoints for the application) are configured via Schema Configurations.

Schema Configurations

We can create many Schema Configurations, customizing them for different users or applications.

Public or Private Endpoints

Custom endpoints and persisted queries can be public, private and password-protected.

Endpoint Categories

Manage custom endpoints and persisted queries by adding categories to them.

Security

We can configure exactly what custom post types, options and meta keys can be queried on an endpoint by endpoint basis.

Settings

Configure every aspect from the plugin via the Settings page.

Modules

Modules providing different functionalities and GraphQL schema extensions can be enabled and disabled.

Extensions

Augment the plugin functionality and GraphQL schema via extensions.

Tutorial

The Tutorial section demonstrates queries using all elements from the GraphQL schema.

Access Control (extension)

Validate who can access the endpoint in granular fashion (down to the operation, field and directive level), based on user roles and capabilities, visitor IP, and more.

Cache Control (extension)

Cache the API response via standard HTTP caching, with the max-age automatically calculated based on the fields present in the query.

Field Deprecation via UI (extension)

Deprecate fields to evolve the GraphQL schema directly from the user interface.

GraphiQL client to execute queries in the wp-admin

Interactively browse the private GraphQL schema, exploring all connections among entities

The GraphiQL client for the single endpoint is exposed to the Internet

Interactively browse the public GraphQL schema exposed for the single endpoint

Persisted queries are pre-defined and stored in the server

Requesting a persisted query URL will retrieve its pre-defined GraphQL response

We can create multiple custom endpoints, each for a different target

Endpoints are configured via Schema Configurations

We can create many Schema Configurations, customizing them for different users or applications

Custom endpoints and Persisted queries can be public, private and password-protected

Manage custom endpoints and persisted queries by adding categories to them

We can configure exactly what custom post types, options and meta keys can be queried on an endpoint by endpoint basis

Configure every aspect from the plugin via the Settings page

Modules providing different functionalities and GraphQL schema extensions can be enabled and disabled

Augment the plugin functionality and GraphQL schema via extensions

The Tutorial section demonstrates queries using all elements from the GraphQL schema

Validate who can access the endpoint in granular fashion (down to the operation, field and directive level), based on user roles and capabilities, visitor IP, and more

Cache the API response via standard HTTP caching, with the max-age automatically calculated based on the fields present in the query

Deprecate fields to evolve the GraphQL schema directly from the user interface