Hooks
The following chart describes the execution order and available hooks. Each hook can have a start/end phase, where you can control different parts of the execution flow.
Lifetimes
Throughout the implementation of the plugin lifecycle hooks, you’ll notice two named lifetimes:
'req: This lifetime is used for the HTTP request context and is valid for the duration of the request.'exec: This lifetime represents the lifetime of the GraphQL execution. It is used to ensure that the payload and result are valid for the duration of the GraphQL execution.
Plugin Lifecycle Hooks
on_plugin_init
This hook is called exactly once during Router initialization, when the plugin is first loaded.
You can use this hook to access the plugin configuration, create the initial state of the plugin, and register background tasks to be handled by the router.
fn on_plugin_init(payload: OnPluginInitPayload<Self>) -> OnPluginInitResult<Self>Usage Examples
Access the plugin configuration
Use payload.config() to read the plugin’s configuration from the Router YAML. The ? propagates
initialization errors back to the router.
fn on_plugin_init(payload: OnPluginInitPayload<Self>) -> OnPluginInitResult<Self> {
let config = payload.config()?;
payload.initialize_plugin_with_defaults()
}Customize plugin instance
Build the plugin’s initial state from the parsed configuration before the router starts serving requests.
fn on_plugin_init(payload: OnPluginInitPayload<Self>) -> OnPluginInitResult<Self> {
payload.initialize_plugin(Self {
my_thing: payload.config()?.my_config_flag,
})
}Register background tasks
Spawn long-running work tied to the router lifecycle. The CancellationToken is signaled when the
router shuts down.
use hive_router::background_tasks::BackgroundTask;
#[async_trait]
impl BackgroundTask for MyBackgroundTask {
fn id(&self) -> &str {
"my_task"
}
async fn run(&self, token: CancellationToken) {
// Check token.is_cancelled() to handle graceful shutdown
}
}
fn on_plugin_init(payload: OnPluginInitPayload<Self>) -> OnPluginInitResult<Self> {
payload.bg_tasks_manager.register_task(MyBackgroundTask::new());
payload.initialize_plugin_with_defaults()
}Payload API Reference
Plugin Examples
Request Lifecycle Hooks
on_http_request
This hook is called immediately after the router receives an HTTP request. It allows you to inspect or modify the HTTP request before any further processing occurs. At this point, the Router doesn’t know yet if the request is a GraphQL request or not.
fn on_http_request<'req>(
&'req self,
payload: OnHttpRequestHookPayload<'req>
) -> OnHttpRequestHookResult<'req>With this hook, you can add custom authentication and authorization logic based on HTTP headers, method, path, and similar request properties. You can also implement custom restrictions, HTTP validations, short-circuit responses, or serve static assets.
Usage Examples
Reject unauthenticated requests
Check for required HTTP headers in the start phase and short-circuit with an error before any GraphQL processing begins.
fn on_http_request<'req>(
&'req self,
payload: OnHttpRequestHookPayload<'req>,
) -> OnHttpRequestHookResult<'req> {
if payload.router_http_request.headers().get("authorization").is_none() {
return payload.end_with_graphql_error(
GraphQLError::from_message_and_code("Unauthorized", "UNAUTHORIZED"),
StatusCode::UNAUTHORIZED,
);
}
payload.proceed()
}Add a response header
Use on_end with map_response to attach custom headers to every outgoing HTTP response.
fn on_http_request<'req>(
&'req self,
payload: OnHttpRequestHookPayload<'req>,
) -> OnHttpRequestHookResult<'req> {
payload.on_end(|payload| {
payload.map_response(|mut response| {
response.response_mut().headers_mut().insert(
"x-served-by",
"hive-router".parse().unwrap(),
);
response
}).proceed()
})
}Override the HTTP response status
Use map_response in on_end to rewrite the status code on the outgoing response, for example, to
normalize all successful responses to 200 OK.
use hive_router::http::StatusCode;
fn on_http_request<'req>(
&'req self,
payload: OnHttpRequestHookPayload<'req>,
) -> OnHttpRequestHookResult<'req> {
payload.on_end(|end_payload| {
end_payload.map_response(|mut response| {
*response.response_mut().status_mut() = StatusCode::OK;
response
})
})
}Propagate subgraph status codes
Read context data written by a downstream hook (e.g., on_subgraph_http_request) and apply it to
the final HTTP response status.
fn on_http_request<'req>(
&'req self,
payload: OnHttpRequestHookPayload<'req>,
) -> OnHttpRequestHookResult<'req> {
payload.on_end(|payload| {
let ctx = payload.context.get_ref::<PropagateStatusCodeCtx>();
if let Some(ctx) = ctx {
return payload.map_response(|mut response| {
*response.response_mut().status_mut() = ctx.status_code;
response
}).proceed();
}
payload.proceed()
})
}Payload API Reference
Plugin Examples
on_graphql_params
This hook is called after the router has determined that the incoming request is a GraphQL request, and it decides to parse the GraphQL parameters (query, variables, operation name, etc.).
This hook allows you to customize how GraphQL request parameters are detected and extracted. You can also prevent execution of the GraphQL request if certain conditions are met.
async fn on_graphql_params<'exec>(
&'exec self,
payload: OnGraphQLParamsStartHookPayload<'exec>
) -> OnGraphQLParamsStartHookResult<'exec>Usage Examples
Read extracted GraphQL parameters
Use the on_end callback to access the fully extracted GraphQL parameters: query,
operation_name, variables, and extensions are all available here.
async fn on_graphql_params<'exec>(
&'exec self,
payload: OnGraphQLParamsStartHookPayload<'exec>,
) -> OnGraphQLParamsStartHookResult<'exec> {
payload.on_end(|end_payload| {
let maybe_operation_name = &end_payload.graphql_params.operation_name.as_ref();
let maybe_query = &end_payload.graphql_params.query.as_ref();
let variables = &end_payload.graphql_params.variables;
end_payload.proceed()
})
}Modify extracted GraphQL parameters
Mutate the extracted GraphQL parameters inside on_end to override the query, operation name, or
variables before execution continues.
async fn on_graphql_params<'exec>(
&'exec self,
payload: OnGraphQLParamsStartHookPayload<'exec>,
) -> OnGraphQLParamsStartHookResult<'exec> {
payload.on_end(|mut end_payload| {
end_payload.graphql_params.query = Some("query { overridingTheOperation }".to_string());
end_payload.proceed()
})
}Reject anonymous operations
Return a GraphQL error from on_end to short-circuit the request when a condition on the parsed
parameters is not met.
async fn on_graphql_params<'exec>(
&'exec self,
payload: OnGraphQLParamsStartHookPayload<'exec>,
) -> OnGraphQLParamsStartHookResult<'exec> {
payload.on_end(|payload| {
if payload.graphql_params.operation_name.is_none() {
return payload.end_with_graphql_error(
GraphQLError::from_message_and_code(
"Anonymous operations are not allowed",
"ANONYMOUS_OPERATION",
),
StatusCode::BAD_REQUEST,
);
}
payload.proceed()
})
}Resolve a query from extensions (Persisted Queries)
Read from extensions in on_end and rewrite graphql_params.query to implement patterns like
Automatic Persisted Queries (APQ).
async fn on_graphql_params<'exec>(
&'exec self,
payload: OnGraphQLParamsStartHookPayload<'exec>,
) -> OnGraphQLParamsStartHookResult<'exec> {
payload.on_end(|mut payload| {
let hash = payload
.graphql_params
.extensions
.as_ref()
.and_then(|ext| ext.get("persistedQuery"))
.and_then(|pq| pq.as_object())
.and_then(|obj| obj.get(&"sha256Hash"))
.and_then(|h| h.as_str());
if let Some(hash) = hash {
if let Some(cached_query) = self.cache.get(hash) {
payload.graphql_params.query = Some(cached_query.value().to_string());
} else {
return payload.end_with_graphql_error(
GraphQLError::from_message_and_code(
"PersistedQueryNotFound",
"PERSISTED_QUERY_NOT_FOUND",
),
StatusCode::BAD_REQUEST,
);
}
}
payload.proceed()
})
}Payload API Reference
Plugin Examples
on_graphql_parse
This hook is called after the request has been deserialized, and before the router parses the GraphQL request body as specified by the GraphQL-over-HTTP spec.
async fn on_graphql_parse<'exec>(
&'exec self,
payload: OnGraphQLParseStartHookPayload<'exec>,
) -> OnGraphQLParseHookResult<'exec>Usage Examples
Read the raw query string
The start phase has access to the raw GraphQLParams, including the unparsed query string, before
the router parses it into a document.
async fn on_graphql_parse<'exec>(
&'exec self,
payload: OnGraphQLParseStartHookPayload<'exec>,
) -> OnGraphQLParseHookResult<'exec> {
let raw_query = &payload.graphql_params.query;
payload.proceed()
}Access the parsed document (AST)
Use on_end to access the fully parsed Document AST after the router has completed parsing the
operation.
async fn on_graphql_parse<'exec>(
&'exec self,
payload: OnGraphQLParseStartHookPayload<'exec>,
) -> OnGraphQLParseHookResult<'exec> {
payload.on_end(|end_payload| {
let document = &end_payload.document;
end_payload.proceed()
})
}Payload API Reference
on_graphql_validation
This hook is called during the GraphQL validation phase of the Router. At this stage, the GraphQL operation has been extracted and parsed from the incoming request, and validation rules are being applied against the GraphQL schema.
Plugins can use this hook to add custom validation rules, skip validation entirely, or modify the operation prior to validation.
async fn on_graphql_validation<'exec>(
&'exec self,
payload: OnGraphQLValidationStartHookPayload<'exec>,
) -> OnGraphQLValidationStartHookResult<'exec>Usage Examples
Add a custom validation rule
Implement ValidationRule and register it with payload.with_validation_rule() to enforce custom
constraints on every incoming operation.
impl ValidationRule for MyCustomValidationRule {
fn error_code<'a>(&self) -> &'a str {
"MAX_ALIASES_EXCEEDED"
}
fn validate(
&self,
ctx: &mut OperationVisitorContext<'_>,
error_collector: &mut ValidationErrorContext,
) {
// ...
}
}
async fn on_graphql_validation<'exec>(
&'exec self,
payload: OnGraphQLValidationStartHookPayload<'exec>,
) -> OnGraphQLValidationStartHookResult<'exec> {
payload.with_validation_rule(MyCustomValidationRule::new()).proceed()
}Payload API Reference
Plugin Examples
on_query_plan
This hook is invoked during the Federation query planning process. At this stage, the Router has already extracted, parsed, and validated the GraphQL operation and is ready to plan how to execute it across subgraphs.
This hook gives plugins access to the Router’s internal query planning process, allowing them to modify the operation being planned or inspect and modify the resulting plan.
async fn on_query_plan<'exec>(
&'exec self,
payload: OnQueryPlanStartHookPayload<'exec>,
) -> OnQueryPlanStartHookResult<'exec>Usage Examples
Inspect the query plan
Use on_end to access the constructed QueryPlan after the planner has completed. This runs after
operation parsing and validation.
async fn on_query_plan<'exec>(
&'exec self,
payload: OnQueryPlanStartHookPayload<'exec>,
) -> OnQueryPlanStartHookResult<'exec> {
payload.on_end(|end_payload| {
let query_plan = &end_payload.query_plan;
end_payload.proceed()
})
}Reject based on query plan content
Inspect the plan in on_end and short-circuit with an error if the operation exceeds a policy - for
example, too many root fields.
async fn on_query_plan<'exec>(
&'exec self,
payload: OnQueryPlanStartHookPayload<'exec>,
) -> OnQueryPlanStartHookResult<'exec> {
payload.on_end(|end_payload| {
let root_field_count = count_root_fields(&end_payload.query_plan);
if root_field_count > self.max_root_fields {
return end_payload.end_with_graphql_error(
GraphQLError::from_message_and_code(
"Query exceeds the maximum number of root fields",
"TOO_MANY_ROOT_FIELDS",
),
StatusCode::PAYLOAD_TOO_LARGE,
);
}
end_payload.proceed()
})
}Payload API Reference
Plugin Examples
on_execute
This hook is invoked during the Federation query execution process. At this stage, the Router has already extracted, parsed, and validated the GraphQL operation, and a query plan has been constructed and is ready to be executed.
Plugins can use this hook to observe the execution process or implement solutions such as response caching that bypass it entirely.
async fn on_execute<'exec>(
&'exec self,
payload: OnExecuteStartHookPayload<'exec>,
) -> OnExecuteStartHookResult<'exec>Usage Examples
Return a cached response
Bypass the execution pipeline entirely and return a pre-built response - useful for response caching, where a cached result can be served without querying any subgraphs.
async fn on_execute<'exec>(
&'exec self,
payload: OnExecuteStartHookPayload<'exec>,
) -> OnExecuteStartHookResult<'exec> {
if let Some(cached) = self.cache.get(&cache_key) {
return payload.end_with_response(cached);
}
payload.proceed()
}Inspect the GraphQL response
Use on_end to read the executed data and errors before they are serialized and sent to the
client - useful for caching the result or collecting metrics.
async fn on_execute<'exec>(
&'exec self,
payload: OnExecuteStartHookPayload<'exec>,
) -> OnExecuteStartHookResult<'exec> {
payload.on_end(|end_payload| {
let data = &end_payload.data;
let errors = &end_payload.errors;
end_payload.proceed()
})
}Capture the operation for usage reporting
Read operation_for_plan in the start phase to record the query string and operation name for
telemetry or analytics before execution proceeds.
async fn on_execute<'exec>(
&'exec self,
payload: OnExecuteStartHookPayload<'exec>,
) -> OnExecuteStartHookResult<'exec> {
self.reports.lock().await.push(UsageReport {
query: payload.operation_for_plan.to_string(),
operation_name: payload.operation_for_plan.name.clone(),
});
payload.proceed()
}Payload API Reference
Plugin Examples
on_subgraph_execute
This hook is called during the preparation process of every subgraph request, based on the query plan. At this stage, the subgraph name, the execution request to be sent, and other contextual information are available.
Plugins can use this hook to control the planning process of subgraph requests, replace or modify a subgraph response, pass custom data to subgraphs, and handle custom execution transports.
Unlike the on_subgraph_http_request hook, this hook does not yet have the complete HTTP request to
be sent to the subgraph.
async fn on_subgraph_execute<'exec>(
&'exec self,
payload: OnSubgraphExecuteStartHookPayload<'exec>,
) -> OnSubgraphExecuteStartHookResult<'exec>Usage Examples
Add a header to the subgraph request
Mutate payload.execution_request.headers to inject custom headers into every request sent to a
subgraph before it is dispatched.
async fn on_subgraph_execute<'exec>(
&'exec self,
mut payload: OnSubgraphExecuteStartHookPayload<'exec>,
) -> OnSubgraphExecuteStartHookResult<'exec> {
payload.execution_request.headers.insert(
"x-internal-token",
http::HeaderValue::from_static("secret"),
);
payload.on_end(|payload| payload.proceed())
}Filter by subgraph name
Use payload.subgraph_name to apply logic selectively to specific subgraphs.
async fn on_subgraph_execute<'exec>(
&'exec self,
payload: OnSubgraphExecuteStartHookPayload<'exec>,
) -> OnSubgraphExecuteStartHookResult<'exec> {
if payload.subgraph_name == "products" {
tracing::info!("Dispatching request to the products subgraph");
}
payload.on_end(|payload| payload.proceed())
}Count subgraph calls per request
Use context to track how many subgraph calls were made during a single GraphQL request and log the
total in on_end.
async fn on_subgraph_execute<'exec>(
&'exec self,
payload: OnSubgraphExecuteStartHookPayload<'exec>,
) -> OnSubgraphExecuteStartHookResult<'exec> {
payload.on_end(|payload| {
let count = payload.context.get_mut::<SubgraphCallCount>();
if let Some(mut count) = count {
count.value += 1;
}
payload.proceed()
})
}Payload API Reference
Plugin Examples
on_subgraph_http_request
This hook is called during the actual HTTP call made to the subgraph. In this hook, you have access to the full HTTP request that will be sent to the subgraph.
Plugins using this hook can extend or modify the HTTP request, HTTP headers, path and other aspects of communicating with the subgraph.
You can also use this hook to capture or forward data returned from subgraphs.
async fn on_subgraph_http_request<'exec>(
&'exec self,
payload: OnSubgraphHttpRequestStartHookPayload<'exec>,
) -> OnSubgraphHttpRequestStartHookResult<'exec>Usage Examples
Track the subgraph response status
Use on_end to capture the HTTP response status from each subgraph call and store it in context for
use in a later hook (e.g., on_http_request).
async fn on_subgraph_http_request<'exec>(
&'exec self,
payload: OnSubgraphHttpRequestHookPayload<'exec>,
) -> OnSubgraphHttpRequestHookResult<'exec> {
payload.on_end(|payload| {
let status = payload.response.status;
if self.watched_codes.contains(&status) {
payload.context.insert(SubgraphStatus { code: status });
}
payload.proceed()
})
}Replace the subgraph HTTP request
Bypass the default HTTP transport entirely and send a custom request.
async fn on_subgraph_http_request<'exec>(
&'exec self,
payload: OnSubgraphHttpRequestHookPayload<'exec>,
) -> OnSubgraphHttpRequestHookResult<'exec> {
let response = self.client
.post(payload.endpoint.to_string())
.body(payload.body.clone())
.send()
.await
.unwrap();
payload.end_with_response(SubgraphHttpResponse {
status: response.status(),
headers: response.headers().clone().into(),
body: response.bytes().await.unwrap(),
})
}Payload API Reference
Plugin Examples
on_graphql_error
This hook is called whenever a GraphQL error is about to be sent to the client during plan execution. Use it to inspect or modify the error before it reaches the client.
You can use this hook to implement custom error handling logic, such as masking certain error details, adding additional information to the error response, or transforming the error into a different format.
fn on_graphql_error(
&self,
payload: OnGraphQLErrorHookPayload
) -> OnGraphQLErrorHookResult;Usage Examples
Collect errors for monitoring
Intercept errors before they reach the client and record them for analytics, alerting, or structured logging.
fn on_graphql_error(&self, mut payload: OnGraphQLErrorHookPayload) -> OnGraphQLErrorHookResult {
payload.collected_errors.push(payload.error.message.clone());
payload.proceed()
}Mask sensitive error details
Sanitize error output by replacing the message and clearing fields like path before the error is
sent to the client.
fn on_graphql_error(&self, mut payload: OnGraphQLErrorHookPayload) -> OnGraphQLErrorHookResult {
payload.error.message = "An internal error occurred".to_string();
payload.error.path = None;
payload.proceed()
}Remap error codes and HTTP status
Map known error codes to different codes or HTTP status codes based on plugin configuration - useful for normalizing errors from subgraphs.
fn on_graphql_error(&self, mut payload: OnGraphQLErrorHookPayload) -> OnGraphQLErrorHookResult {
if let Some(code) = &payload.error.extensions.code {
if let Some(mapping) = self.config.get(code) {
if let Some(new_code) = &mapping.code {
payload.error.extensions.code = Some(new_code.clone());
}
if let Some(status) = mapping.status_code {
if let Ok(status_code) = StatusCode::from_u16(status) {
payload.status_code = status_code;
}
}
}
}
payload.proceed()
}Payload API Reference
Plugin Examples
Router Lifecycle
on_shutdown
This hook is called when the router is shutting down.
Plugins can use this hook to implement cleanup and flush logic.
async fn on_shutdown<'exec>(&'exec self)Usage Examples
Flush buffered data before exit
Collect and send any in-memory data (e.g., usage reports or metrics) to an external service before the router process exits.
async fn on_shutdown<'exec>(&'exec self) {
let reports = self.reports.lock().await;
let _ = reqwest::Client::new()
.post(&self.endpoint)
.json(reports.as_slice())
.send()
.await;
}Plugin Examples
on_supergraph_load
This hook is called whenever the supergraph is loaded or reloaded. This occurs at startup and any time the supergraph is reloaded.
Plugins can use this hook to clear or reset caches and state that depends on the schema structure.
This hook is guaranteed to be called at least once for a healthy router instance during startup. If the Router is configured to poll for schema updates, it will be called each time the schema is reloaded.
fn on_supergraph_reload<'exec>(
&'exec self,
start_payload: OnSupergraphLoadStartHookPayload,
) -> OnSupergraphLoadStartHookResult<'exec>Usage Examples
Scan the schema for directives
Inspect the incoming new_ast to extract schema metadata, for example, collecting all types
annotated with a custom directive and caching them for use in later hooks.
fn on_supergraph_reload<'exec>(
&'exec self,
start_payload: OnSupergraphLoadStartHookPayload,
) -> OnSupergraphLoadStartHookResult<'exec> {
let tagged_types: Vec<String> = start_payload.new_ast
.definitions
.iter()
.filter(|def| has_directive(def, "myDirective"))
.filter_map(|def| type_name(def))
.collect();
self.tagged_types.store(Arc::new(tagged_types));
start_payload.proceed()
}Invalidate caches on schema reload
Clear any schema-dependent state whenever the supergraph is reloaded, so stale entries don’t leak across schema versions.
fn on_supergraph_reload<'exec>(
&'exec self,
start_payload: OnSupergraphLoadStartHookPayload,
) -> OnSupergraphLoadStartHookResult<'exec> {
self.cache.clear();
println!("Supergraph reloaded! cache invalidated!");
start_payload.proceed()
}