Skip to content

Bug: graphql_root_value filter runs before request params are available, limiting access to query context #3405

@jasonbahl

Description

@jasonbahl

Description

There is a timing issue in the Request class where the graphql_root_value filter is applied before the request parameters ($this->params) are parsed and available. This prevents developers from accessing valuable request context (such as the query string, variables, operation name, etc.) when filtering the root value.

Steps to reproduce

Add the following snippet.

// In a plugin or theme functions.php
add_filter( 'graphql_root_value', function( $root_value, $request ) {
    // This will be null because params aren't set yet
    var_dump( $request->params ); // null
    
    // Cannot access query information
    if ( $request->params instanceof \GraphQL\Server\OperationParams ) {
        $query = $request->params->query;
        $variables = $request->params->variables;
        // This code never runs because params is null
    }
    
    return $root_value;
}, 10, 2 );

Execute any GraphQL query - the filter will run without access to params

Expected vs Actual Behavior

Expected Behavior:

When the graphql_root_value filter runs, the $request parameter should have access to parsed request parameters ($request->params) containing:

  • The GraphQL query string
  • Query variables
  • Operation name
  • Query ID
  • Other request context

This would allow developers to make informed decisions about the root value based on the specific query being executed.

Actual Behavior:

The graphql_root_value filter runs during the Request constructor before request parameters are parsed, so $request->params is null. This prevents access to any query-specific context when filtering the root value.

Root Cause

In the Request constructor, get_root_value() is called immediately:

public function __construct( array $data = [] ) {
    // ... initialization ...
    $this->root_value = $this->get_root_value(); // ❌ Called too early
    // ...
}

However, $this->params is only set later in the execution methods:

public function execute() {
    $this->params = $helper->parseRequestParams( 'POST', $this->data, [] ); // ✅ Set here
    // ...
}

Impact

This timing issue prevents developers from implementing:

  • Query-specific root value logic
  • Context-aware authentication/authorization
  • Variable-based root value selection
  • Query pattern analysis during root value determination

Proposed Solution

The proposed fix involves moving the root value determination to execution time when params are available:

  1. Remove $this->root_value = $this->get_root_value(); from the constructor
  2. Call get_root_value() during execution when params are set:
$result = GraphQL::executeQuery(
    $this->schema,
    $query,
    $this->get_root_value(), // ✅ Now params are available
    $this->app_context,
    // ...
);
  1. Update server config accordingly:
if ( ! empty( $this->get_root_value() ) ) {
    $config->setRootValue( $this->get_root_value() );
}

Benefits

  • graphql_root_value filter gains access to request context
  • ✅ Enables sophisticated query-aware root value logic
  • ✅ Maintains full backward compatibility
  • ✅ No performance impact (lazy evaluation)
  • ✅ Consistent with other filters that run during execution

This change would significantly improve the flexibility and usefulness of the graphql_root_value filter while maintaining complete backward compatibility.

Additional context

I'm working on some experiments with WPGraphQL Subscriptions and the implementation makes use of the graphql_root_value filter to pass context from a sidecar server (that handles subscription management and realtime transport) back to WPGraphQL for access within subscription resolvers.

WPGraphQL Version

2.3.3

WordPress Version

6.8.2

PHP Version

8.2.23

Additional environment details

No response

Please confirm that you have searched existing issues in the repo.

  • Yes

Please confirm that you have disabled ALL plugins except for WPGraphQL.

  • Yes
  • My issue is with compatibility with a specific WordPress plugin, and I have listed all my installed plugins (and version info) above.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

Status

✅ Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions