Skip to content
Guides

GraphQL errors

Handle data, errors, HTTP status codes, and GraphQLResponseError in Storefront API.

Last updated:

A GraphQL response uses a JSON envelope with data and, when execution finds problems, errors. Inspect both in addition to the HTTP status.

HTTP 200 does not guarantee an error-free execution

A GraphQL operation can return HTTP 200 and include errors. Checking only response.ok is not sufficient.

Response shape

A successful execution contains data:

Successful response
{
  "data": {
    "store": {
      "id": "7204558912004325301",
      "name": "My store"
    }
  }
}

When a resolver rejects the operation, the response includes errors. data can be null, as in this invalid argument example:

Error response
{
  "errors": [
    {
      "message": "Product ID is invalid.",
      "path": ["relatedProducts"],
      "extensions": {
        "code": "BAD_USER_INPUT"
      }
    }
  ],
  "data": null
}
FieldUse
dataResult of the requested fields when available.
errorsList of errors found during execution.
errors[].messageDiagnostic message. Do not use it as a stable identifier.
errors[].pathPath to the field that caused the error, when available.
errors[].extensions.codeCode for distinguishing known cases, when available.

Document parsing and validation errors can use other codes from the GraphQL runtime.

Public resolver codes

Public Storefront resolvers use these values in extensions.code:

CodeMeaningFirst action
BAD_USER_INPUTAn argument, filter, limit, or ID is invalid.Fix the input; do not repeat the same operation unchanged.
NOT_FOUNDThe requested resource does not exist or is not visible.Check the ID or slug, resolved store, and resource visibility.
STORE_CONTEXT_REQUIREDThe store could not be resolved from x-Store-Domain.Check that the header contains a configured slug or domain.

Make decisions from extensions.code, not by comparing the text in message.

Handle errors with fetch

Check the transport status first and then the GraphQL envelope:

graphql-fetch.js
const response = await fetch("https://storefront.ecomiq.pe/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-Store-Domain": "my-store",
  },
  body: JSON.stringify({
    query: "query Store { store { id name } }",
  }),
});

const result = await response.json();

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

if (result.errors?.length) {
  const firstError = result.errors[0];
  const code = firstError.extensions?.code;

  switch (code) {
    case "BAD_USER_INPUT":
      console.error("Fix the query arguments.");
      break;
    case "NOT_FOUND":
      console.error("The resource does not exist or is not visible.");
      break;
    case "STORE_CONTEXT_REQUIRED":
      console.error("Check x-Store-Domain.");
      break;
    default:
      console.error(firstError.message);
  }

  throw new Error(firstError.message);
}

console.log(result.data.store);

Do not access result.data before checking errors.

Handle GraphQLResponseError

@ecomiq/storefront validates data with the Zod schema passed to graphql(). The method throws GraphQLResponseError if the response contains errors or if data does not match that schema.

graphql-client.ts
import {
  createClient,
  GraphQLResponseError,
  z,
} from "@ecomiq/storefront";

const SellerData = z.object({
  seller: z.object({
    id: z.string(),
    name: z.string(),
    slug: z.string(),
  }),
});

const api = createClient();

try {
  const data = await api.graphql(
    `
      query Seller($slug: String!) {
        seller(slug: $slug) { id name slug }
      }
    `,
    SellerData,
    { slug: "acme" },
  );

  console.log(data.seller);
} catch (error) {
  if (!(error instanceof GraphQLResponseError)) {
    throw error;
  }

  for (const graphQLError of error.errors) {
    console.error(
      graphQLError.extensions?.code ?? "GRAPHQL_ERROR",
      graphQLError.message,
    );
  }
}

GraphQLResponseError.errors preserves the errors from the envelope. GraphQLResponseError.data preserves any received data; check that it is present before using it.

Next steps

On this page