Skip to main content

Introspection

Discover how GraphQL APIs describe themselves through introspection.


What is Introspection?

Introspection is GraphQL's built-in mechanism for querying the schema itself. Every GraphQL server can answer questions about its own types, fields, and capabilities.

This is what powers:

  • Auto-complete in GraphiQL and other IDEs
  • Documentation generators
  • Client code generators
  • Schema validation tools
┌─────────────────────────────────────────────────────────────────────┐
│ INTROSPECTION POWER │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Normal Query: │
│ "Give me data from your database" │
│ │
│ Introspection Query: │
│ "Tell me about yourself - what types do you have?" │
│ "What fields does the Movie type have?" │
│ "What arguments does this query accept?" │
│ │
│ Result: Self-documenting, discoverable APIs │
│ │
└─────────────────────────────────────────────────────────────────────┘

The __schema Query

Every GraphQL API has a special __schema field that returns schema metadata:

query IntrospectSchema {
__schema {
types {
name
kind
description
}
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
}
}

Response (abbreviated):

{
"data": {
"__schema": {
"types": [
{ "name": "Movie", "kind": "OBJECT", "description": "A movie in the catalog" },
{ "name": "String", "kind": "SCALAR", "description": "..." },
{ "name": "Query", "kind": "OBJECT", "description": null }
],
"queryType": { "name": "Query" },
"mutationType": { "name": "Mutation" },
"subscriptionType": { "name": "Subscription" }
}
}
}

The __type Query

Query details about a specific type:

query IntrospectMovieType {
__type(name: "Movie") {
name
kind
description
fields {
name
description
type {
name
kind
ofType {
name
kind
}
}
args {
name
type {
name
}
defaultValue
}
}
}
}

Response:

{
"data": {
"__type": {
"name": "Movie",
"kind": "OBJECT",
"description": "A movie in the catalog",
"fields": [
{
"name": "id",
"description": "Unique identifier",
"type": { "name": null, "kind": "NON_NULL", "ofType": { "name": "ID", "kind": "SCALAR" } },
"args": []
},
{
"name": "title",
"description": "The movie's title",
"type": { "name": null, "kind": "NON_NULL", "ofType": { "name": "String", "kind": "SCALAR" } },
"args": []
},
{
"name": "reviews",
"description": "User reviews",
"type": { "name": null, "kind": "NON_NULL", "ofType": { "name": null, "kind": "LIST" } },
"args": [
{ "name": "limit", "type": { "name": "Int" }, "defaultValue": "10" }
]
}
]
}
}
}

Type Kinds

GraphQL types are categorized by kind:

enum __TypeKind {
SCALAR # Int, String, Boolean, Float, ID, custom scalars
OBJECT # User-defined types like Movie, Review
INTERFACE # Abstract types that other types implement
UNION # A type that could be one of several types
ENUM # A fixed set of values
INPUT_OBJECT # Input types for arguments
LIST # A list of another type
NON_NULL # A wrapper indicating non-nullability
}

Understanding Type Wrappers

GraphQL uses wrapper types for lists and non-null:

┌─────────────────────────────────────────────────────────────────────┐
│ TYPE WRAPPER STRUCTURE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Schema: title: String! │
│ Introspection: │
│ type: { kind: "NON_NULL", ofType: { kind: "SCALAR", name: "String" } }│
│ │
│ Schema: genres: [String!]! │
│ Introspection: │
│ type: { │
│ kind: "NON_NULL", │
│ ofType: { │
│ kind: "LIST", │
│ ofType: { │
│ kind: "NON_NULL", │
│ ofType: { kind: "SCALAR", name: "String" } │
│ } │
│ } │
│ } │
│ │
│ Read from outside in: NON_NULL → LIST → NON_NULL → SCALAR │
│ │
└─────────────────────────────────────────────────────────────────────┘

Introspecting Enums

query IntrospectGenreEnum {
__type(name: "Genre") {
name
kind
enumValues {
name
description
isDeprecated
deprecationReason
}
}
}

Response:

{
"data": {
"__type": {
"name": "Genre",
"kind": "ENUM",
"enumValues": [
{ "name": "ACTION", "description": "Action films", "isDeprecated": false },
{ "name": "COMEDY", "description": "Comedy films", "isDeprecated": false },
{ "name": "SCIFI", "description": "Science fiction", "isDeprecated": false },
{ "name": "ADVENTURE", "description": null, "isDeprecated": true, "deprecationReason": "Use ACTION instead" }
]
}
}
}

Introspecting Input Types

query IntrospectInput {
__type(name: "CreateMovieInput") {
name
kind
inputFields {
name
description
type {
name
kind
ofType {
name
}
}
defaultValue
}
}
}

The Full Introspection Query

Tools like GraphiQL use a comprehensive introspection query to fetch everything:

query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}

fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}

fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}

fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}

This query is used by most GraphQL tooling to understand a schema.


Tools Powered by Introspection

IDE Extensions

  • GraphiQL: Auto-complete, documentation explorer
  • GraphQL Playground: Query editor with schema exploration
  • VS Code GraphQL: IntelliSense for .graphql files

Code Generators

  • GraphQL Code Generator: TypeScript types, React hooks
  • Apollo Codegen: Swift, Kotlin, TypeScript
  • graphql-java-codegen: Java classes from schema

Documentation

  • GraphQL Voyager: Interactive schema visualization
  • SpectaQL: Static documentation generator
  • GraphDoc: Auto-generated docs

Validation & Testing

  • graphql-inspector: Schema change detection
  • Apollo Studio: Schema registry, breaking change detection

Security Considerations

Introspection exposes your entire schema. In production:

Option 1: Disable Introspection

┌─────────────────────────────────────────────────────────────────────┐
│ INTROSPECTION SECURITY │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Development: Introspection ON (for tooling) │
│ Production: Introspection OFF (for security) │
│ │
│ Why disable in production? │
│ - Hides schema structure from attackers │
│ - Prevents enumeration of all types/fields │
│ - Reduces attack surface │
│ │
│ Trade-off: │
│ - Client tooling won't auto-complete │
│ - Need alternative documentation │
│ │
└─────────────────────────────────────────────────────────────────────┘

Option 2: Allow Only for Authenticated Users

Some APIs allow introspection only for authenticated developers or admin users.

Option 3: Rate Limit Introspection

Allow introspection but rate-limit it heavily to prevent abuse.


Introspection Best Practices

1. Document Your Schema

Introspection exposes descriptions. Write them!

"""
A movie in the catalog.
Contains details about films including ratings and reviews.
"""
type Movie {
"Unique identifier for the movie"
id: ID!

"The movie's display title"
title: String!
}

2. Mark Deprecations

type Movie {
"Use `genres` instead"
genre: String @deprecated(reason: "Replaced by genres array")

genres: [Genre!]!
}

Deprecated fields appear in introspection with isDeprecated: true.

3. Use in CI/CD

# Check for breaking changes before deploy
graphql-inspector diff old-schema.graphql new-schema.graphql

Summary

ConceptDescription
IntrospectionQuerying the schema itself
__schemaRoot field for full schema metadata
__typeQuery a specific type by name
Type KindCategory of type (OBJECT, SCALAR, ENUM, etc.)
ofTypeUnwraps LIST and NON_NULL wrappers

What's Next?

In the next chapter, we'll explore Error Handling - how GraphQL reports errors and how to design for graceful failures.