Guides
GraphQL API
Every project gets a GraphQL schema, generated automatically from its collections and declared relationships - no schema to write or maintain by hand.
Query fields only - writes still go through the REST data endpoints. A GraphQL mutation layer is a planned follow-up, not yet available.Endpoint
A single POST endpoint per project runs any query against that project's current schema. The schema is rebuilt on every request from the project's live collections and relationships, so a field you just added in the console is queryable on your very next call:
POST /api/projects/{projectId}/graphql
Content-Type: application/json
X-API-Key: YOUR_API_KEY
{
"query": "{ posts { id title comments { body } } }"
}POST /api/projects/{projectId}/graphql
Content-Type: application/json
X-API-Key: YOUR_API_KEY
{
"query": "{ posts { id title comments { body } } }"
}Authentication is the same as any other project data request - an API key (X-API-Key) or a Bearer session token. The route requires project read access, the same as GET data reads; there is no separate, weaker GraphQL auth path.
Generated schema shape
Each collection becomes a GraphQL object type (PascalCased and singularized - a posts collection becomes type Post), with every declared field mapped to a scalar (number → Float, boolean → Boolean, everything else, including date, → String, dates as ISO-8601), plus id, createdAt, and updatedAt. Two query fields are generated per collection:
# List - camelCased plural of the collection slug
posts(filter: String, sort: String, limit: Int, offset: Int): [Post]
# Single by id - camelCased singular (falls back to "<name>ById" if that
# would collide with the list field name)
post(id: ID!): Post# List - camelCased plural of the collection slug
posts(filter: String, sort: String, limit: Int, offset: Int): [Post]
# Single by id - camelCased singular (falls back to "<name>ById" if that
# would collide with the list field name)
post(id: ID!): Postfilter takes the same JSON-encoded Mongo filter as REST's ?filter=. sort mirrors REST's ?sort= (e.g. -createdAt). limit defaults to 20 and is capped at 100; offset defaults to 0. A project with no collections declared yet still returns a valid (placeholder) schema rather than erroring on introspection.
Relationships as fields
Every declared relationship becomes a field on its source type, resolved through the exact same permission-scoped populate engine ?populate uses - not a separate implementation. A one-to-many or many-to-many relationship resolves to a list; many-to-one and one-to-one resolve to a single object or null. Nested relationship fields are batched per request (one query per level, regardless of how many parent rows), so this is cheap even for a deeply-linked query:
{
posts {
id
title
comments {
body
author {
name
}
}
tags {
name
}
}
}{
posts {
id
title
comments {
body
author {
name
}
}
tags {
name
}
}
}Permissions
GraphQL enforces the identical permission model REST does, field by field. A query field for a collection the caller cannot read fails with a FORBIDDEN error. A relationship field the caller cannot read - or that declares an allowedRoles restriction the caller doesn't satisfy - resolves to null or [] rather than a leaked document or an error, exactly like ?populate.
Query limits
A generated schema has no built-in cost limiter from the GraphQL spec itself, so the following bounds are enforced on every request before execution:
- Max query depth: 8 selection levels.
- Max field/alias count: 30 per operation.
- Max parsed tokens: 10,000 per query document.
- Execution timeout: 10 seconds.
- Rate limit: 100 requests per 60 seconds per caller, per project.
A query that exceeds the depth or alias budget is rejected with 400 and a QUERY_TOO_COMPLEX error code before any schema or database work runs. A query that runs past the execution timeout returns a QUERY_TIMEOUT error in the standard GraphQL errors array.
Response shape and errors
Standard GraphQL-over-HTTP semantics. A malformed request (missing or non-string query) is a 400. Once the query is at least parseable, execution responds 200, with any syntax, validation, or resolver errors in the standard { data, errors } shape every GraphQL client already expects - a per-field resolver failure is a normal partial result, not a transport-level failure.
{
"data": {
"posts": [
{ "id": "665f0a...c31", "title": "Hello world", "comments": [] }
]
}
}{
"data": {
"posts": [
{ "id": "665f0a...c31", "title": "Hello world", "comments": [] }
]
}
}Calling it from your app
There is no dedicated GraphQL client generated alongside the REST SDKs yet - call the endpoint with any standard GraphQL client (Apollo Client, graphql-request) or a plain HTTP request, the same way you'd call any other MUDBASE endpoint:
const res = await fetch(
`https://cloud.mudbase.dev/api/projects/${projectId}/graphql`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.MUDBASE_API_KEY,
},
body: JSON.stringify({
query: `{ posts { id title comments { body } } }`,
}),
},
);
const { data, errors } = await res.json();const res = await fetch(
`https://cloud.mudbase.dev/api/projects/${projectId}/graphql`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.MUDBASE_API_KEY,
},
body: JSON.stringify({
query: `{ posts { id title comments { body } } }`,
}),
},
);
const { data, errors } = await res.json();