Interact with all your data in WordPress using GraphQL
GenericCategory.taxonomy
and GenericTag.taxonomy
(#2666)GenericCustomPost.update
, Root.updateCustomPost
and Root.createCustomPost
(#2663)We have added mutations to create an update custom posts!
For instance, this query updates the title and content for a Custom Post Type "my-portfolio"
:
mutation UpdateCustomPost {
updateCustomPost(input: {
id: 1616
customPostType: "my-portfolio"
title: "Updated title"
contentAs: { html: "Updated content" }
}) {
status
errors {
__typename
...on ErrorPayload {
message
}
}
customPost {
__typename
...on CustomPost {
id
title
content
}
}
}
}
Concerning Custom Post Types (CPT) of your own creation, and which do not require any additional fields over those from a Post, then you can use both createCustomPost
and updateCustomPost
without any fear or restraint. For instance, a MyPortfolio
CPT that simply uses the standard fields title
and content
, and has no extra fields, can be fully managed via these new fields.
Custom post types that are provided by 3rd-party plugins, though, may need to be created (and possibly updated too) by the corresponding plugin only. This is because they may have their custom data (either in wp_postmeta
or in a proprietary table) that needs to be added too, and that Gato GraphQL is unaware of.
To manage these CPTs appropriately, a corresponding integration between that plugin and Gato GraphQL should be created, which would provide the mapping for all the fields for the CPT.
For instance, to work with WooCommerce, we can currently use field Root.updateCustomPost
to translate and update the title and content of an WooCommerce product (i.e. from the Product CPT).
However, we cannot create an WooCommerce product. For that, we must wait until the "WooCommerce for Gato GraphQL" extension is available.
Gato GraphQL extensions with 3rd-party plugins now support alternative filenames from the plugin, where any of them being active will make the extension be enabled.
For instance, the Polylang extension in Gato GraphQL PRO will be enabled if either polylang/polylang.php
or polylang-pro/polylang.php
is active.
Gato GraphQL PRO now has an integration with the Polylang plugin.
The GraphQL schema is provided the fields to retrieve multilingual data.
Root
/QueryRoot
Query the site metadata configured in Polylang.
Field | Description |
---|---|
polylangDefaultLanguage |
Default language on Polylang, or null if there are no languages enabled. |
polylangEnabledLanguages |
Enabled languages on Polylang. |
Running this query:
{
polylangDefaultLanguage
polylangEnabledLanguages
}
...might produce:
{
"data": {
"polylangDefaultLanguage": "en",
"polylangEnabledLanguages": [
"en",
"es",
"fr"
]
}
}
Post
, Page
, PostTag
, PostCategory
and Media
Query the language for the entity, and the IDs for the translations for that entity.
These types implement interface PolylangTranslatable
. (Type Media
does only when media support is enabled, via the Polylang settings.)
Field | Description |
---|---|
polylangLanguage |
Language code of the post or page, or null if no language was assigned (eg: Polylang was installed later on). |
polylangTranslationLanguageIDs |
Nodes for all the translation languages for the entity, as a JSON object with the language code as key and entity ID as value, or null if no language was assigned (eg: Polylang was installed later on). |
Field polylangTranslationLanguageIDs
provides the post/page IDs for all the translations. It accepts field includeSelf
, to indicate if to include the queried entity's ID in the results (it's false
by default).
Running this query:
{
posts {
__typename
id
title
polylangLanguage
polylangTranslationLanguageIDs
polylangTranslationLanguageIDsWithSelf: polylangTranslationLanguageIDs(filter: { includeSelf: true })
categories {
__typename
id
name
polylangLanguage
polylangTranslationLanguageIDs
polylangTranslationLanguageIDsWithSelf: polylangTranslationLanguageIDs(filter: { includeSelf: true })
}
tags {
__typename
id
name
polylangLanguage
polylangTranslationLanguageIDs
polylangTranslationLanguageIDsWithSelf: polylangTranslationLanguageIDs(filter: { includeSelf: true })
}
}
pages {
__typename
id
title
polylangLanguage
polylangTranslationLanguageIDs
polylangTranslationLanguageIDsWithSelf: polylangTranslationLanguageIDs(filter: { includeSelf: true })
}
mediaItems {
__typename
id
title
polylangLanguage
polylangTranslationLanguageIDs
polylangTranslationLanguageIDsWithSelf: polylangTranslationLanguageIDs(filter: { includeSelf: true })
}
}
...might produce:
{
"data": {
"posts": [
{
"__typename": "Post",
"id": 1668,
"title": "Some post translated using Polylang",
"polylangLanguage": "en",
"polylangTranslationLanguageIDs": {
"fr": 1670,
"es": 1672
},
"polylangTranslationLanguageIDsWithSelf": {
"en": 1668,
"fr": 1670,
"es": 1672
},
"categories": [
{
"__typename": "PostCategory",
"id": 61,
"name": "Category for Polylang",
"polylangLanguage": "en",
"polylangTranslationLanguageIDs": {
"fr": 63,
"es": 65
},
"polylangTranslationLanguageIDsWithSelf": {
"en": 61,
"fr": 63,
"es": 65
}
}
],
"tags": [
{
"__typename": "PostTag",
"id": 67,
"name": "Tag for Polylang",
"polylangLanguage": "en",
"polylangTranslationLanguageIDs": {
"fr": 69,
"es": 71
},
"polylangTranslationLanguageIDsWithSelf": {
"en": 67,
"fr": 69,
"es": 71
}
}
]
}
],
"pages": [
{
"__typename": "Page",
"id": 1674,
"title": "Some page translated using Polylang",
"polylangLanguage": "en",
"polylangTranslationLanguageIDs": {
"fr": 1676,
"es": 1678
},
"polylangTranslationLanguageIDsWithSelf": {
"en": 1674,
"fr": 1676,
"es": 1678
}
}
],
"mediaItems": [
{
"__typename": "Media",
"id": 40,
"title": "Media-for-Polylang",
"polylangLanguage": "en",
"polylangTranslationLanguageIDs": {
"fr": 42,
"es": 44
},
"polylangTranslationLanguageIDsWithSelf": {
"en": 40,
"fr": 42,
"es": 44
}
}
]
}
}
GenericCustomPost
, GenericTag
and GenericCategory
These types implement interface PolylangMaybeTranslatable
.
GenericCustomPost
is a type used to represent any custom post installed on the site, such as Portfolio
, Event
, Product
, or other. Similarly, GenericTag
and GenericCategory
are used to represent their taxonomies.
Each of these CPTs and taxonomies can be defined to be translatable on the Polylang settings. Fields polylangLanguage
and polylangTranslationLanguageIDs
will then have the same behavior as for Post
and the others (described above), and also return null
if the entity's CPT or taxonomy is not configured to be translated.
In addition, field polylangIsTranslatable
indicates if the CPT or taxonomy is configured to be translatable.
Field | Description |
---|---|
polylangLanguage |
Language code of the post or page, or null if no language was assigned (eg: Polylang was installed later on), or if the entity is not configured to be translated (via Polylang Settings). |
polylangTranslationLanguageIDs |
Nodes for all the translation languages for the entity, as a JSON object with the language code as key and entity ID as value, or null if no language was assigned (eg: Polylang was installed later on), or if the entity is not configured to be translated (via Polylang Settings). |
polylangIsTranslatable |
Indicate if the entity can be translated. |
Running this query:
{
customPosts(filter: { customPostTypes: ["some-cpt", "another-cpt"] }) {
__typename
...on GenericCustomPost {
id
title
customPostType
polylangIsTranslatable
polylangLanguage
polylangTranslationLanguageIDs
polylangTranslationLanguageIDsWithSelf: polylangTranslationLanguageIDs(filter: { includeSelf: true })
categories(taxonomy: "some-category") {
__typename
...on GenericCategory {
id
name
polylangIsTranslatable
polylangLanguage
polylangTranslationLanguageIDs
polylangTranslationLanguageIDsWithSelf: polylangTranslationLanguageIDs(filter: { includeSelf: true })
}
}
tags(taxonomy: "some-tag") {
__typename
...on GenericTag {
id
name
polylangIsTranslatable
polylangLanguage
polylangTranslationLanguageIDs
polylangTranslationLanguageIDsWithSelf: polylangTranslationLanguageIDs(filter: { includeSelf: true })
}
}
}
}
}
...might produce:
{
"data": {
"customPosts": [
{
"__typename": "GenericCustomPost",
"id": 10,
"title": "Some CPT that has Polylang translation enabled",
"customPostType": "some-cpt",
"polylangIsTranslatable": true,
"polylangLanguage": "en",
"polylangTranslationLanguageIDs": {
"fr": 12,
"es": 14
},
"polylangTranslationLanguageIDsWithSelf": {
"en": 10,
"fr": 12,
"es": 14
},
"categories": [
{
"__typename": "GenericCategory",
"id": 30,
"name": "Some Category for Polylang",
"polylangIsTranslatable": true,
"polylangLanguage": "en",
"polylangTranslationLanguageIDs": {
"fr": 32,
"es": 34
},
"polylangTranslationLanguageIDsWithSelf": {
"en": 30,
"fr": 32,
"es": 34
}
}
],
"tags": [
{
"__typename": "GenericTag",
"id": 50,
"name": "Some Tag for Polylang",
"polylangIsTranslatable": true,
"polylangLanguage": "en",
"polylangTranslationLanguageIDs": {
"fr": 52,
"es": 54
},
"polylangTranslationLanguageIDsWithSelf": {
"en": 50,
"fr": 52,
"es": 54
}
}
]
},
{
"__typename": "GenericCustomPost",
"id": 20,
"title": "Another CPT that does not have Polylang translation enabled",
"customPostType": "another-cpt",
"polylangIsTranslatable": false,
"polylangLanguage": null,
"polylangTranslationLanguageIDs": null,
"polylangTranslationLanguageIDsWithSelf": null,
"categories": [
{
"__typename": "GenericCategory",
"id": 70,
"name": "Category without support for Polylang",
"polylangIsTranslatable": false,
"polylangLanguage": null,
"polylangTranslationLanguageIDs": null,
"polylangTranslationLanguageIDsWithSelf": null
}
],
"tags": [
{
"__typename": "GenericTag",
"id": 72,
"name": "Tag without support for Polylang",
"polylangIsTranslatable": false,
"polylangLanguage": null,
"polylangTranslationLanguageIDs": null,
"polylangTranslationLanguageIDsWithSelf": null
}
]
}
]
}
}
In order to simplify the product offering, Gato GraphQL PRO will now be released as a single plugin, containing all the extensions. As such, installing bundles and single extensions are now both deprecated, and not offered anymore.
Similar to above, predefined persisted queries are now simplified. These now have "[PRO]" prepended to their title, and the list of required extensions (printed on the body of the query) is removed.
Gato GraphQL now supports providing the Schema Configuration to apply when executing a query via an internal GraphQL Server (i.e. directly within the PHP application, not via an endpoint).
This new feature enhances several PRO extensions.
The Internal GraphQL Server extension makes use of this feature. It now accepts a $schemaConfigurationIDOrSlug
parameter on methods executeQuery
and executeQueryInFile
from the GraphQLServer
class, and already extracts the schema configuration used by the persisted query on executePersistedQuery
:
class GraphQLServer {
public static function executeQuery(
string $query,
array $variables = [],
?string $operationName = null,
+ // Accept parameter
+ int|string|null $schemaConfigurationIDOrSlug = null,
): Response {
// ...
}
public static function executeQueryInFile(
string $file,
array $variables = [],
?string $operationName = null,
+ // Accept parameter
+ int|string|null $schemaConfigurationIDOrSlug = null,
): Response {
// ...
}
+ // Schema Configuration is taken from the Persisted Query
public static function executePersistedQuery(
string|int $persistedQueryIDOrSlug,
array $variables = [],
?string $operationName = null,
): Response {
// ...
}
The Automation extension also benefits from this new feature.
It now provides a user interface for its "automator" functionality (directly via the WordPress editor), called Automation Configurator. The automation trigger is any WordPress action hook, and the action is the execution of a GraphQL persisted query.
For instance, when creating a new post, automation rule Add comments block to new post checks if the core/comments
block is present and, if not, it adds it at the bottom of the post:
The newly-added persisted GraphQL query "Insert block in post" allows to inject a block in a post. It identifies the nth block of a given type (wp:paragraph
by default) in a post, and places the provided custom block's HTML content right after it.
Used with the Automation extension, this persisted query can be used to automatically inject mandatory blocks to a newly-published post (eg: a CTA block to promote an ongoing campaign).
These are all the changes for v2.0
.
The new module "Media Mutations" adds fields to the schema that upload media attachments, and query for the logged-in user's media attachments.
createMediaItem
Mutation createMediaItem
allows uploading files to the Media Library. It offers 2 ways to provide the source file:
Running this query:
mutation CreateMediaItems {
fromURL: createMediaItem(input: {
from: {
url: {
source: "https://gatographql.com/assets/GatoGraphQL-logo.png"
}
}
caption: "Gato GraphQL logo"
altText: "This is the Gato GraphQL logo"
}) {
mediaItemID
status
errors {
__typename
...on ErrorPayload {
message
}
}
mediaItem {
...MediaItemData
}
}
directlyByContents: createMediaItem(input: {
from: {
contents: {
body: """
<html>
<body>
Hello world!
</body>
</html>
"""
filename: "hello-world.html"
}
}
title: "Hello world!"
}) {
mediaItemID
status
errors {
__typename
...on ErrorPayload {
message
}
}
mediaItem {
...MediaItemData
}
}
}
fragment MediaItemData on Media {
altText
caption
mimeType
slug
src
title
}
...will produce:
{
"data": {
"fromURL": {
"mediaItemID": 1380,
"status": "SUCCESS",
"errors": null,
"mediaItem": {
"altText": "This is the Gato GraphQL logo",
"caption": "Gato GraphQL logo",
"mimeType": "image/png",
"slug": "gatographql-logo-png",
"src": "https://mysite.com/wp-content/uploads/GatoGraphQL-logo.png",
"title": "GatoGraphQL-logo.png"
}
},
"directlyByContents": {
"mediaItemID": 1381,
"status": "SUCCESS",
"errors": null,
"mediaItem": {
"altText": "",
"caption": "",
"mimeType": "text/html",
"slug": "hello-world-html",
"src": "https://mysite.com/wp-content/uploads/hello-world.html",
"title": "Hello world!"
}
}
}
}
myMediaItemCount
, myMediaItems
and myMediaItem
Logged-in users can now retrieve all of their media files.
Running this query:
query GetMediaItems {
me {
slug
}
myMediaItemCount
myMediaItems(pagination: {
limit: 3
}) {
...MediaItemData
}
myMediaItem(by: { id: 1380 }) {
...MediaItemData
}
}
fragment MediaItemData on Media {
id
mimeType
src
author {
slug
}
}
...will produce:
{
"data": {
"me": {
"slug": "admin"
},
"myMediaItemCount": 2,
"myMediaItems": [
{
"id": 1380,
"mimeType": "image/png",
"src": "https://mysite.com/wp-content/uploads/GatoGraphQL-logo.png",
"author": {
"slug": "admin"
}
},
{
"id": 1365,
"mimeType": "image/png",
"src": "https://mysite.com/wp-content/uploads/browser.png",
"author": {
"slug": "admin"
}
}
],
"myMediaItem": {
"id": 1380,
"mimeType": "image/png",
"src": "https://mysite.com/wp-content/uploads/GatoGraphQL-logo.png",
"author": {
"slug": "admin"
}
}
}
}
A new predefined Persisted query, with title "Generate a post's featured image using AI and optimize it", has been added.
It uses generative AI to produce images for posts without a featured image, choosing from these service providers:
It first checks if a post has a featured image. If it does not, it creates one by calling the generative AI service. We must provide the corresponding API key for the chosen service to use.
As the generative AI images are not optimized for the web, the query can also send the newly-generated image to TinyPNG to compress it. We must provide the API key to use it.
Finally, the query creates a new media item with the image, and sets it as the post's featured image.
_dataMatrixOutputAsCSV
from the Helper Function Collection extensionField _dataMatrixOutputAsCSV
has been added to the documentation for the Helper Function Collection extension.
This field takes a matrix of data, and produces a CSV string. For instance, this query:
csv: _dataMatrixOutputAsCSV(
fields:
["Name", "Surname", "Year"]
data: [
["John", "Smith", 2003],
["Pedro", "Gonzales", 2012],
["Manuel", "Perez", 2008],
["Jose", "Pereyra", 1999],
["Jacinto", "Bloomberg", 1998],
["Jun-E", "Song", 1983],
["Juan David", "Santamaria", 1943],
["Luis Miguel", null, 1966],
]
)
...will produce:
{
"data": {
"csv": "Name,Surname,Year\nJohn,Smith,2003\nPedro,Gonzales,2012\nManuel,Perez,2008\nJose,Pereyra,1999\nJacinto,Bloomberg,1998\nJun-E,Song,1983\nJuan David,Santamaria,1943\nLuis Miguel,,1966\n"
}
}
validateFieldArgValue
method receives extra argument $fieldArgs
It is now possible for a field to validate an input based on the value of another input.
For this, method validateFieldArgValue
from AbstractObjectTypeFieldResolver
now receives a $fieldArgs
param, which contains the values for all inputs provided to the field:
/**
* Validate the constraints for a field argument
* @param array<string,mixed> $fieldArgs
*/
public function validateFieldArgValue(
ObjectTypeResolverInterface $objectTypeResolver,
string $fieldName,
string $fieldArgName,
mixed $fieldArgValue,
AstInterface $astNode,
array $fieldArgs,
ObjectTypeFieldResolutionFeedbackStore $objectTypeFieldResolutionFeedbackStore,
): void;
The new module "Media Mutations" adds fields to the schema that upload media attachments, and query for the logged-in user's media attachments.
createMediaItem
Mutation createMediaItem
allows uploading files to the Media Library. It offers 2 ways to provide the source file:
Running this query:
mutation CreateMediaItems {
fromURL: createMediaItem(input: {
from: {
url: {
source: "https://gatographql.com/assets/GatoGraphQL-logo.png"
}
}
caption: "Gato GraphQL logo"
altText: "This is the Gato GraphQL logo"
}) {
mediaItemID
status
errors {
__typename
...on ErrorPayload {
message
}
}
mediaItem {
...MediaItemData
}
}
directlyByContents: createMediaItem(input: {
from: {
contents: {
body: """
<html>
<body>
Hello world!
</body>
</html>
"""
filename: "hello-world.html"
}
}
title: "Hello world!"
}) {
mediaItemID
status
errors {
__typename
...on ErrorPayload {
message
}
}
mediaItem {
...MediaItemData
}
}
}
fragment MediaItemData on Media {
altText
caption
mimeType
slug
src
title
}
...will produce:
{
"data": {
"fromURL": {
"mediaItemID": 1380,
"status": "SUCCESS",
"errors": null,
"mediaItem": {
"altText": "This is the Gato GraphQL logo",
"caption": "Gato GraphQL logo",
"mimeType": "image/png",
"slug": "gatographql-logo-png",
"src": "https://mysite.com/wp-content/uploads/GatoGraphQL-logo.png",
"title": "GatoGraphQL-logo.png"
}
},
"directlyByContents": {
"mediaItemID": 1381,
"status": "SUCCESS",
"errors": null,
"mediaItem": {
"altText": "",
"caption": "",
"mimeType": "text/html",
"slug": "hello-world-html",
"src": "https://mysite.com/wp-content/uploads/hello-world.html",
"title": "Hello world!"
}
}
}
}
myMediaItemCount
, myMediaItems
and myMediaItem
Logged-in users can now retrieve all of their media files.
Running this query:
query GetMediaItems {
me {
slug
}
myMediaItemCount
myMediaItems(pagination: {
limit: 3
}) {
...MediaItemData
}
myMediaItem(by: { id: 1380 }) {
...MediaItemData
}
}
fragment MediaItemData on Media {
id
mimeType
src
author {
slug
}
}
...will produce:
{
"data": {
"me": {
"slug": "admin"
},
"myMediaItemCount": 2,
"myMediaItems": [
{
"id": 1380,
"mimeType": "image/png",
"src": "https://mysite.com/wp-content/uploads/GatoGraphQL-logo.png",
"author": {
"slug": "admin"
}
},
{
"id": 1365,
"mimeType": "image/png",
"src": "https://mysite.com/wp-content/uploads/browser.png",
"author": {
"slug": "admin"
}
}
],
"myMediaItem": {
"id": 1380,
"mimeType": "image/png",
"src": "https://mysite.com/wp-content/uploads/GatoGraphQL-logo.png",
"author": {
"slug": "admin"
}
}
}
}
A new predefined Persisted query, with title "Generate a post's featured image using AI and optimize it", has been added.
It uses generative AI to produce images for posts without a featured image, choosing from these service providers:
It first checks if a post has a featured image. If it does not, it creates one by calling the generative AI service. We must provide the corresponding API key for the chosen service to use.
As the generative AI images are not optimized for the web, the query can also send the newly-generated image to TinyPNG to compress it. We must provide the API key to use it.
Finally, the query creates a new media item with the image, and sets it as the post's featured image.
_dataMatrixOutputAsCSV
from the Helper Function Collection extensionField _dataMatrixOutputAsCSV
has been added to the documentation for the Helper Function Collection extension.
This field takes a matrix of data, and produces a CSV string. For instance, this query:
csv: _dataMatrixOutputAsCSV(
fields:
["Name", "Surname", "Year"]
data: [
["John", "Smith", 2003],
["Pedro", "Gonzales", 2012],
["Manuel", "Perez", 2008],
["Jose", "Pereyra", 1999],
["Jacinto", "Bloomberg", 1998],
["Jun-E", "Song", 1983],
["Juan David", "Santamaria", 1943],
["Luis Miguel", null, 1966],
]
)
...will produce:
{
"data": {
"csv": "Name,Surname,Year\nJohn,Smith,2003\nPedro,Gonzales,2012\nManuel,Perez,2008\nJose,Pereyra,1999\nJacinto,Bloomberg,1998\nJun-E,Song,1983\nJuan David,Santamaria,1943\nLuis Miguel,,1966\n"
}
}