iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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

Test yourself

Q1. A required scalar is denoted with…
Q2. Read-only entry points live under type…
Q3. Mutations are top-level under type…

Discussion

Loading…