SDL (Schema Language)
Schema Definition Language is the text format you write your GraphQL schema in. SDL describes types, fields, arguments, and directives in a syntax designed for readability — one source of truth that humans and tools both consume.
Types, scalars, args, directives, docs
EXAMPLE
// 1) A complete tiny schema
const schema = `
# A blog API
""" Top-level entry points """
type Query {
me: User
user(id: ID!): User
posts(first: Int = 20, after: String, status: PostStatus): PostConnection!
}
type Mutation {
createPost(input: CreatePostInput!): Post!
publishPost(id: ID!): Post!
}
type Subscription {
postPublished: Post!
}
""" A logged-in user. """
type User {
id: ID!
email: String!
name: String
role: Role!
posts(first: Int = 10): [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
body: String!
status: PostStatus!
author: User!
tags: [String!]!
publishedAt: DateTime
}
""" Standard cursor-based pagination connection. """
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
}
type PostEdge {
cursor: String!
node: Post!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
enum Role { GUEST USER EDITOR ADMIN }
enum PostStatus { DRAFT PUBLISHED ARCHIVED }
input CreatePostInput {
title: String!
body: String!
tags: [String!] = []
}
scalar DateTime
`;
// 2) Built-in scalars
// Int — 32-bit signed integer
// Float — double-precision floating point
// String — UTF-8
// Boolean — true / false
// ID — usually serialised as String; semantically opaque identifier
// 3) Custom scalars
// scalar DateTime
// scalar JSON
// scalar UUID
// scalar EmailAddress
//
// In resolvers, supply a parseValue / serialize / parseLiteral to validate.
// 4) Non-null + list combinations
// String nullable scalar
// String! non-null scalar
// [String] nullable list of nullable scalars
// [String!] nullable list of non-null scalars
// [String!]! non-null list of non-null scalars ← typical for fields like 'tags'
// 5) Input types — for mutation arguments
input CreatePostInput { title: String! body: String! tags: [String!] }
// Input objects can be nested, can't have circular dependencies, and can't include unions / interfaces.
// 6) Enums — closed set of string values
enum Role { GUEST USER EDITOR ADMIN }
// Enums serialise as their NAME, not an integer. Clients get type-safety; you can add values
// (and clients keep working), but renaming a value is a breaking change.
// 7) Interfaces
interface Node { id: ID! }
type User implements Node { id: ID! email: String! }
type Post implements Node { id: ID! title: String! }
type Query { node(id: ID!): Node }
// 8) Unions — mutually exclusive types
union SearchResult = User | Post | Comment
type Query { search(q: String!): [SearchResult!]! }
// 9) Field arguments + defaults
type Query {
posts(
first: Int = 20,
after: String,
status: PostStatus = PUBLISHED,
): PostConnection!
}
// 10) Directives — metadata that tools and the server use
directive @auth(role: Role = USER) on FIELD_DEFINITION | OBJECT
directive @cost(value: Int!) on FIELD_DEFINITION
directive @deprecated(reason: String!) on FIELD_DEFINITION | ENUM_VALUE
type Mutation {
deletePost(id: ID!): Boolean! @auth(role: EDITOR)
}
type User {
oldField: String @deprecated(reason: "Use newField. Removal: 2025-12-01")
}
// 11) Description strings — show in introspection + docs
"""
Return the currently signed-in user, or null if anonymous.
Prefer this over user(id) when fetching the viewer's own profile.
"""
type Query {
me: User
}
// Triple-quoted descriptions render in GraphiQL/Apollo Studio.
// They are the documentation that ships with the schema.
// 12) Schema extension — split across files
// posts.gql
type Post { id: ID! title: String! body: String! }
// schema.gql
extend type Query { posts: [Post!]! }
// Useful when generating from multiple modules; merge with @graphql-tools/load.
// 13) Federation (Apollo) — distributed schema
type Product @key(fields: "id") { id: ID! name: String! }
// @key, @requires, @provides, @external — federation directives.
// Lets multiple services contribute fields to the same type.
// 14) Generating from SDL
// • @graphql-codegen/cli — generate TypeScript types and React Query / Apollo hooks from .gql files
// • Strawberry / Hot Chocolate — schema-first SDL in Python / .NET
// • Hasura, PostGraphile — generate SDL from a DB schema (often the wrong abstraction; review carefully)
// 15) Design tips
// • Make non-null fields the default; opt out for genuinely optional ones
// • Lists usually [T!]! — neither the list nor items are null
// • Use input types even for single fields — mutations can grow inputs over time without breaking changes
// • Pluralise list-returning fields: 'posts' not 'post'
// • Connection pattern for pagination ('PostConnection { edges { node, cursor }, pageInfo }')
// • Use ISO strings for DateTime; declare it as a scalar; pick a single library to (de)serialise
// • Use ID for opaque identifiers and keep them String at the wire — let clients treat them as opaque
// • Document with description strings; not separate Markdown files
// 16) Backwards-compat rules of thumb
// • Adding a new type, field, or arg with a default — safe
// • Making a nullable field non-null — BREAKING
// • Removing or renaming a field — BREAKING; use @deprecated first
// • Changing argument type or making an optional arg required — BREAKING
// • Reordering enum values — usually safe; renaming a value — BREAKING
// 17) Common bugs
// • Forgetting ! on resolver return types that always have a value — clients write defensive code
// • Defining the same field on multiple types with different shapes — interface or union needed
// • Enum value names with mixed case ('admin') — convention is SCREAMING_SNAKE_CASE
// • Schema file with stray BOM or smart quotes — parse error
// • Using a scalar for something that should be an enum — clients can pass garbage
// • Recursive input — input types CANNOT have themselves in their fields
Why it matters
SDL is the contract: prefer non-null fields, use [T!]! for lists, and lean on description strings as the documentation surface so every client tool shows the same docs you wrote. Plan deprecations with @deprecated well before a removal date — breaking changes are technically allowed but socially expensive.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
type Query { users: [User!]! }
type User { id: ID! name: String! email: String }
Try it Yourself »
Exercise
Mark a non-null scalar with…
name: String
A single character.
Discussion
Loading…