Input Types vs. Custom Scalars: Which One When?
AI generated
{ }
type
GraphQL · Schema Design · Input Types · Custom Scalars · Validation
Input Types vs. Custom Scalars:
Which Approach, When?

In GraphQL schema design, the question of input types versus custom scalars leads to regular discussions. Both solve similar problems in different ways. The difference does not lie in capability, but in semantic intent: does the type describe a structure or a value range?

13 min read Input Types · Custom Scalars · Validation · Enums · Magento Schema Design · Type Safety · Server-Side Validation

1. The Core Question: Structure or Value Range?

The decision between Input Types and Custom Scalars comes down to one simple core question: is the value that a mutation or query accepts a structured object with multiple fields, or a single value with a specific format and value range? Input Types describe the first category, Custom Scalars the second. Both can contain validation logic, but they do so in fundamentally different ways and at different points in the system.

This sounds like a technical distinction without practical consequence, but it is decisive for the readability of the schema and the kind of error messages clients receive. A poorly chosen custom scalar that should really have been an input type makes the schema harder to understand and pushes validation logic into the resolver, where it does not belong. An input type that should really have been a custom scalar creates unnecessary nesting depth and makes life harder for clients.

2. Input Types: Structured Data as Types in the Schema

An Input Type is a named type in the GraphQL schema that can only be used as input, it must never appear in output fields. That distinguishes it from regular object types, which can only be used as output. Input Types are suited to every case where multiple related fields should be passed to a mutation or a query resolver as a unit. Typical examples: address data, filter combinations, pagination parameters or product configurations.

The main advantage of Input Types lies in self-documentation: the schema immediately shows which fields are expected in a given context, which of them are optional and what types they have. Magento uses Input Types extensively: ProductAttributeFilterInput, CustomerInput and AddProductsToCartInput are established examples that show how structured input is modeled cleanly in a complex API.


# Input Type example: structured address data for checkout mutations
# Multiple related fields grouped as a reusable input type

input AddressInput {
  firstname: String!
  lastname: String!
  street: [String!]!       # array: street lines
  city: String!
  postcode: String!
  country_code: String!
  telephone: String
  company: String
  vat_id: String
  save_in_address_book: Boolean
}

# Input Type for product filter: reusable, well-documented
input ProductAttributeFilterInput {
  category_id: FilterEqualTypeInput
  price: FilterRangeTypeInput
  sku: FilterEqualTypeInput
  name: FilterMatchTypeInput
}

input FilterRangeTypeInput {
  from: String
  to: String
}

# Usage in mutation
type Mutation {
  setBillingAddressOnCart(
    input: SetBillingAddressOnCartInput!
  ): SetBillingAddressOnCartOutput
}

3. Custom Scalars: Semantically Meaningful Single Values

Custom Scalars extend the built-in scalars (String, Int, Float, Boolean, ID) with domain-specific value types. An Email scalar is a string with a specific structure and a value range defined by the email format. A DateTime scalar is a string in ISO 8601 format. A URL scalar is a string with a valid URL scheme. Validation happens in the scalar resolver on the server, not in the field resolver.

The decisive advantage: Custom Scalars communicate semantics directly in the schema. A field of type Email is self-documenting, every client developer immediately understands what is expected without reading the documentation. A field of type String that expects an email address, by contrast, is only understandable through documentation comments. Custom Scalars also work as output types, which Input Types cannot, that makes them the right choice for fields that are both read and written.


# Custom Scalar definitions: semantic types for domain-specific values
# Server-side validation happens in the scalar resolver

scalar Email       # validated RFC 5322 email address
scalar URL         # validated absolute URL with scheme
scalar DateTime    # ISO 8601 datetime string
scalar PhoneE164   # E.164 international phone number format
scalar Money       # decimal with 2 fractional digits
scalar SKU         # alphanumeric product identifier pattern

# Usage: scalars work as both input and output types
type Customer {
  email: Email!                  # output: read and write
  phone: PhoneE164               # output: read and write
  created_at: DateTime!          # output: read only
}

type Mutation {
  createCustomer(
    email: Email!                # input: validated at scalar level
    phone: PhoneE164
  ): Customer
}

# Input Type for the same use case (more verbose, less semantic)
# (only use when multiple related fields justify the structure)
input CustomerCreateInput {
  email: Email!
  phone: PhoneE164
  firstname: String!
  lastname: String!
}

4. Validation: Where Input Types and Scalars Diverge

With Input Types, validation happens at field level: required fields, types and the basic structure are checked by the GraphQL execution framework before the resolver is called. Validation logic that goes beyond that, such as "the date must be in the future" or "the price range must contain positive numbers", has to be implemented in the resolver. That is correct, because these rules carry business meaning that the schema alone cannot express.

With Custom Scalars, validation happens in the scalar resolver before the field resolver is called. An Email scalar that receives an invalid value fails immediately with a schema validation error, without ever touching the resolver. That is more efficient and produces more consistent error messages. The boundary lies where validation rules depend on context: "this email address is already registered" is not a scalar error, it is an application error in the resolver.

5. Enums as a Third Option: When the Value Range Is Finite

Enums are the natural choice when a field's value range is finite and fully known at the schema level. Instead of using a String with the comment "allowed values: asc, desc", you define an enum SortDirection { ASC DESC }. The schema validates the value automatically, and on invalid input the client receives a clear error message before the resolver is even called.

In Magento you see enums for sort directions, product visibility and order status. The critical design difference from Custom Scalars: an enum value cannot be validated in the custom-scalar sense, it is either one of the known values or invalid. Custom Scalars are for value ranges that cannot be enumerated in the schema: email addresses, URLs and dates are, in principle, infinitely many valid values. Enums cover the case where the possible values are known and stable at design time.

6. Input Types and Custom Scalars in Magento GraphQL

Magento uses Input Types extensively and Custom Scalars selectively. The built-in input types such as ProductAttributeFilterInput, AddSimpleProductsToCartInput and CustomerAddressInput are good reference examples of structured input in a complex e-commerce API. For your own modules, you should follow the same convention: use an Input Type when a mutation has several related parameters, and name the input object with the suffix Input.

Custom Scalars are used sparingly in Magento. The system mainly relies on the built-in type String for values that could be more semantically specific. In your own modules you can define Custom Scalars for domain-specific values, an SKU scalar for the product identifier format or a Barcode scalar for barcode strings. The SDL definition is simple, but the resolver implementation in PHP has to include the serialization and validation logic.


# Magento-style input type for a custom module
# Convention: suffix "Input", all related fields grouped

input CreateLeadInput {
  firstname: String!
  lastname: String!
  email: String!          # ideally: Email scalar, but String is common in Magento
  phone: String
  message: String!
  product_sku: String     # reference to a product
  preferred_contact_date: String  # ideally: Date scalar
  store_id: Int
}

# Response type for mutations (not an input type)
type CreateLeadOutput {
  lead_id: ID!
  status: LeadStatus!
  created_at: String!
}

enum LeadStatus {
  PENDING
  CONFIRMED
  CLOSED
  REJECTED
}

type Mutation {
  createLead(input: CreateLeadInput!): CreateLeadOutput
}

type Query {
  # Input type for filter: reusable across queries
  leads(filter: LeadFilterInput, pageSize: Int, currentPage: Int): LeadsOutput
}

input LeadFilterInput {
  status: FilterEqualTypeInput
  created_at: FilterRangeTypeInput
  email: FilterMatchTypeInput
}

7. Code Generation and Type Safety with Both Approaches

Code generation tools such as GraphQL Code Generator produce TypeScript interfaces from Input Types that map 1:1 to the schema structure. That means frontend developers get complete types for all input forms and mutation calls, without having to maintain them by hand. With Custom Scalars it is a bit more involved: the generated TypeScript type for a custom scalar defaults to any, because the TypeScript compiler has no way of knowing what the scalar represents. You configure in the code generator config which TypeScript type corresponds to which scalar, Email maps to string, DateTime to Date or string.

The practical tip for teams that use code generation: Custom Scalars with carefully defined TypeScript mappings are the cleanest way to carry type safety from the schema level all the way into client code. A DateTime scalar mapped to TypeScript Date ensures that the compiler catches type errors when a developer accidentally passes a plain string there.

8. Common Design Mistakes and How to Avoid Them

The most common mistake with Input Types is the "God Input Object": a single giant input object that contains every possible field of a mutation as optional. That sounds flexible, but it is hard to understand and hard to validate. Which fields are relevant for which use case? Which combinations make sense? Better: separate Input Types for different mutation paths, even if fields overlap. A CreateCustomerInput and an UpdateCustomerInput can have similar fields but different non-null requirements.

The most common mistake with Custom Scalars is creating scalars for values that should really be enums: a Status scalar with the values "active", "inactive", "pending" is not a good idea, because the value range is finite and known to the schema. That is what enums are for. Custom Scalars are for value ranges that cannot be meaningfully enumerated. Another mistake: defining a custom scalar but not implementing any validation logic. An Email scalar that accepts any string is worse than a plain String, because it creates false expectations.

Scenario Wrong Right Reason
Email address String Email (Custom Scalar) Semantics visible in the schema
Sort direction String (asc/desc) enum SortEnum { ASC DESC } Finite value range
Checkout address 5 separate arguments AddressInput Related fields belong together
Date filter range String (undocumented) DateTime Scalar Enforce and document the format
Status value Custom Scalar Status enum OrderStatus { ... } Value range is finite and known

9. Direct Comparison: Which One When?

The decision rule is simple once internalized: use an Input Type when several related fields are passed together as a unit and when the structure itself should be part of the API documentation. Use a Custom Scalar when a single value has specific semantics that go beyond "is a string", and when that value can be validated at the scalar level. Use an Enum when the value range is finite and fully known at design time.

In practice, the most common correct combination is this: an Input Type contains fields that are Custom Scalars or Enums. A CreateCustomerInput has a field email: Email! (Custom Scalar), a field status: CustomerStatus (Enum) and a field address: AddressInput (nested Input Type). This combination uses all three concepts in the right place and produces a schema that is self-explanatory without comments.

Input Types vs. Custom Scalars: The Essentials at a Glance

Input Types

For several related fields grouped as a unit. Usable only as input. Validation happens in the resolver, ideal for addresses, filters and pagination.

Custom Scalars

For single values with a specific format and value range. Usable as both input and output. Validation happens in the scalar resolver, ideal for email, URL, DateTime.

Enums

For finite, known value ranges. Schema validation happens automatically. Better than a Custom Scalar for status values and direction fields.

Combination

Input Types contain Custom Scalars and Enums as fields, that produces self-documenting schemas without redundant comments.

10. Summary

Input Types, Custom Scalars and Enums are not mutually exclusive alternatives, they are tools for different design problems. Input Types structure related input fields and make mutations readable. Custom Scalars make the value range of individual fields explicit and validatable at the schema level. Enums close off finite value ranges within the schema. Anyone who applies these three concepts deliberately produces schemas that are understandable without extensive documentation.

In Magento projects, the most common catch-up area is Custom Scalars: many fields that hold semantically meaningful values (email, URL, date) are modeled as String. That is not wrong, but it is a missed opportunity to make the schema self-explanatory. For your own modules, it is worth defining Custom Scalars for domain-specific values, the effort is small, and the benefit for schema readability and validatability is lasting.

11. FAQ: Input Types vs. Custom Scalars, Which One When?

1Difference between Input Type and Custom Scalar?
Input Types are structured objects with multiple fields. Custom Scalars are single values with a specific format and scalar-level validation.
2Can a Custom Scalar be used as an output type?
Yes. Custom Scalars work as both input and output. Input Types only as input, they must not appear in output fields.
3Enum instead of Custom Scalar?
Whenever the value range is finite and known at design time. Status, directions, types go to Enum. Email, URL, date go to Custom Scalar.
4Where does Custom Scalar validation happen?
In the scalar resolver, before the field resolver is called. More efficient than resolver-level validation, invalid values are caught early.
5Custom Scalars in Input Type fields?
Yes, recommended: CreateCustomerInput with email: Email! (Scalar), address: AddressInput (nested Input) and status: CustomerStatus (Enum), all concepts in the right place.
6Defining a Custom Scalar in Magento?
In schema.graphqls with 'scalar Email', the resolver implements CustomScalarInterface, serialization, parsing and literal handling need to be defined.
7What is a God Input Object?
A giant input object with every possible field as optional. Hard to understand and validate. Better: separate Input Types for different use cases.
8Custom Scalars with code generation?
Default type is 'any'. TypeScript mapping in the generator config: Email to string, DateTime to Date. Enables end-to-end type safety down to the client code.
9A Custom Scalar for every value?
Only when validation and semantics matter: email, URL, DateTime, domain-specific formats, yes. Arbitrary strings without a format requirement, String is enough.
10Why does Magento use few Custom Scalars?
Grew historically, many fields were modeled as String. For your own modules, introducing Custom Scalars for domain-specific values is recommended.