GraphQL Input Types â Structured Mutation Arguments Explained
In this tutorial, you will learn about Graphql Input Types. We cover key concepts, practical examples, and best practices to help you master this topic.
GraphQL input types are special object types that serve as structured arguments for mutations and queries, enabling complex data to be passed from client to server with type validation.
What You'll Learn
You will learn how to define input types in SDL, use them in mutation arguments, validate input data, handle nested inputs, and design effective input hierarchies.
Why Input Types Matter
Without input types, mutations with more than 2-3 arguments become unwieldy. Imagine createDevice(name: String!, os: String!, version: String, userId: ID!, tags: [String!], settings: JSON) â the argument list is long and ordering matters. Input types group related fields into a single structured object. DodaTech's Durga Antivirus Pro uses input types for every mutation â CreateDeviceInput, UpdateScanConfigInput, ThreatReportInput â keeping mutation signatures clean and enabling reusable validation.
flowchart LR
A["Mutation:\ncreateDevice(input: CreateDeviceInput!)"] --> B["CreateDeviceInput {\n name: String!\n os: String!\n version: String\n userId: ID!\n tags: [String!]\n}"]
B --> C["GraphQL validates\neach input field"]
C --> D["Resolver receives\nstructured input"]
D --> E["Business logic\nuses input"]
style A fill:#dbeafe,stroke:#2563eb
style B fill:#fef3c7,stroke:#d97706
Prerequisites: GraphQL schema design and mutation basics.
Defining Input Types
input CreateDeviceInput {
name: String!
os: String!
version: String
userId: ID!
tags: [String!]
settings: JSON
}
input UpdateDeviceInput {
name: String
os: String
version: String
tags: [String!]
}
input ThreatReportInput {
deviceId: ID!
threatName: String!
severity: Severity!
details: String
fileHash: String
}
Input types use the input keyword instead of type. They cannot have resolvers â they are pure data structures for argument passing.
Using Input Types in Mutations
type Mutation {
createDevice(input: CreateDeviceInput!): Device!
updateDevice(id: ID!, input: UpdateDeviceInput!): Device!
reportThreat(input: ThreatReportInput!): Threat!
batchCreateDevices(inputs: [CreateDeviceInput!]!): [Device!]!
}
mutation CreateNewDevice($input: CreateDeviceInput!) {
createDevice(input: $input) {
id
name
os
createdAt
}
}
# Variables
{
"input": {
"name": "Office-PC",
"os": "Windows 11",
"version": "22H2",
"userId": "user-001",
"tags": ["production", "development"],
"settings": { "autoUpdate": true, "scanInterval": 3600 }
}
}
# Expected response
{
"data": {
"createDevice": {
"id": "dev-007",
"name": "Office-PC",
"os": "Windows 11",
"createdAt": "2026-06-28T12:00:00Z"
}
}
}
Input Type Resolver Pattern
const resolvers = {
Mutation: {
createDevice: (_, { input }, context) => {
// Validate business rules
if (input.name.length < 2) {
throw new UserInputError('Name must be at least 2 characters');
}
if (input.version && !/^\d+\.\d+$/.test(input.version)) {
throw new UserInputError('Version must be in format "major.minor"');
}
// Create the device
const device = {
id: generateId(),
...input,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
return context.db.devices.create(device);
},
batchCreateDevices: async (_, { inputs }, context) => {
const results = [];
for (const input of inputs) {
// Validate each input
if (!input.name) {
throw new UserInputError('Name is required for all devices');
}
const device = {
id: generateId(),
...input,
createdAt: new Date().toISOString(),
};
results.push(await context.db.devices.create(device));
}
return results;
},
},
};
Nested Input Types
Input types can reference other input types, enabling complex nested structures:
input DeviceConfigInput {
autoUpdate: Boolean!
scanInterval: Int!
notifications: NotificationConfigInput
network: NetworkConfigInput
}
input NotificationConfigInput {
email: Boolean!
push: Boolean!
sms: Boolean!
quietHours: QuietHoursInput
}
input QuietHoursInput {
start: String!
end: String!
timezone: String!
}
input NetworkConfigInput {
proxyUrl: URL
dnsServers: [String!]
vpnRequired: Boolean
}
type Mutation {
updateDeviceConfig(id: ID!, config: DeviceConfigInput!): Device!
}
// Variables for nested input
{
"config": {
"autoUpdate": true,
"scanInterval": 3600,
"notifications": {
"email": true,
"push": false,
"sms": true,
"quietHours": {
"start": "22:00",
"end": "08:00",
"timezone": "America/New_York"
}
}
}
}
Input Type vs Type
# Output type â has resolvers, used in responses
type Device {
id: ID!
name: String!
createdAt: DateTime!
user: User! # Can reference other object types
}
# Input type â no resolvers, used for arguments
input CreateDeviceInput {
name: String!
userId: ID! # References scalar/ID, not User type
}
Key differences: input types cannot reference object types (only scalars, enums, or other input types). They cannot have resolvers. They cannot be used in response fields.
Common Mistakes
1. Using Object Types as Input Types
Object types (type) cannot be used as mutation arguments. You must define a parallel input type even if it has the same fields.
2. Not Reusing Input Types
If CreateDeviceInput and UpdateDeviceInput share fields, consider using a base input type pattern â though GraphQL doesn't support input inheritance, you can compose with nested inputs.
3. Forgetting Validation in Resolvers
Input types validate structure (types, nullability) but not business rules. Always validate in resolvers too â check ranges, uniqueness, and relationships.
4. One Giant Input Type
A single SaveDeviceInput with all optional fields for create and update makes validation complex. Separate create and update input types.
5. Input Types Circular References
Input types can reference other input types, but circular references (A â B â A) can cause infinite Recursion in some code generators.
Practice Questions
- What keyword defines an input type in SDL?
- Can input types reference object types?
- Why separate Create and Update input types?
- Can input types have default values?
- What validation do input types provide automatically?
Answers:
- The
inputkeyword. Input types use the same field syntax as object types but without resolvers. - No â input types can only reference scalars, enums, and other input types. They cannot reference object types (types with resolvers).
- Create inputs typically require more fields (name, os). Update inputs make all fields optional since you're modifying existing data.
- No â GraphQL SDL does not support default values for input fields. Defaults must be handled in resolver logic.
- Input types validate structure (field names exist), types (String vs Int), and nullability (required vs optional). They do not validate business rules.
Challenge: Design input types for DodaTech's complete device management system. Include inputs for creating devices, updating configurations, scheduling scans, managing user permissions, and batch operations. Implement resolver validation for all inputs.
FAQ
Mini Project
Create input types for DodaTech's threat reporting system. Include ThreatReportInput (deviceId, threatName, severity, details, fileHash), ThreatFilterInput (severity, dateRange, deviceId, status), BatchActionInput (threatIds, action), and a ScanScheduleInput with nested time and recurrence configuration.
What's Next
| Topic | Description |
|---|---|
| Arguments Guide | Query and field arguments in depth |
| Directives Guide | Schema and query directives |
| Mutations Guide | Mutation patterns and practices |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro