All skills
apollographql avatar

/apollo-router-plugin-creator

@7be6130 official
by Apollo GraphQLapollographql/skills115 stars
13

Guide for writing Apollo Router native Rust plugins. Use this skill when: (1) users want to create a new router plugin, (2) users want to add service hooks (router_service, supergraph_service, execution_service, subgraph_service), (3) users want to modify an existing router plugin, (4) users need to understand router plugin patterns or the request lifecycle. (5) triggers on requests like "create a new plugin", "add a router plugin", "modify the X plugin", or "add subgraph_service hook".

Use this Skill: https://skilld.dev/gh/apollographql/skills/apollo-router-plugin-creator

This session only. Nothing lands on disk.

referencesservice-hooks.md

≈1.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Service Hooks Reference

Table of Contents


router_service

Intercepts at HTTP level before GraphQL parsing. Access raw HTTP request/response.

fn router_service(&self, service: router::BoxService) -> router::BoxService {
  if !self.configuration.enabled {
    return service;
  }

  ServiceBuilder::new()
    .map_request(|mut request: router::Request| {
      // Access/modify headers
      let auth = request.router_request.headers().get("authorization");
      request.router_request.headers_mut().insert("x-custom", "value".parse().unwrap());

      // Add to context for later hooks
      request.context.insert("my_key", value).unwrap();

      request
    })
    .map_response(|mut response: router::Response| {
      // Modify response headers
      response.response.headers_mut().insert("cache-control", "no-store".parse().unwrap());

      response
    })
    .service(service)
    .boxed()
}

Request types:

  • request.router_request - The HTTP request
  • request.router_request.headers() - Request headers
  • request.router_request.headers_mut() - Mutable headers
  • request.context - Shared context

supergraph_service

Intercepts at GraphQL level after parsing. Access query, variables, extensions.

fn supergraph_service(&self, service: supergraph::BoxService) -> supergraph::BoxService {
  if !self.configuration.enabled {
    return service;
  }

  ServiceBuilder::new()
    .checkpoint(move |request: supergraph::Request| {
      // Check introspection
      if let Ok(Some(true)) = request.context.get::<_, bool>(IS_INTROSPECTION_QUERY) {
        return Ok(ControlFlow::Break(
          supergraph::Response::error_builder()
            .error(Error::builder().message("Forbidden").extension_code("FORBIDDEN").build())
            .status_code(StatusCode::OK)
            .context(request.context)
            .build()
            .unwrap()
        ));
      }

      Ok(ControlFlow::Continue(request))
    })
    .map_response(|response: supergraph::Response| {
      // Transform response stream
      response.map_stream(|mut graphql_response| {
        graphql_response.extensions.insert("custom", json!({"key": "value"}).into());
        graphql_response
      })
    })
    .service(service)
    .boxed()
}

Request types:

  • request.supergraph_request - The GraphQL request
  • request.supergraph_request.headers() - Request headers
  • request.query_plan - The query plan (in execution_service)
  • request.context - Shared context

Response transformation:

// For streaming responses
response.map_stream(|mut graphql_response| {
  // Modify each response chunk
  graphql_response
})

execution_service

Intercepts after query planning, before subgraph calls. Access to the query plan.

fn execution_service(&self, service: execution::BoxService) -> execution::BoxService {
  if !self.configuration.enabled {
    return service;
  }

  ServiceBuilder::new()
    .checkpoint(move |request: execution::Request| {
      // Access query plan
      if request.query_plan.contains_mutations() {
        return Ok(ControlFlow::Break(
          execution::Response::error_builder()
            .error(
              Error::builder()
                .message("Mutations are forbidden")
                .extension_code("MUTATION_FORBIDDEN")
                .build()
            )
            .status_code(StatusCode::BAD_REQUEST)
            .context(request.context)
            .build()
            .unwrap()
        ));
      }

      Ok(ControlFlow::Continue(request))
    })
    .service(service)
    .boxed()
}

Request types:

  • request.query_plan - The generated query plan
  • request.query_plan.contains_mutations() - Check if plan has mutations
  • request.context - Shared context

Use cases:

  • Forbid mutations
  • Query cost/complexity limits
  • Query plan inspection and logging

subgraph_service

Intercepts per-subgraph calls. Receives subgraph name as parameter.

fn subgraph_service(&self, name: &str, service: subgraph::BoxService) -> subgraph::BoxService {
  if !self.configuration.enabled {
    return service;
  }

  let subgraph_name = name.to_string();
  let config = self.configuration.clone();

  ServiceBuilder::new()
    .map_request(move |mut request: subgraph::Request| {
      // Add headers to subgraph request
      request.subgraph_request.headers_mut().insert("x-subgraph", subgraph_name.parse().unwrap());

      request
    })
    .map_response(move |mut response: subgraph::Response| {
      // Read subgraph response extensions
      let extensions = &response.response.body().extensions;

      // Collect headers from subgraph
      let cookies = response.response.headers().get_all("set-cookie");

      response
    })
    .service(service)
    .boxed()
}

Request types:

  • request.subgraph_request - The HTTP request to subgraph
  • response.response.body() - The GraphQL response body
  • response.response.body().extensions - Response extensions

Per-subgraph configuration:

fn subgraph_service(&self, name: &str, service: subgraph::BoxService) -> subgraph::BoxService {
  // Check if this subgraph has specific config
  let subgraph_config = self.configuration.subgraphs.get(name);

  if let Some(config) = subgraph_config {
    // Apply subgraph-specific logic
  }

  service
}

Async Operations

Use checkpoint_async for async operations:

fn supergraph_service(&self, service: supergraph::BoxService) -> supergraph::BoxService {
  let client = self.http_client.clone();

  ServiceBuilder::new()
    .checkpoint_async(move |request: supergraph::Request| {
      let client = client.clone();
      async move {
        // Async validation
        let result = client.validate(&request).await;

        if result.is_err() {
          return Ok(ControlFlow::Break(error_response()));
        }

        Ok(ControlFlow::Continue(request))
      }
    })
    .service(service)
    .boxed()
}

Use buffered() when cloning the service:

ServiceBuilder::new()
  .buffered()
  .checkpoint_async(...)
  .service(service)
  .boxed()

Error Responses

GraphQL Error (200 OK with errors)

supergraph::Response::error_builder()
  .error(
    Error::builder()
      .message("Error message")
      .extension_code("ERROR_CODE")
      .build()
  )
  .status_code(StatusCode::OK)
  .context(request.context)
  .build()
  .unwrap()

HTTP Error

supergraph::Response::error_builder()
  .error(Error::builder().message("Forbidden").build())
  .status_code(StatusCode::FORBIDDEN)
  .context(request.context)
  .build()
  .unwrap()

Router-level Error

router::Response::error_builder()
  .error(Error::builder().message("Not Found").build())
  .status_code(StatusCode::NOT_FOUND)
  .context(request.context)
  .build()
  .unwrap()

Source: SKILL.md on GitHub

1 warning16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides educational guidance and code templates for developing Apollo Router plugins. All external references target the vendor's official trusted repositories, and the provided code follows standard development practices for the platform.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    3/4 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 7be6130. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 8 months ago
All 1 allowed tools
Read Write Edit Glob Grep
Other metadata
metadata
{
  "author": "apollographql",
  "version": "1.0.0",
  "compatibility": "Requires Apollo Router with native plugin support"
}
  • Rust
  • apollo-router
  • graphql
  • plugins
  • authentication
  • middleware
  • request-lifecycle
  • subgraph
  • tower

README badge

README badge for apollographql/skills/apollo-router-plugin-creator

Guides writing native Rust plugins for Apollo Router that hook into the request lifecycle via router_service, supergraph_service, execution_service, and subgraph_service. Use this to add authentication, request validation, query plan control, or per-subgraph request modification.

Generated from the current SKILL.md.

Which service hook should I use to modify HTTP headers?
Use router_service, which runs at the beginning and end of the HTTP request lifecycle and has access to HTTP context and headers.
Can I access the GraphQL query and variables in a plugin?
Yes, use supergraph_service, which runs at the beginning and end of the GraphQL request lifecycle and can inspect and modify the GraphQL request and response.
How do I add custom logic for each subgraph request?
Use subgraph_service, which is called once per subgraph request and receives the subgraph name, allowing you to configure per-subgraph communication like dynamically adding headers.
What language are Apollo Router plugins written in?
Plugins are written in Rust and compile as native plugins for Apollo Router.
Can I add rate limiting or timeouts in a plugin?
Yes, Tower layers like rate_limit and timeout are available to compose within any service hook.

Generated from the current SKILL.md. These answers refresh after source changes.