Launch Apollo Studio

Embedding the Explorer

If you have a public variant of your graph, you can embed the Apollo Studio Explorer in a webpage that you can then provide to your graph's consumers. This enables those consumers to test out operations from your own website.

For example, here's an embedded Explorer for an Apollo example graph. Try it out!

This embedded Explorer collapses its left column by default because the full three-column layout is a little cramped on this page. You can customize the embedded Explorer's appearance to suit the page it's on.

Basic syntax

Before you proceed, make sure you have a public variant of your graph in Apollo Studio.

You embed the Explorer in an iframe that points to

<iframe src="" />

As shown, the iframe's src URL requires the following query parameters:

  • graphRef - This indicates which public variant's schema the Explorer should download from Apollo Studio (e.g., my-graph-id@my-variant).
  • sendRequestsFrom - This indicates whether operation requests are sent directly from the embedded iframe (embed) or they're routed through the parent page (parent).

Handling CORS

Your GraphQL endpoint needs to be able to accept requests from your embedded Explorer. Because the Explorer is embedded in an iframe, its requests originate from

There are two supported methods for handling this: adding a CORS domain or routing requests through the parent page.

We recommend that you update your GraphQL endpoint's CORS configuration (such as with the cors ApolloServer option) to accept requests from

If you use this method, set the sendRequestsFrom query parameter to embed.

Also make sure you've set your public variant's endpoint URL (and its subscription endpoint, if any) in Apollo Studio.

After you add the Explorer's origin to your endpoint's CORS configuration, you're all set! You can now embed the iframe in any webpage and set its dimensions as you like. Also check out additional supported options.

Route requests through the parent page (advanced)

Some endpoints can't receive requests from the Explorer's origin due to firewall rules or organizational policies. In these cases, you can use the the postMessage API to route Explorer requests through your embedding webpage's domain.

If you use this method, set the sendRequestsFrom query parameter to parent.

If you need to use this advanced method, proceed to the additional setup in Using postMessage.


When you embed the Explorer, you set options for it by including query parameters in the src URL of the iframe, like so:

<iframe src="" />

The following options are supported:

Name /


Required. The graph ref for the public variant you want to use the embedded Explorer with. Has the format graph-id@variant-name.

The Explorer fetches this variant's schema from Apollo Studio to populate its Documentation panel and enable code completion.

If you omit this option, the Explorer does still load, but it doesn't know which schema to fetch. This prevents the Explorer from providing critical features like documentation and code completion.


"embed" | "parent"

Required. If parent, the embedded Explorer uses the postMessage API to notify the parent page when a user initiates a GraphQL operation. The parent page is then responsible for transmitting the operation to your GraphQL server.

If embed, the embedded Explorer transmits operations directly to your GraphQL server from the origin

See Handling CORS.



A URI-encoded operation to populate in the Explorer's editor on load.

If you omit this, the Explorer initially loads an example query based on your schema.

