Build your First WPGraphQL Extension
Video Tutorial
The following video walks through how to build a WPGraphQL Extension plugin. After watching the video, viewers should be able to create a WordPress plugin that extends the WPGraphQL Schema with new fields.
If you prefer learning these concepts by reading, scroll down to the written tutorial below.
Prerequisites
Before starting this tutorial, you should have:
- WordPress (recommended: 6.4+)
- PHP (recommended: 8.0+)
- WPGraphQL (latest version)
- A local WordPress development environment
- Basic understanding of WordPress plugin development
What We’ll Build
In this tutorial, we’ll learn how to:
- Create a basic WPGraphQL extension plugin
- Register custom types and fields to the GraphQL Schema
- Work with WordPress data in your GraphQL API
- Follow GraphQL naming conventions and best practices
We’ll start with basic concepts using an abstract example, then move on to practical real-world implementations.
Getting Started
Setting Up Your Plugin
- Navigate to your WordPress site’s
wp-content/plugins
directory - Create a new folder named
my-first-wpgraphql-extension
- Create a PHP file with the same name:
my-first-wpgraphql-extension.php
Add this code to your PHP file:
<?php
/
Plugin Name: My First WPGraphQL Extension
Description: A tutorial plugin demonstrating WPGraphQL extension development
Version: 1.0.0
Author: Your Name
/
// Prevent direct access to this file
if ( ! defined( 'ABSPATH' ) ) {
die( 'Unauthorized access!' );
}
After adding this code, activate the plugin in your WordPress admin panel. You should see it listed in the plugins page.
Understanding GraphQL Naming Conventions
Before we start extending the schema, it’s important to understand GraphQL naming conventions:
- Type use PascalCase: Post, Page, CustomType, BookType
- Fields use camelCase: title, date, bookTitle, customField
- Enum values use ALL_CAPS: POST_STATUS
- Descriptions should be clear, complete sentences
These conventions help maintain consistency and clarity in your GraphQL API.
Your First Schema Extension
Let’s start with a basic example of extending the schema. We’ll create:
We’ll create:
- A custom type
- A field that returns that type
<?php
add_action( 'graphql_register_types', function() {
// Register a custom type
register_graphql_object_type( 'CustomType', [
'description' => ( 'An example custom type demonstrating schema extension', 'my-graphql-extension' ),
'fields' => [
'message' => [
'type' => 'String',
'description' => ( 'A simple message field', 'my-graphql-extension' ),
],
'number' => [
'type' => 'Int',
'description' => ( 'A simple number field', 'my-graphql-extension' ),
],
],
]);
// Register a field that returns our custom type
register_graphql_field( 'RootQuery', 'example', [
'type' => 'CustomType',
'description' => ( 'An example field returning our custom type', 'my-graphql-extension' ),
'resolve' => function() {
return [
'message' => 'Hello from your first WPGraphQL extension!',
'number' => 42,
];
}
]);
});
Testing Your First Extension
After adding this code, you can test your extension using GraphiQL in the WordPress admin panel. Navigate to GraphQL → GraphiQL IDE and try this query:
{
example {
message
number
}
}
You should see a response like:
{
"data": {
"example": {
"message": "Hello from your first WPGraphQL extension!",
"number": 42
}
}
}
Real-World Example: Working with WordPress Options
Now let’s look at a practical example. While WPGraphQL automatically exposes some WordPress settings when they’re registered with show_in_graphql => tru
e`, you might need to manually expose options or add custom resolution logic.
Here’s an example of exposing a custom option:
php
add_action( 'graphql_register_types', function() {
register_graphql_field( 'RootQuery', 'siteCustomSetting', [
'type' => 'String',
'description' => ( 'A custom site setting', 'my-graphql-extension' ),
'resolve' => function() {
// Basic capability check
if ( ! current_user_can( 'read' ) ) {
throw new \GraphQL\Error\UserError( 'You do not have permission to access this setting' );
}
return get_option( 'my_custom_setting', 'default value' );
}
]);
});
After adding this code, you can test your extension using GraphiQL in the WordPress admin panel. Navigate to GraphQL → GraphiQL IDE and try this query:
{
siteCustomSetting
}
You should see a response like:
{
"data": {
"siteCustomSetting": "default value"
}
}
Advanced Example: Custom Settings Type
Let’s create a more complex example with multiple fields and proper error handling:
add_action( 'graphql_register_types', function() {
// Register a custom settings type
register_graphql_object_type( 'CustomSiteSettings', [
'description' => ( 'Custom site settings', 'my-graphql-extension' ),
'fields' => [
'mainColor' => [
'type' => 'String',
'description' => ( 'The main color theme setting', 'my-graphql-extension' ),
],
'featuredCategories' => [
'type' => ['list_of' => 'String'],
'description' => ( 'List of featured category slugs', 'my-graphql-extension' ),
],
'lastUpdated' => [
'type' => 'String',
'description' => ( 'When the settings were last updated', 'my-graphql-extension' ),
],
],
]);
// Register a field that returns our settings type
register_graphql_field( 'RootQuery', 'customSiteSettings', [
'type' => 'CustomSiteSettings',
'description' => ( 'Custom site settings configuration', 'my-graphql-extension' ),
'resolve' => function() {
// Check permissions
if ( ! current_user_can( 'manage_options' ) ) {
throw new \GraphQL\Error\UserError(( 'You do not have permission to access site settings', 'my-graphql-extension' ));
}
try {
return [
'mainColor' => get_option( 'site_main_color', '#ffffff' ),
'featuredCategories' => get_option( 'featured_categories', [] ),
'lastUpdated' => get_option( 'settings_updated_at' ),
];
} catch ( \Exception $e ) {
throw new \GraphQL\Error\UserError(( 'Error fetching site settings', 'my-graphql-extension' ));
}
}
]);
});
You can query these settings with:
{
siteSettings {
mainColor
featuredCategories
lastUpdated
}
}
Best Practices
Naming and Documentation
- Type Names:
- Use PascalCase:
ProductType
,OrderStatus
- Be specific, but concise
- Avoid WordPress-specific terminology when possible (e.g. use Product instead of WooCommerceProduct)
- Field Names:
- Use camelCase:
productPrice
,orderTotal
- Start with a verb for actions:
createPost
,updateUser
- Use clear, descriptive names
- Boolean fields should start with is, has, should, can: (e.g.
isPublished
,hasChildren
,shouldUpdate
,canUpdate
)
- Descriptions:
- Write complete sentences
- Explain the purpose, not just what it is
- Include important details about permissions or side effects (e.g.
'description' => __( 'The product\'s base price, excluding taxes and discounts.', 'my-textdomain' )
)
Security Considerations
Access Control
register_graphql_field( 'RootQuery', 'sensitiveData', [
'type' => 'String',
'resolve' => function() {
// Always check capabilities
if ( ! current_user_can( 'manage_options' ) ) {
throw new \GraphQL\Error\UserError( 'Unauthorized access' );
}
return get_option( 'sensitive_data' );
}
]);
Data Sanitization
'resolve' => function( $source, array $args ) {
// Always sanitize input
$safe_input = sanitize_text_field( $args['input'] );
return wp_kses_post( $safe_input );
}
Error Handling
Use Try-Catch Blocks
'resolve' => function() {
try {
// Your resolution logic
return get_option( 'some_option' );
} catch ( \Exception $e ) {
throw new \GraphQL\Error\UserError(
__( 'Failed to retrieve data', 'my-textdomain' )
);
}
}
Meaningful Error Messages
- Be specific but don’t expose sensitive information
- Translate all error messages
- Include actionable information when possible
Troubleshooting Common Issues
If you run into issues, common debugging tips can be found in the Debugging guide.
Next Steps
Explore More Features
Join the Community
Remember to always test your extensions thoroughly and keep security in mind when exposing data through GraphQL.