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.

referencesexamples.md

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

Plugin Examples

Table of Contents


Simple Gate Plugin (execution_service)

From Apollo Router's forbid_mutations.rs - demonstrates checkpoint pattern to gate on query plan:

fn execution_service(&self, service: execution::BoxService) -> execution::BoxService {
  if self.forbid {
    ServiceBuilder::new()
      .checkpoint(|req: ExecutionRequest| {
        if req.query_plan.contains_mutations() {
          let error = Error::builder()
            .message("Mutations are forbidden".to_string())
            .extension_code("MUTATION_FORBIDDEN")
            .build();
          let res = ExecutionResponse::builder()
            .error(error)
            .status_code(StatusCode::BAD_REQUEST)
            .context(req.context)
            .build()?;
          Ok(ControlFlow::Break(res))
        } else {
          Ok(ControlFlow::Continue(req))
        }
      })
      .service(service)
      .boxed()
  } else {
    service
  }
}

Checkpoint with Response Transform (supergraph_service)

Demonstrates checkpoint + map_response with context passing:

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

  let proxy_request_header = self.configuration.proxy_request_header.clone();
  ServiceBuilder::new()
    .checkpoint(move |request: supergraph::Request| {
      // Store data in context for response phase
      let _ = request.context.insert(
        IS_CGP_REQUESTS,
        request.supergraph_request.headers().contains_key(&proxy_request_header)
      );
      Ok(ControlFlow::Continue(request))
    })
    .map_response(|response: supergraph::Response| {
      // Check context values set during request phase
      match (
        response.context.get::<_, bool>(IS_CGP_REQUESTS),
        response.context.get::<_, bool>(IS_INTROSPECTION_QUERY)
      ) {
        (Ok(Some(true)), Ok(Some(true))) => {
          supergraph::Response::error_builder()
            .error(Error::builder()
              .message("Not allowed")
              .extension_code("FORBIDDEN_REQUEST")
              .build())
            .status_code(StatusCode::OK)
            .context(response.context)
            .build()
            .unwrap()
        }
        _ => response
      }
    })
    .service(service)
    .boxed()
}

Testing Plugins

Use Apollo Router's test utilities to test plugins:

#[cfg(test)]
mod tests {
  use apollo_router::plugin::{test, Plugin, PluginInit};
  use apollo_router::services::supergraph::{self, Response};
  use apollo_router::Context;
  use tower::ServiceExt;

  #[tokio::test]
  async fn test_plugin() {
    // 1. Create plugin with fake config
    let service = MyPlugin::new(
      PluginInit::fake_builder()
        .config(MyPluginConfig { enabled: true })
        .build()
    )
    .await
    .expect("failed to create plugin")
    // 2. Wrap a mock service with the plugin's hook
    .supergraph_service({
      let mut mock = test::MockSupergraphService::new();
      mock.expect_call()
        .returning(|_| Response::fake_builder().build());
      mock.boxed()
    });

    // 3. Send a fake request through the service
    let response = service
      .oneshot(
        supergraph::Request::fake_builder()
          .context(Context::new())
          .query("query { field }")
          .build()
          .unwrap()
      )
      .await
      .unwrap();

    // 4. Assert on response
    let graphql_response = response.next_response().await.unwrap();
    assert!(graphql_response.errors.is_empty());
  }
}

Testing with Headers

#[tokio::test]
async fn test_with_headers() {
  let service = MyPlugin::new(/* ... */)
    .await
    .expect("failed to create plugin")
    .supergraph_service(mock_service());

  let response = service
    .oneshot(
      supergraph::Request::fake_builder()
        .context(Context::new())
        .header("x-custom-header", "value")
        .query("query { field }")
        .build()
        .unwrap()
    )
    .await
    .unwrap();
}

Testing Error Responses

#[tokio::test]
async fn test_error_response() {
  let service = MyPlugin::new(/* ... */)
    .await
    .expect("failed to create plugin")
    .supergraph_service(mock_service());

  let mut response = service
    .oneshot(request)
    .await
    .unwrap();

  let status_code = response.response.status().as_u16();
  let graphql_response = response.next_response().await.unwrap();

  assert_eq!(status_code, 200);
  assert!(!graphql_response.errors.is_empty());
  assert_eq!(
    graphql_response.errors[0].message,
    "Expected error message"
  );
  assert_eq!(
    graphql_response.errors[0].extensions.get("code").unwrap(),
    "ERROR_CODE"
  );
}

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.