Archives: Recipes

  • Page Siblings Connection

    Below is a connection that adds the ability to query page siblings (i.e. pages that share the same parent)

    add_action( 'graphql_register_types', function() {
    
    	register_graphql_connection( [
    		'fromType' => 'Page',
    		'toType' => 'Page',
    		'connectionTypeName' => 'PageSiblings',
    		'fromFieldName' => 'siblings',
    		'resolve' => function( $page, $args, $context, $info ) {
    			$parent = $page->parentDatabaseId ?? null;
    
    			if ( ! $parent ) {
    				return null;
    			}
    
    			$resolver = new \WPGraphQL\Data\Connection\PostObjectConnectionResolver( $page, $args, $context, $info );
    			$resolver->set_query_arg( 'post_parent', $parent );
    			$resolver->set_query_arg( 'post__not_in', $page->databaseId );
    			return $resolver->get_connection();
    		}
    	]);
    
    } );
  • Fix pagination conflict with the “Advanced Taxonomy Terms Order” plugin

    When using the Advanced Taxonomy Terms Order plugin along with WPGraphQL, you might experience some issues with paginated queries.

    This snippet should help correct the conflict:

    add_action( 'pre_get_terms', function() {
    
    	// if WPGraphQL isn't active or CPTUI isn't active, carry on and bail early
    	if ( ! function_exists( 'is_graphql_request' ) || ! is_graphql_request() || ! function_exists( 'TO_activated' ) ) {
    		return;
    	}
    
            // Remove the terms_clauses filter from the Term Order plugin
            // as this filter runs after WPGraphQL has determined how
            // to resolve paginated requests with order arguments applied
    	remove_filter( 'terms_clauses', 'TO_apply_order_filter', 2 );
    
            // Set the query order and orderby to term_order
            // to match the intent of the Term Order plugin
    	add_filter( 'graphql_term_object_connection_query_args', function( $query_args, $source, $input_args, $context, $info ) {
    
    		$query_args['orderby'] = ! empty( $input_args['orderby'] ) ? $input_args['orderby'] : 'term_order';
    		$query_args['order'] = ! empty( $input_args['order'] ) ? $input_args['order'] : ( ! empty( $input_args['last'] ) ? 'DESC' : 'ASC' );
    
    		return $query_args;
    
    	}, 10, 5 );
    
    });

    What this snippet does:

    First, this hooks into pre_get_terms which fires when WP_Term_Query is executing to get Terms out of the database.

    This checks to see if the request is a GraphQL request and whether Term Order plugin is active. If these conditions are not met, nothing happens.

    If these conditions are met, we carry on.

    Next, we remove the “terms_clauses” filter from the Terms Order plugin, as it modifies the SQL statement directly, after WPGraphQL has determined how to map order args to SQL.

    Then, we add our own filter back to WPGraphQLs Term Object Connection query args and set the order and orderby arguments here. This ensures that the order will be properly mapped to WPGraphQLs cursor pagination logic.

  • Fix pagination conflict with the “Post Type Order” plugin

    When using the Post Type Order plugin along with WPGraphQL, you might experience some issues with paginated queries.

    This snippet should help correct the conflict:

    add_action( 'pre_get_posts', function () {
    
    	// access the custom post type order class
    	global $CPTO;
    
    	// if WPGraphQL isn't active or CPTUI isn't active, carry on and bail early
    	if ( ! function_exists( 'is_graphql_request' ) || ! is_graphql_request() || ! $CPTO ) {
    		return;
    	}
    
    	// Remove the Post Type Order plugin's "posts_orderby" filter for WPGraphQL requests
    	// This filter hooks in too late and modifies the SQL directly so WPGraphQL
    	// can't properly map the orderby args to generate the SQL for proper pagination
    	remove_filter( 'posts_orderby', [ $CPTO, 'posts_orderby' ], 99 );
    
    	// Add a filter
    	add_filter( 'graphql_post_object_connection_query_args', function ( $args, $source, $input_args, $context, $info ) {
    
    		$orderby = [];
    
    		// If the connection has explicit orderby arguments set,
    		// use them
    		if ( ! empty( $input_args['where']['orderby'] ) ) {
    			return $args;
    		}
    
    		// Else use any orderby args set on the WP_Query
    		if ( isset( $args['orderby'] ) ) {
    			$orderby = [];
    
    			if ( is_string( $args['orderby'] ) ) {
    				$orderby[] = $args['orderby'];
    			} else {
    				$orderby = $args['orderby'];
    			}
    		}
    
    		$orderby['menu_order'] = ! empty( $input_args['last'] ) ? 'DESC' : 'ASC';
    		$args['orderby']       = $orderby;
    
    		return $args;
    	}, 10, 5 );
    
    });

    What this snippet does:

    First, this hooks into pre_get_posts which fires when WP_Query is executing.

    This then checks to see if the request is a GraphQL request and whether Post Types Order plugin class is available as a global. If these conditions are not met, nothing happens. If these conditions are met, we carry on.

    Next, we remove the “posts_orderby” filter from the Post Types Order plugin, as it was overriding WPGraphQL’s ordering which is needed for cursor pagination to work properly.

    Then, we add our own filter back to WPGraphQLs Post Object Connection query args and set the orderby to be menu_order => 'ASC'

  • Deprecating a field in the Schema

    Sometimes it can be helpful to deprecate a field in the Schema without removing it altogether.

    This snippet shows how to deprecate the `Post.excerpt` field.

    You can use this technique to deprecate other fields.

    // Filter the Object Fields
    add_filter( 'graphql_object_fields', function( $fields, $type_name, $wp_object_type, $type_registry ) {
    
            // If the Object Type is not the "Post" Type
            // return the fields unaltered
    	if ( 'Post' !== $type_name ) {
    		return $fields;
    	}
    
            // If the excerpt field doesn't exist 
            // (removed by another plugin, for example)
            // return the fields unaltered
    	if ( ! isset( $fields['excerpt'] ) ) {
    		return  $fields;
    	}
    
            // Add a deprecation reason to the excerpt field
    	$fields['excerpt']['deprecationReason'] = __( 'Just showing how to deprecate an existing field', 'your-textdomain' );
    	
            // return the modified
            return $fields;
    
    }, 10, 4 );

    After using this snippet, we can verify in the WPGraphQL Schema Docs that the field is indeed deprecated:

    Screenshot of the excerpt field showing deprecated in the GraphiQL IDE Schema Docs
  • Allow login mutation to be public when the endpoint is fully restricted

    If you’ve configured your WPGraphQL settings to “Limit the execution of GraphQL operations to authenticated requests”, this will block all root operations unless the user making the request is already authenticated.

    If you’re using a GraphQL mutation to authenticate, such as the one provided by WPGraphQL JWT Authentication, you might want to allow the login mutation to still be executable by public users, even if the rest of the API is restricted.

    This snippet allows you to “allow” the login mutation when all other root operations are restricted.

    add_filter( 
      'graphql_require_authentication_allowed_fields', 
      function( $allowed ) {
    	$allowed[] = 'login';
    	return $allowed;
    }, 10, 1 );
  • Remove Extensions from GraphQL Response

    This snippet removes the “extensions” from the GraphQL response:

    add_filter( 'graphql_request_results', function( $response ) {
    	// If it's an ExecutionResult object, we need to handle it differently
    	if ( $response instanceof \GraphQL\Executor\ExecutionResult ) {
    		// Convert to array and remove extensions if they exist
    		$array = $response->toArray();
    		if ( isset( $array['extensions'] ) ) {
    			unset( $array['extensions'] );
    		}
    		return $array;
    	}
    
    	// Handle array responses
    	if ( is_array( $response ) && isset( $response['extensions'] ) ) {
    		unset( $response['extensions'] );
    	}
    
    	// Handle object responses
    	if ( is_object( $response ) && isset( $response->extensions ) ) {
    		unset( $response->extensions );
    	}
    
    	return $response;
    }, 99, 1 );

    Before

    After

  • Query the Homepage

    In WordPress, the homepage can be a Page or an archive of posts of the Post post_type (which is represented by WPGraphQL as a “ContentType” node).

    This query allows you to query the homepage, and specify what data you want in response if the homepage is a page, or if the homepage is a ContentType node.

    {
      nodeByUri(uri: "/") {
        __typename
        ... on ContentType {
          id
          name
        }
        ... on Page {
          id
          title
        }
      }
    }

    If the homepage were set to a Page, like so:

    Then a Page would be returned in the Query Results, like so:

    But if the homepage were set to be the Posts page:

    Then the results would return a ContentType node, like so:

  • Make all Users Public

    The following snippets allow for Users with no published content to be shown in public (non-authenticated) WPGraphQL query results.

    For a more detailed write-up, read the blog post: Allowing WPGraphQL to show unpublished authors in User Queries

    add_filter( 'graphql_connection_query_args', function( $query_args, $connection_resolver ) {
    
      if ( $connection_resolver instanceof \WPGraphQL\Data\Connection\UserConnectionResolver ) {
        unset( $query_args['has_published_posts'] );
      }
    
      return $query_args;
    
    }, 10, 2 );
    add_filter( 'graphql_object_visibility', function( $visibility, $model_name, $data, $owner, $current_user ) {
    
      // only apply our adjustments to the UserObject Model
      if ( 'UserObject' === $model_name ) {
        $visibility = 'public';
      }
    
      return $visibility;
    
    }, 10, 5 );
  • Register a basic Mutation

    This snippet shows how to register a basic GraphQL Mutation with a single input field, a single output field, and the input is simply returned as the value for the output.

    add_action( 'graphql_register_types', function() {
    
    	register_graphql_mutation( 'testMutation', [
    		'inputFields' => [
    			'phoneNumber' => [
    				'type' => [ 'non_null' => 'String' ],
    			],
    		],
    		'outputFields' => [
    			'phoneNumber' => [
    				'type' => 'String',
    			],
    		],
    		'mutateAndGetPayload' => function( $input ) {
    
    			return [
    				'phoneNumber' => $input['phoneNumber'] ?? null,
    			];
    
    		}
    	]);
    
    } );
  • Showing Post Type labels in public queries

    WPGraphQL respects WordPress core access control rights. This means that data that is only available to authenticated users in the WordPress admin is only available to authenticated users making GraphQL requests.

    Sometimes, you want to expose fields that are restricted by default.

    Take the Post Type Label field, for example.

    Querying for the label of a Post Type as a public user returns a null value by default:

    Screenshot of a query for ContentTypes and their label, showing null value for the label.

    With the following snippet, you can expose the label field to public users:

    add_filter( 'graphql_allowed_fields_on_restricted_type', function( $allowed_restricted_fields, $model_name, $data, $visibility, $owner, $current_user ) {
    
    	if ( 'PostTypeObject' === $model_name ) {
    		$allowed_restricted_fields[] = 'label';
    	}
    
    	return $allowed_restricted_fields;
    
    }, 10, 6 );

    And below we can see the same query, showing the value of the labels to public users.

    Screenshot of a query for ContentTypes and their label, showing the label's value for the label.