At a glance
- Identifier: #793
- Stage: RFC 3 / Accepted
- Champion: @benjie
- Latest activity: 2 commits pushed on 2025-07-01
- Spec PR: https://github.com/graphql/graphql-spec/pull/793
Spec PR description
Coercing Field Arguments states:
5.c. Let defaultValue be the default value for argumentDefinition. [...] 5.h. If hasValue is not true and defaultValue exists (including null): 5.h.i. Add an entry to coercedValues named argumentName with the value defaultValue. [...] 5.j.iii.1. If value cannot be coerced according to the input coercion rules of argumentType, throw a field error. 5.j.iii.2. Let coercedValue be the result of coercing value according to the input coercion rules of argumentType. 5.j.iii.3. Add an entry to coercedValues named argumentName with the value coercedValue.
Here we note that there is no run-time coercion of defaultValue, which makes sense (why do at runtime that which can be done at build time?). However, there doesn't seem to be a rule that specifies that defaultValue must be coerced at all, which leads to consequences:
- you could use any value for defaultValue and it could break the type safety guarantees of GraphQL
- nested defaultValues are not applied
When building the following GraphQL schema programmatically (code below) with GraphQL.js:
type Query {
example(inputObject: ExampleInputObject! = {}): Int
}
input ExampleInputObject {
number: Int! = 3
}
And a resolver for Query.example:
resolve(source, args) {
return args.inputObject.number;
}
You might expect the following queries to all gave the same result:
query A {
example
}
query B {
example(inputObject: {})
}
query C {
example(inputObject: { number: 3 })
}
query D($inputObject: ExampleInputObject! = {}) {
example(inputObject: $inputObject)
}
However, it turns out that query A's result differs:
{"example":null}
{"example":3}
{"example":3}
{"example":3}
This is because defaultValue for Query.example(inputObject:) was not coerced, so none of the defaultValues of ExampleInputObject were applied.
This is extremely unexpected, because looking at the GraphQL schema definition it looks like there's no circumstance under which ExampleInputObject may not have number as an integer; however when the defaultValue of Query.example(inputObject:) is used, the value of number is undefined.
const {
graphqlSync,
printSchema,
GraphQLSchema,
GraphQLObjectType,
GraphQLInputObjectType,
GraphQLInt,
GraphQLNonNull,
} = require("graphql");
const ExampleInputObject = new GraphQLInputObjectType({
name: "ExampleInputObject",
fields: {
number: {
type: new GraphQLNonNull(GraphQLInt),
defaultValue: 3,
},
},
});
const Query = new GraphQLObjectType({
name: "Query",
fields: {
example: {
args: {
inputObject: {
type: new GraphQLNonNull(ExampleInputObject),
defaultValue: {},
},
},
type: GraphQLInt,
resolve(source, args) {
return args.inputObject.number;
},
},
},
});
const schema = new GraphQLSchema({
query: Query,
});
console.log(printSchema(schema));
// All four of these should be equivalent?
const source = /* GraphQL */ `
query A {
example
}
query B {
example(inputObject: {})
}
query C {
example(inputObject: { number: 3 })
}
query D($inputObject: ExampleInputObject! = {}) {
example(inputObject: $inputObject)
}
`;
const result1 = graphqlSync({ schema, source, operationName: "A" });
const result2 = graphqlSync({ schema, source, operationName: "B" });
const result3 = graphqlSync({ schema, source, operationName: "C" });
const result4 = graphqlSync({ schema, source, operationName: "D" });
console.log(JSON.stringify(result1.data));
console.log(JSON.stringify(result2.data));
console.log(JSON.stringify(result3.data));
console.log(JSON.stringify(result4.data));
</details>
This was raised against GraphQL.js back in 2016, but @IvanGoncharov closed it early last year stating that GraphQL.js conforms to the GraphQL Spec in this regard.
My proposal is that when a defaultValue is specified, the GraphQL implementation should coerce it to conform to the relevant type just like it does for runtime values as specified in Coercing Variable Values and Coercing Field Arguments.
This is validated for query documents (and schema defined as SDL), because:
Literal values must be compatible with the type expected in the position they are found as per the coercion rules defined in the Type System chapter. -- http://spec.graphql.org/draft/#sel-FALXDFDDAACFAhuF
But there doesn't seem to be any such assertion for GraphQL schemas defined in code.
GraphQL.js implementation: https://github.com/graphql/graphql-js/pull/3814
Timeline
- 2 commits pushed on 2025-07-01:
- leebyron committed "Merge branch 'main' into input-object-default-value"
- leebyron committed "Editorial: simplify optimization note"
- Added to WG agenda on 2025-06-05
- 2 commits pushed on 2025-05-22:
- benjie committed "Fix accidental mutation"
- benjie committed "Merge branch 'main' into input-object-default-value"
- Added to WG agenda on 2025-05-01
- Added to WG agenda on 2025-04-03
- 2 commits pushed on 2025-03-07:
- benjie committed "Merge branch 'main' into input-object-default-value"
- benjie committed "Rather than assertions, use return values."
- Added to WG agenda on 2023-12-07
- Mentioned in WG notes on 2023-12-01
- Added to WG agenda on 2023-06-01
- Mentioned in WG notes on 2023-06-01
- Added to WG agenda on 2023-03-02
- Mentioned in WG notes on 2023-03-01
- Added to WG agenda on 2023-02-02
- Mentioned in WG notes on 2023-02-01
- Commit pushed on 2023-01-31 by benjie: Merge branch 'main' into input-object-default-value
- 5 commits pushed on 2022-01-06:
- benjie committed "Rewrite default value cycle algorithm in the style of DetectFragmentC…"
- benjie committed "Rewrite input object cycle algorithm to match GraphQL.js implementation"
- benjie committed "Asterisks"
- benjie committed "Remove unnecessary step"
- benjie committed "Add non-normative note about memoizing default value coercion"
- Added to WG agenda on 2022-01-06
- Mentioned in WG notes on 2022-01-06
- Added to WG agenda on 2021-05-13
- Mentioned in WG notes on 2021-05-13
- Commit pushed on 2021-05-03 by benjie: Clarify the default value cycle detection logic
- Added to WG agenda on 2021-03-04
- Mentioned in WG notes on 2021-03-04
- 2 commits pushed on 2021-02-26:
- benjie committed "Reorder assertions to prevent infinite loop during coercion"
- benjie committed "Reduce diff"
- Commit pushed on 2021-02-19 by benjie: Fix 'must not cause an infinite loop' wording.
- Added to WG agenda on 2021-01-07
- Mentioned in WG notes on 2021-01-07
- Added to WG agenda on 2020-12-03
- Mentioned in WG notes on 2020-12-03
- Spec PR created on 2020-11-13 by benjie
- Commit pushed on 2020-11-13 by benjie: Default value coercion rules