Skip to content

Fix: GraphQL 400 Bad Request Error (Query Syntax and Variable Errors)

FixDevs ·

Quick Answer

How to fix GraphQL 400 Bad Request errors — malformed query syntax, variable type mismatches, missing required fields, schema validation failures, and how to debug GraphQL errors from Apollo and fetch.

The Error

A GraphQL request returns HTTP 400 with an error response:

{
  "errors": [
    {
      "message": "Syntax Error: Expected Name, found <EOF>.",
      "locations": [{ "line": 3, "column": 1 }]
    }
  ]
}

Or a variable type error:

{
  "errors": [
    {
      "message": "Variable \"$id\" of type \"String\" used in position expecting type \"ID!\"."
    }
  ]
}

Or a field validation error:

{
  "errors": [
    {
      "message": "Cannot query field \"username\" on type \"User\". Did you mean \"name\"?"
    }
  ]
}

Why This Happens

GraphQL servers validate every request against the schema before execution. A 400 response means the request itself is malformed or invalid — the server refused to run the query at all. Common causes:

  • Query syntax errors — missing braces, unclosed strings, wrong field syntax.
  • Querying fields that don’t exist on the type — field name typo or using a field from a different type.
  • Variable type mismatch — passing a String where the schema expects an ID or Int.
  • Missing required variables — a query uses $id: ID! but the variables object doesn’t include id.
  • Sending the wrong Content-Type — GraphQL servers require Content-Type: application/json with a JSON body containing { query, variables }.
  • Introspection disabled in production — queries that rely on __schema or __type fail with 400 in hardened production environments.

Fix 1: Fix Query Syntax Errors

GraphQL has strict syntax — a missing brace or wrong field structure causes immediate rejection:

Broken — missing closing brace:

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
  # Missing closing brace for user
# Missing closing brace for query

Broken — wrong field selection syntax:

# Wrong — using = to assign
query {
  users {
    id = name  # ← Syntax error
  }
}

# Correct — just list fields
query {
  users {
    id
    name
  }
}

Use an IDE with GraphQL language support to catch syntax errors before sending:

npm install --save-dev graphql-language-service-cli @graphql-eslint/eslint-plugin

Validate queries against your schema locally:

npx graphql-inspector validate 'src/**/*.graphql' --schema schema.graphql

Fix 2: Fix Variable Type Mismatches

GraphQL is strictly typed — variables must match the schema exactly:

Schema:

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
}

Broken — sending wrong variable types:

// Schema expects ID! but sending a number
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) { id name }
  }
`;

// Wrong — variables don't match
client.query({
  query: GET_USER,
  variables: { id: 123 },          // Number — schema wants ID (string)
});

Fixed:

client.query({
  query: GET_USER,
  variables: { id: '123' },        // String — matches ID type
});

Common type mappings:

GraphQL TypeJavaScript Type
IDstring
Stringstring
Intnumber (integer)
Floatnumber
Booleanboolean
Custom scalar DateTimestring (ISO 8601) or Date

Check required vs optional variables:

# $id: ID! — required, must be provided
# $name: String — optional, can be omitted
query GetUser($id: ID!, $name: String) {
  user(id: $id, name: $name) { id }
}
// If $id is required and you omit it:
variables: { name: 'Alice' }  // Error: Variable "$id" of required type "ID!" was not provided.

// Must always include required variables:
variables: { id: '1', name: 'Alice' }

Fix 3: Fix the Request Format

GraphQL servers expect a specific JSON body format. Wrong content type or body structure causes 400:

Using fetch directly:

// Wrong — sending the query as plain text
const response = await fetch('/graphql', {
  method: 'POST',
  body: `query { users { id name } }`,  // ← Must be JSON
});

// Correct — JSON body with query and variables
const response = await fetch('/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',  // Required
    'Authorization': `Bearer ${token}`,
  },
  body: JSON.stringify({
    query: `
      query GetUsers($limit: Int) {
        users(limit: $limit) {
          id
          name
          email
        }
      }
    `,
    variables: { limit: 10 },
  }),
});

const { data, errors } = await response.json();
if (errors) {
  console.error('GraphQL errors:', errors);
}

Using Apollo Client — ensure the link is configured correctly:

import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';

const client = new ApolloClient({
  link: new HttpLink({
    uri: '/graphql',
    headers: {
      Authorization: `Bearer ${getToken()}`,
    },
  }),
  cache: new InMemoryCache(),
});

Using graphql-request:

import { GraphQLClient, gql } from 'graphql-request';

const client = new GraphQLClient('/graphql', {
  headers: { Authorization: `Bearer ${token}` },
});

const GET_USERS = gql`
  query GetUsers($limit: Int) {
    users(limit: $limit) { id name }
  }
