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.
Field arguments
Section titled “Field arguments”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.
Query variables
Section titled “Query variables”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"] 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.
Why variables matter
Section titled “Why variables matter”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.
Arguments and variables together
Section titled “Arguments and variables together”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.
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.