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
.graphqlfiles
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
| Concept | Description |
|---|---|
| Introspection | Querying the schema itself |
| __schema | Root field for full schema metadata |
| __type | Query a specific type by name |
| Type Kind | Category of type (OBJECT, SCALAR, ENUM, etc.) |
| ofType | Unwraps 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.