GraphQL playground variables guide for testing and debugging

GraphQL playground variables guide for testing and debugging

GraphQL Playground variables let you pass dynamic data into your queries and mutations without hardcoding values directly into the query string. You define them once in a dedicated JSON panel at the bottom of the interface, then reference them inside your operation using the $ prefix. This keeps your operations reusable, your data clean, and your testing workflow fast — especially when you need to run the same query against dozens of different inputs.

Key Benefits at a Glance

  • Reusable Operations: Write a query or mutation once, run it with any combination of values by changing only the JSON panel — no copy-pasting or rewriting required.
  • Injection Safety: Variables are transmitted separately from the query document, so user-supplied input never touches your query syntax and cannot alter its structure.
  • Cleaner Schema Testing: Quickly reproduce exact API states by saving a query alongside its variable JSON, making bug reports and team handoffs reproducible in one paste.
  • Type Validation: GraphQL validates variable types before execution, so type mismatches surface as clear errors before your resolver ever runs.
  • Better Caching: Parameterized queries with consistent structure are far easier for client-side caches (Apollo, urql) to normalize than dynamically constructed query strings.

Introduction to variables

In GraphQL, a variable is a named placeholder declared in your operation signature and resolved at execution time from a separate JSON object. This is fundamentally different from string interpolation — the query document itself never changes, only the data you hand to it. That separation is what makes variables the standard approach for anything beyond a one-off manual test.

GraphQL Playground and GraphiQL both expose this through a split panel layout: the top-left editor holds your query or mutation, and the Query Variables tab at the bottom holds the JSON. When you click Run, both are sent together in a single HTTP POST — the query as query, the variables as variables, and optionally the operation name as operationName.

  • Variables are declared in the operation signature, not injected into the query string
  • They are transmitted as a separate JSON object alongside the query document
  • GraphQL validates types before any resolver executes
  • The same query document works for every test — only the JSON changes
  • Variables work identically in queries, mutations, and subscriptions

Setting up query variables

The Query Variables panel is the tab at the bottom-left of GraphQL Playground, directly below the query editor. It accepts standard JSON. If the panel is collapsed, click the tab label to expand it — it will split the left pane horizontally, giving you the query editor on top and the variable editor below.

  1. Open GraphQL Playground in your browser and connect to your endpoint.
  2. Write your operation in the top editor, declaring variables in the signature (see syntax below).
  3. Click the Query Variables tab at the bottom of the left panel to open the JSON editor.
  4. Enter a valid JSON object whose keys match your declared variable names (without the $).
  5. Press the Run (▶) button — Playground sends both the query and the variables in one request.

A common mistake at this step is leaving the Variables panel empty while declaring variables in the query. GraphQL will throw a Variable "$id" of required type "ID!" was not provided error. If a variable is truly optional, declare it without ! and omit it from the JSON — GraphQL will pass null to the resolver.

If you use authorization headers in Playground, note that headers and variables are independent panels. Auth tokens go in the HTTP Headers tab; runtime data goes in Query Variables.

Using variables in queries

Variables pair naturally with where clause syntax — pass filter criteria as a variable object instead of hardcoding conditions, so the same query handles every filter combination your UI can produce.

Every variable follows the same three-step pattern: declare it in the operation signature, type it, and use it inside the operation body. The declaration sits inside the parentheses after your operation name. The type must match a scalar or input type defined in your schema.

The $ prefix is required in both the declaration ($id: ID!) and every reference inside the body (user(id: $id)). Omitting it in either place is one of the most common beginner mistakes and produces a clear parse error.

“In the Query Variables tab, you add the variable and use it in your GraphQL query — variables must be written in valid JSON.”
GeeksforGeeks
Source link
  • Declare every variable in the operation signature with $name: Type
  • Add ! to mark a variable as required; omit it to make it optional (nullable)
  • Reference variables with $name inside field arguments, directives, and fragments
  • Keys in the JSON panel must exactly match variable names without the $

Variable syntax example

Below are two complete examples — a query and a mutation — showing both the operation and the corresponding Variables JSON. These cover the two most common patterns you will encounter in daily development.

Query example: fetching a user by ID. The $id: ID! declaration means the variable is required and must be a valid ID scalar. The exclamation mark prevents the query from running with a null identifier.

query GetUser($id: ID!) {
  user(id: $id) {
    name
    email
    profile {
      avatar
      bio
    }
  }
}
{
  "id": "123"
}

Mutation example: creating a new post. Input objects are declared as a custom input type (CreatePostInput) and passed as a single variable, which is the idiomatic pattern for mutations with multiple fields.

mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
    publishedAt
  }
}
{
  "input": {
    "title": "Getting started with GraphQL variables",
    "body": "Variables make your operations reusable...",
    "authorId": "456"
  }
}

Notice that for the mutation, the entire payload is nested under a single input key. This matches the $input: CreatePostInput! declaration and is the standard convention for mutation arguments in production schemas.

Variable TypeDeclarationJSON ValueTypical Use
String$name: String!“John Doe”Names, search terms
Int$limit: Int = 1010Pagination, counts
Boolean$active: BooleantrueFlags, toggles
ID$userId: ID!“abc123”Entity identifiers
Input Object$input: CreatePostInput!{ “title”: “…” }Mutation payloads
Enum$sort: SortOrder = ASC“DESC”Ordering, status

Best practices

Variables are simple to use but easy to misuse. The patterns below reflect conventions from production GraphQL APIs and will make your operations easier to maintain, test, and hand off to colleagues.