If shouldPersistState is true and you provide this option, the Explorer loads any of the user's tabs from localStorage, and it also opens a new tab with this operation.


  query ExampleQuery {
    books {


A URI-encoded, serialized object containing initial variable values to populate in the Explorer on load.

If provided, these variables should apply to the initial query you provide for document.


    userID: "abc123"


A URI-encoded, serialized object containing initial HTTP header values to populate in the Explorer on load.

Note that if you route requests through the parent page, you need to specify headers in your request handler, not just in the Explorer.


    authorization: "Bearer abc123"

"true" | "false"

If true, the embedded Explorer uses localStorage to persist its state (including operations, tabs, variables, and headers) between user sessions. This state is automatically populated in the Explorer on page load.

If false, the embedded Explorer loads with an example query based on your schema (unless you provide document).

The default value is false.



A string to populate in the Explorer's search bar on load. You can use this to direct users to a particular type or field.

Example: ?


"open" | "closed"

If open, the Explorer's Documentation panel (the left column) is initially expanded. If closed, the panel is initially collapsed.

The default value is open.


"true" | "false"

If true, the embedded Explorer includes the panels for setting request headers and environment variables. If false, those panels are not present.

The default value is true.


"dark" | "light"

If dark, the Explorer's dark theme is used. If light, the light theme is used.

The default value is dark.

Using postMessage (advanced)

If you're embedding the Explorer, it's possible that your GraphQL server can't accept requests from due to organizational policies. In this case, your webpage can use the postMessage API to act as an intermediary between the embedded Explorer and your server:

Embedded ExplorerYour WebpageGraphQL ServerUser executes queryPosts ExplorerRequest messageExtracts query detailsfrom event objectTransmits query to GraphQL serverResolves query and respondsPosts ExplorerResponse messageExtracts response datafrom event objectDisplays response dataEmbedded ExplorerYour WebpageGraphQL Server

In this case, the embedded Explorer never sends network requests to your GraphQL server. Instead, the Explorer's iframe uses the window.postMessage method to tell your webpage when the user wants to execute a GraphQL operation. Your webpage is then responsible for sending the operation to your GraphQL server (for example, via fetch) and then providing the operation result back to the iframe via another postMessage call.

Because of this, embedding the Explorer requires additional setup beyond adding an iframe to your page.

postMessage examples

  • This repo demonstrates a React app that embeds the Explorer and uses postMessage.

    • See in particular setupEmbedRelay.ts, which defines view-layer-agnostic logic for communicating between the iframe, your own page, and your GraphQL server.

    • You can also run this example on CodeSandbox:

      Edit embedded-explorer-demo
  • The Explorer embedded in this article uses this component from Apollo's Gatsby documentation theme.

postMessage setup

This section walks you through the code to set up an embedded Explorer that uses postMessage. It borrows code from this setupEmbedRelay.ts example and removes some optional portions for clarity.

1. Define Explorer constants

Your webpage and the embedded Explorer use the postMessage API to send messages to each other. Those messages each have a name that indicates the type of message being posted.

Let's define constants for each of those message types, along with a constant for our iframe's src URL:

// URL for any embedded Explorer iframe

// Message types for queries and mutations

// Message types for subscriptions (not covered in this setup)
const EXPLORER_SUBSCRIPTION_REQUEST = "ExplorerSubscriptionRequest";
const EXPLORER_SUBSCRIPTION_RESPONSE = "ExplorerSubscriptionResponse";
const SUBSCRIPTION_TERMINATION = "ExplorerSubscriptionTermination";

2. Set up communication

Next, let's define a function that sets up communication between a webpage and the embedded Explorer's iframe. This function defines a callback that will execute whenever window.postMessage is called by the iframe.

export function setUpEmbedRelay() {

  // Callback definition
  const onPostMessageReceived = (
    event: MessageEvent<{
      name?: string;
      operation?: string;
      operationId?: string;
      operationName?: string;
      variables?: Record<string, string>;
      headers?: Record<string, string>;
  ) => {

    // Obtain the iframe element by any applicable logic for your page.
    // This obtains an element with ID `embedded-explorer`.
    const embeddedExplorerIFrame =
      (document.getElementById("embedded-explorer") as HTMLIFrameElement) ??

    // Check to see if the posted message indicates that the user is
    // executing a query or mutation in the Explorer
    const isQueryOrMutation =

    // If the user is executing a query or mutation...
    if (
      isQueryOrMutation && && &&
    ) {
      // Extract the operation details from the object
      const { operation, operationId, operationName, variables, headers } =;

      // Execute the operation, providing all required details
      // (we'll define executeOperation next)

  // Execute our callback whenever window.postMessage is called
  window.addEventListener("message", onPostMessageReceived);

Now we can import and call setupEmbedRelay on any page where we'll embed the Explorer. However, we still need to define the executeOperation function that's called by our callback.

3. Define operation execution logic

Our webpages can use the fetch API to send operations to our GraphQL server. To do this, we need the URL of our server's GraphQL endpoint. Substitute it in the executeOperation function below, where indicated.

// Helper function that adds content-type: application/json
// to each request's headers if not present
function getHeadersWithContentType(
  headers: Record<string, string> | undefined
) {
  const headersWithContentType = headers ?? {};
  if (
      (key) => key.toLowerCase() !== "content-type"
  ) {
    headersWithContentType["content-type"] = "application/json";
  return headersWithContentType;

// Function for executing operations
async function executeOperation({
}: {
  operation: string;
  operationId: string;
  operationName?: string;
  variables?: Record<string, string>;
  headers?: Record<string, string>;
  embeddedExplorerIFrame?: HTMLIFrameElement;
}) {
  const response = await fetch(
    // Substitute your server's URL for this example URL.
      method: "POST",
      headers: getHeadersWithContentType(headers),
      body: JSON.stringify({
        query: operation,

  // After the operation completes, post a response message to the
  // iframe that includes the response data
  await response.json().then((response) => {
        // Include the same operation ID in the response message's name
        // so the Explorer knows which operation it's associated with

Instead of hardcoding your server's URL as shown, you can modify executeOperation to take a server URL parameter if you'll be embedding Explorers for multiple graphs.

4. Embed the iframe

We're ready to add an embedded Explorer to our page! Below is an example App.tsx file that uses our setupEmbedRelay function to wire up an embedded Explorer iframe.

This example uses React, but you can use any view layer you like. If you do use React, you should also modify your code to remove your message event listener when your component unmounts.

import React, { useEffect } from 'react';
import { EMBEDDABLE_EXPLORER_URL, setUpEmbedRelay } from './setupEmbedRelay';

export const App = ()=> {

  useEffect(() => {
  }, [])

  // Provide iframe options via URL query parameters
    '?graphRef=Apollo-Fullstack-Demo-o3tsz8@current' +

  return (
    <div className="App">
      <h1 className="demo-app-title">Apollo's Embedded Explorer Demo App</h1>
      <iframe id="embedded-explorer" className="embedded-explorer-iframe" title="embedded-explorer" src={explorerURL} />

5. Run the example

You can view and run a complete example that uses code very similar to the above on Codesandbox:

Edit embedded-explorer-demo

The example includes support for subscriptions, which are omitted from this Setup section for clarity. The subscription-specific logic should be more straightforward after familiarizing yourself with the logic for queries and mutations.

Message types

If you're using the postMessage API with your embedded Explorer, every message posted this way has an which indicates the type of message.

These are the message types used by the embedded Explorer's iframe and your page:


Posted by the iframe to notify your page that the user wants to execute a query or mutation.

The following operation details are included in the associated object:

  • operation (string)
  • operationId (string)
  • operationName (string)
  • variables (object)
  • headers (object)

Posted by your page to notify the iframe of a query or mutation result. The message should be the operation's JSON response object.


Posted by the iframe to notify your page that the user wants to execute a subscription operation.

The same operation details are included in the object that are included in ExplorerRequest messages.


Posted by your page to notify the iframe of a new subscription result. The message should be the subscription's new JSON response object.


Posted by the iframe to notify your page that the user wants to terminate an active subscription operation.


Posted by your page to provide the iframe with an operation string to populate in a new tab of the Explorer's editor. You might want to use this to populate a default operation on page load.


Posted by the iframe to notify your page when the Explorer is ready for you to post a SetOperation message.

If you post a SetOperation message before the Explorer is ready, the Explorer does not populate its editor with the operation you provide.

Edit on GitHub