`;

const data = await client.request(GET_USERS, { limit: 10 });

Fix 4: Fix “Cannot query field” Errors

This means you are requesting a field that doesn’t exist on the type:

{
  "message": "Cannot query field \"username\" on type \"User\". Did you mean \"name\"?"
}

Check the actual schema:

# Introspection query — run this to get the User type fields
query {
  __type(name: "User") {
    fields {
      name
      type { name kind }
    }
  }
}

Or use a GraphQL playground (Apollo Sandbox, GraphiQL) to browse the schema.

Fix the field name:

# Wrong — field doesn't exist
query {
  user(id: "1") {
    username   # ← No such field
    email_address  # ← No such field
  }
}

# Correct — use actual schema field names
query {
  user(id: "1") {
    name    # ← Correct
    email   # ← Correct
  }
}

For nested type errors:

{
  "message": "Cannot query field \"street\" on type \"User\". Did you mean to use an inline fragment on \"Address\"?"
}
# Wrong — trying to access nested fields without drilling in
query {
  user(id: "1") {
    address.street  # ← Not valid GraphQL syntax
  }
}

# Correct — nest the selection set
query {
  user(id: "1") {
    address {
      street
      city
      zip
    }
  }
}

Fix 5: Handle GraphQL Errors in Your Application

GraphQL can return both HTTP 200 with errors (partial data) and HTTP 400 (request rejected). Handle both:

// Apollo Client — error handling
import { ApolloClient, from, HttpLink } from '@apollo/client';
import { onError } from '@apollo/client/link/error';

const errorLink = onError(({ graphQLErrors, networkError, operation }) => {
  if (graphQLErrors) {
    graphQLErrors.forEach(({ message, locations, path, extensions }) => {
      console.error(`GraphQL Error: ${message}`, { locations, path });

      if (extensions?.code === 'UNAUTHENTICATED') {
        // Redirect to login
        window.location.href = '/login';
      }
    });
  }

  if (networkError) {
    console.error('Network error:', networkError);
    // networkError.statusCode === 400 means bad request
    if ('statusCode' in networkError && networkError.statusCode === 400) {
      console.error('Bad GraphQL request — check query syntax and variables');
    }
  }
});

const client = new ApolloClient({
  link: from([errorLink, new HttpLink({ uri: '/graphql' })]),
  cache: new InMemoryCache(),
});

In components — check for errors explicitly:

const { loading, error, data } = useQuery(GET_USER, {
  variables: { id: userId },
});

if (error) {
  // error.graphQLErrors — validation/resolver errors
  // error.networkError — HTTP-level errors (400, 500, network failures)
  const is400 = error.networkError?.statusCode === 400;
  return <div>Error: {is400 ? 'Invalid query' : error.message}</div>;
}

Fix 6: Fix Mutations with Input Types

Mutations often use input types — a common source of 400 errors:

Schema:

input CreateUserInput {
  name: String!
  email: String!
  role: Role = USER
}

type Mutation {
  createUser(input: CreateUserInput!): User!
}

Broken — passing variables at the wrong level:

// Wrong — variables passed as top-level instead of nested in input
client.mutate({
  mutation: CREATE_USER,
  variables: { name: 'Alice', email: '[email protected]' },
});

// Also wrong query — missing 'input' wrapper
const CREATE_USER = gql`
  mutation CreateUser($name: String!, $email: String!) {
    createUser(name: $name, email: $email) { id }  # ← Schema expects input: CreateUserInput!
  }
`;

Fixed:

const CREATE_USER = gql`
  mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) { id name email }
  }
`;

client.mutate({
  mutation: CREATE_USER,
  variables: {
    input: { name: 'Alice', email: '[email protected]' },  // Nested in 'input'
  },
});

Fix 7: Enable GraphQL Persisted Queries Correctly

If your server uses Automatic Persisted Queries (APQ) and your client sends a hash that the server doesn’t recognise, it returns 400:

{
  "errors": [{ "message": "PersistedQueryNotFound" }]
}

Apollo Client handles this automatically — ensure the retry link is configured:

import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';
import { sha256 } from 'crypto-hash';

const persistedQueriesLink = createPersistedQueryLink({
  sha256,
  useGETForHashedQueries: true,
});

const client = new ApolloClient({
  link: from([persistedQueriesLink, authLink, httpLink]),
  cache: new InMemoryCache(),
});

When the server returns PersistedQueryNotFound, Apollo Client automatically retries with the full query — no manual handling needed.

Still Not Working?

Use GraphiQL or Apollo Sandbox to test queries interactively. These tools provide schema-aware autocomplete and immediately show validation errors. Test your query there before putting it in code.

Log the exact request being sent:

// Log every GraphQL request
const loggingLink = new ApolloLink((operation, forward) => {
  console.log('GraphQL Request:', {
    operationName: operation.operationName,
    query: operation.query.loc?.source.body,
    variables: operation.variables,
  });
  return forward(operation);
});

Check CSRF protection on the server. Some GraphQL servers require a CSRF token or specific headers. A missing CSRF token often returns 400 or 403:

headers: {
  'Content-Type': 'application/json',
  'X-CSRF-Token': getCsrfToken(),  // May be required
  'Apollo-Require-Preflight': 'true', // Required by some Apollo Server configs
},

For related API errors, see Fix: CORS Access-Control-Allow-Origin Error and Fix: FastAPI 422 Unprocessable Entity.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles