Skip to content

Arguments & Variables

So far our queries have only selected fields. But a read usually needs input: which track, how many results, what search term. GraphQL carries that input in two layers — field arguments declared on the field, and query variables that feed values into those arguments from outside the query string. This lesson connects the two.

A field can declare arguments in its schema definition, written in parentheses after the field name. Each argument has a name and a type, and may be non-null:

type Query {
track(id: ID!): Track
search(term: String!, limit: Int): [Track!]!
}

track takes a required id. search takes a required term and an optional limit. When a client selects the field, it supplies the arguments inline:

{
track(id: "t1") {
title
}
}

Inside the resolver, those arguments arrive as the args object you met in the previous lesson — here args.id would be "t1". Arguments are how the client steers what a field returns.

Hardcoding "t1" into the query string works, but it is rigid: the query text changes every time the value changes, which defeats caching and invites string-building bugs. Query variables fix this by declaring named, typed placeholders on the operation and referencing them where an argument value would go.

A variable name starts with $. You declare it in the operation signature with its type, then use it as the argument value:

query GetTrack($id: ID!) {
track(id: $id) {
title
}
}

The query no longer contains a literal id at all. The actual value travels separately, as a small JSON object the client sends alongside the query:

{ "id": "t1" }
flowchart LR
  Decl["Operation declares $id: ID!"] --> Use["Field uses track(id: $id)"]
  Map["Variables map: { id: 't1' }"] -->|"supplies value"| Use
  Use --> Server["Server validates type, then resolves"]
A variable is declared on the operation, referenced at the argument, and supplied as a separate JSON map.

Read the flow: the operation declares $id: ID!, the field uses $id as the value for its id argument, and the variables map provides the concrete "t1". The query text stays constant across every call; only the variables map changes.

Separating the value from the query is not just tidy — it is how production GraphQL is meant to work:

  • Reuse and caching. The same query string runs for every id, so clients and servers can cache and pre-compile it.
  • Safety. Values are passed as typed data, not spliced into a string, so there is no injection-style query rewriting.
  • Validation. Because each variable is typed ($id: ID!), the server rejects a missing or wrong-typed value before any resolver runs.

The rule of thumb: literals are fine while you are exploring in a playground, but anything an app sends should pass dynamic values as variables.

The example below declares a variable $id: ID! on the operation, uses it as the id argument of the track field, and supplies the value through the variableValues map — exactly how a real client sends it. Press Run, then try changing the value in the variables map.

JavaScript

The query string mentions only $id; the value "t2" lives in variableValues. The resolver receives it through args and looks the track up. Change variableValues to { id: 't1' } and run again — the same query text returns a different track, which is the entire point of variables.

Where are a field’s accepted arguments defined?
What does the "$" prefix denote in a query like query GetTrack($id: ID!)?
Why prefer variables over hardcoding literal values into the query string?