Skip to content

GraphQL Channel

Ask for exactly the fields you need. The GraphQL channel generates its schema from your entry type configuration, so the API always matches your directory.

Enable the channel:

Terminal window
SCRIBE_GRAPHQL_ENABLED=true
URLWhat you get
/graphqlQuery endpoint (POST) and GraphiQL IDE (GET)
/graphql.sdlSchema in SDL format
/graphql.jsonSchema as JSON introspection

Open /graphql in a browser to explore the schema interactively — the GraphiQL UI includes a schema browser, autocompletion, and query history.

GraphQL types and fields are derived from your entry type configuration. Hyphens and underscores are normalized, so organizational-unit, organizational_unit, and OrganizationalUnit all produce the same schema:

Config nameGraphQL typeLookup fieldCollection field
organizational-unitOrganizationalUnitorganizationalUnit(id: ...)organizationalUnits(...)
user or usersUseruser(id: ...)users(...)

Duplicate type names that produce the same GraphQL symbol in any category (type name, lookup field, or collection field) cause startup failure. Examples: foo-bar and foo_bar collide on all three; user and users collide because both normalize to the same lookup and collection names. Names starting with digits or other invalid GraphQL characters are normalized with an underscore prefix.

Attribute names use the same normalization: hyphens and underscores become camelCase. For example, mail-Primary appears as mailPrimary in the schema and in queries. If two attributes in the same type normalize to the same GraphQL field name (e.g. a-b and aB both map to aB), startup fails with a collision error before the schema is served. Use distinct catalog attribute names so each normalizes to a unique field.

Use the GraphiQL schema explorer or fetch /graphql.sdl for exact names.

query {
users(filter: "department=Engineering", first: 25, sort: "-modifyTimestamp") {
nodes {
entryDN
cn
mail
}
pageInfo {
hasNextPage
endCursor
}
}
}

Look up by any identifier format:

query {
user(id: "dXNlcjo5aXg") {
entryDN
cn
mail
memberOf
}
}

When you don’t know the entry type, use node:

query {
node(id: "dXNlcjo5aXg") {
... on User { cn, mail }
... on Group { cn, member }
}
}

Filter syntax matches the REST API — use FleX, JSON, SCIM, or LDAP format:

users(filter: "department=Engineering AND active=true")

Relay-style cursor pagination. Use first/after to page forward:

query {
users(first: 25, after: "Y3Vyc29yOjI1") {
nodes { cn }
pageInfo { hasNextPage endCursor }
}
}

Use count(mode: ESTIMATED) for fast approximate counts, or count(mode: EXACT) when you need precision.

View an entry’s state at any moment. The at argument accepts timestamps, durations, cursors, and relative expressions like now-1h. See Temporal references for all accepted formats.

query {
user(id: "dXNlcjo5aXg", at: "2024-01-15T10:00:00Z") {
cn
mail
department
}
}

Query changes across all entry types or per-entry:

query {
changes(range: "24h", type: [MODIFY], first: 100) {
nodes {
id
type
timestamp
entryType
data {
... on ModifyData {
diff { attribute operation value }
}
}
}
pageInfo { hasNextPage endCursor }
}
}

Filter by time range (range, since/until), event type (ADD, MODIFY, MOVE, DELETE), entry type, or affected attributes. Sort by timestamp or -timestamp.

Each entry also has a changes field for its own history:

user(id: "dXNlcjo5aXg") { changes(first: 5) { nodes { type timestamp } } }

Change events support three data formats: diff, patch, and merge.

Persisted query flow

Cache queries by hash to reduce payload size

Client
Scribe
1
POST /graphql { hash + query }
2
Response (query cached)
3
POST /graphql { hash only }
4
Response (from cache)
5
GET /graphql?hash=abc123
6
PERSISTED_QUERY_NOT_FOUND
7
Resend with full query

APQ caches query strings server-side by their SHA-256 hash. Clients send the 64-byte hash instead of the full query text — typical queries run 10 KB or more, while a hash is always 64 bytes.

On first request, the client sends both the query and its hash. After that, only the hash is needed. If the cache evicts the query, the server responds with PERSISTED_QUERY_NOT_FOUND and the client re-registers it.

GET requests with the hash enable browser and CDN caching. Apollo Client, urql, and other GraphQL clients handle the APQ flow automatically. See Configuration Reference for cache size and TTL settings.

Three layers protect against abusive queries:

LayerProtection
Parser limitsReject oversized payloads before parsing
Depth limitsReject deeply nested queries
Complexity limitsReject queries selecting too many fields

Defaults work for most deployments. See Configuration Reference for tuning.

Timestamp fields (createTimestamp, modifyTimestamp, verifiedTimestamp) accept a format argument with presets like ISO, UNIX, UNIX_MS, RFC_1123, LDAP, or custom DateTimeFormatter patterns:

user(id: "...") { modifyTimestamp(format: "UNIX_MS") }

See Signals for metrics, Error Handling for error codes.