Skip to content

GraphQL Input Types — Structured Mutation Arguments Explained

DodaTech Updated 2026-06-28 6 min read

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
â„šī¸ Info

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

  1. What keyword defines an input type in SDL?
  2. Can input types reference object types?
  3. Why separate Create and Update input types?
  4. Can input types have default values?
  5. What validation do input types provide automatically?

Answers:

  1. The input keyword. Input types use the same field syntax as object types but without resolvers.
  2. No — input types can only reference scalars, enums, and other input types. They cannot reference object types (types with resolvers).
  3. Create inputs typically require more fields (name, os). Update inputs make all fields optional since you're modifying existing data.
  4. No — GraphQL SDL does not support default values for input fields. Defaults must be handled in resolver logic.
  5. 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

Can I use the same name for a type and input type?

Yes — it's common to have Device (type) and DeviceInput (input). Some conventions use CreateDeviceInput and UpdateDeviceInput to be explicit.

Do input types support default values?

No — the GraphQL spec does not support default values in input type fields. Handle defaults in resolver logic: const interval = input.interval || 3600.

Can input types be used in query arguments?

Yes — input types work for any argument, not just mutations. For example, a complex filter: devices(filter: DeviceFilterInput!).

How do I handle file uploads in input types?

File uploads use the Upload scalar, which cannot be inside an input type. Accept Upload as a separate mutation argument: uploadScanFile(file: Upload!, deviceId: ID!).

Can input types extend other input types?

No — GraphQL does not support input type inheritance. Use composition (nested input types) or code generation to avoid duplication.

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
âŦ… Union Types
➡ Arguments Guide

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro