Photo by chansu shin on Unsplash

On this page

This chapter follows Schemas and SDL.

GraphQL types form a graph: object types are nodes with named fields, and scalars are the terminal values execution must eventually produce. Understanding nullability and polymorphism (interface, union) prevents subtle client bugs and bad cache behavior.

Object types and fields

An object type groups fields. Each field has a return type and optional arguments:

type Author {
id: ID!
name: String!
books(first: Int = 10): [Book!]!
}

Every object type implicitly includes __typename, a string naming the runtime type. Clients query it when a field returns an interface or union:

{
author(id: "1") {
__typename
name
}
}

Resolvers for object fields receive the parent value returned by the parent field’s resolver (often a database row or DTO).

Built-in scalars

Scalars represent leaf values. The spec defines:

Scalar Meaning
String UTF-8 text
Int 32-bit signed integer
Float signed double
Boolean true or false
ID opaque identifier, serialized as string

Execution completes when every selected field resolves to a scalar (or null where allowed).

Custom scalars

Domains often need dates, decimals, or URLs. Declare a custom scalar in SDL:

scalar DateTime

The server must implement:

  • serialize — value from resolver → JSON for the response
  • parseValue — variable input → internal value
  • parseLiteral — AST literal in the query → internal value

Link machine-readable semantics with @specifiedBy(url: "...") when a JSON Schema or RFC defines the format.

In NestJS, register a custom scalar class with @Scalar() and wire it in the module; Apollo Server and Yoga expose similar hooks.

Enumerations

Enums restrict a field or argument to a fixed set of values:

enum OrderStatus {
PENDING
SHIPPED
CANCELLED
}

Enum values are conventionally ALL_CAPS. Adding a new enum value is usually backward compatible for queries but can break exhaustive switch statements in clients—a theme in the schema evolution chapters.

Lists and non-null

Type modifiers stack:

type Example {
requiredList: [String!]! # list cannot be null; elements cannot be null
optionalList: [String] # list or elements may be null
nullableField: String # field may be null
}

Misplaced ! is a common source of production errors: a resolver that throws on a non-null field nulls out ancestors per the spec’s bubbling rules.

Interfaces

An interface declares fields implementing types must provide:

interface Node {
id: ID!
}
type User implements Node {
id: ID!
email: String!
}

Queries can return the interface; clients use inline fragments (... on User) to reach type-specific fields.

Unions

A union expresses “one of these object types” without shared fields:

union SearchResult = Book | Author
type Query {
search(text: String!): [SearchResult!]!
}

Clients must query __typename and spread fragments for each member type they care about.

Next: Input types and arguments.

Check your understanding

Question 1 of 7

0 of 7 checked

  1. 1. Which meta-field does every object type expose?

Search articles

Type a word to search published articles.

Open the search page