Use descriptive names. $userId, $searchTerm, and $pageLimit are immediately readable. $input, $data, and $val require the reader to trace through the entire query to understand what they carry. Name variables after the data they represent, not the role they play.

Always declare types explicitly. Even when GraphQL could infer a type, the explicit declaration acts as inline documentation and enables Playground’s autocomplete and inline error highlighting.

Use default values for optional parameters. A declaration like $limit: Int = 20 means your query runs correctly even when the caller omits limit from the JSON, reducing defensive null-handling in your resolvers.

  • Use ! only when a missing value would make the query logically meaningless
  • Provide default values ($limit: Int = 20) for optional but commonly-used parameters
  • Prefer a single $input: InputType! variable for mutations over many individual fields
  • Match your JSON keys exactly to variable names — casing matters (userIdUserId)
PracticeGood ExamplePoor ExampleWhy It Matters
Variable naming$userEmail: String!$input: String!Readability at a glance
Type declaration$limit: Int = 10$limitEnables type validation
Null handling$filter: String$filter: String!Allows optional filters
Default values$sort: SortOrder = ASC$sort: SortOrderSafer resolver logic

Always validate variable inputs server-side as well. Client-provided values must pass schema type checks, but business-level constraints (valid ranges, allowed enum values, ownership) need explicit validation — see how type system violations surface in error responses to understand what to expect when validation fails.

Common use cases

The patterns below account for the majority of variable usage in real-world GraphQL APIs. Understanding them gives you a mental library to draw on whenever you hit a new testing scenario in Playground.

Filtering with dynamic criteria. Pass filter parameters as variables instead of building separate queries for each filter state. For example, a product listing can use $category: String and $minPrice: Float together, and you test every combination by changing only the JSON.

Cursor-based pagination. The Relay pagination convention uses $first: Int and $after: String (a cursor). In Playground, you grab the endCursor from a response, paste it into the Variables JSON as "after", and immediately fetch the next page — no query editing needed.

query GetProducts($first: Int = 10, $after: String, $category: String) {
  products(first: $first, after: $after, category: $category) {
    edges {
      node {
        id
        name
        price
      }
    }
    pageInfo {
      endCursor
      hasNextPage
    }
  }
}
{
  "first": 10,
  "after": "cursor_abc123",
  "category": "electronics"
}
  • Filtering: pass criteria as variables, reuse the same query for every filter combination
  • Pagination: use $first/$after (cursor) or $limit/$offset (offset-based) patterns
  • Authentication: pass tokens as variables in mutation inputs, never in query strings
  • Localization: switch $locale: Locale! to test multi-language content in one query
  • Feature flags: use Boolean variables to toggle conditional field inclusion via @include(if: $flag)

Directive conditions. The built-in @include(if: Boolean) and @skip(if: Boolean) directives accept variables directly, letting you toggle entire field sets from the JSON panel — useful when testing conditional UI states.

Use CaseVariable ExampleBenefitPattern
Data Filtering$status: Status, $category: StringOne query, any filterSearch & list views
Pagination$first: Int, $after: StringCursor reuse from responseData tables, feeds
Authentication$token: String!Keeps tokens out of query docLogin, refresh flows
Localization$locale: Locale!Test all languages in one queryGlobal applications
Conditional fields$showDetails: Boolean!Toggle field sets via @includeProgressive disclosure UI
  • DO: Test your variables with edge-case inputs — empty strings, zero, null, very long values
  • DO: Use default values for optional parameters so queries run safely without a full JSON object
  • DO: Validate variable inputs server-side; client-side type checks are not a security boundary
  • DON’T: Hardcode credentials, tokens, or IDs directly in the query string
  • DON’T: Over-parameterize simple queries that will only ever run with one fixed input
  • DON’T: Use generic names like $data or $input for query variables — only for mutation input objects

More GraphQL Playground Guides

Frequently Asked Questions

Hardcoded values are written directly into the query string (e.g., user(id: "123")), meaning you must rewrite the query for every different input. Variables separate the data from the query document — you write user(id: $id) once and supply any value through the JSON panel. Variables are also type-validated by GraphQL before execution and transmitted separately in the HTTP request, which eliminates injection risks.

This error means the variable was declared as required (!) in your operation but the JSON key is missing or misspelled. JSON keys are case-sensitive and must match the variable name exactly without the $ prefix. Check that your JSON is valid (no trailing commas, all strings quoted) and that the key casing matches — userId and UserId are treated as different names.

Yes, the syntax is identical. Replace query with mutation in the operation keyword, declare your variables in the signature, and populate the Query Variables panel with the JSON. The standard convention for mutations is to wrap all input fields inside a single $input: YourInputType! variable rather than declaring many individual variables — this keeps mutation signatures clean and makes schema evolution easier.

Declare the variable with a list type in square brackets: $ids: [ID!]!. In the JSON panel, provide a standard JSON array: {"ids": ["1", "2", "3"]}. The outer ! means the list itself is required; the inner ! means each element must be non-null. Omit either ! to allow null at the list or element level depending on your schema requirements.

Yes. Both tools implement the same GraphQL over HTTP specification and expose a Variables panel (called “Variables” in GraphiQL, “Query Variables” in Playground) that accepts JSON. The query syntax, type declarations, and $ prefix convention are identical. The main practical difference is the UI layout — GraphiQL uses a bottom drawer while newer Playground versions use a side panel — but the behavior is the same.