Query published content with GraphQL#
Contoprix exposes a tenant-specific GraphQL delivery endpoint at:
POST /graphqlGraphQL is useful when one screen needs an exact combination of published page, navigation, content, relation, or media fields. REST and @contoprix/client are still the simplest choice for common page and content delivery.
Before you send a query#
Create an API client for the intended website with the graphql:read scope. This is separate from delivery:read; a REST delivery key alone is rejected by /graphql.
The request uses a delivery key in the x-contoprix-delivery-key header, or an SDK bearer access token with the same scope. Keep either credential on the server.
const response = await fetch(new URL("/graphql", process.env.CONTOPRIX_BASE_URL!), {
method: "POST",
headers: {
"content-type": "application/json",
"x-contoprix-delivery-key": process.env.CONTOPRIX_GRAPHQL_KEY!,
},
body: JSON.stringify({
query: `query Site { site { id name domain } }`,
}),
cache: "no-store",
});Use a separate CONTOPRIX_GRAPHQL_KEY when you want to make the required scope obvious. It can be the same physical API client as other server-side calls only if that client intentionally has both scopes.
Start with stable system fields#
These root fields exist for every tenant:
| Root field | What it returns |
|---|---|
site | Website identity and default language |
navigation(locale) | Published navigation items |
page(path, locale) | A published page by path |
pageById(id, locale) | A published page by ID |
Here is a safe first query that works without knowing a custom content model:
query SiteNavigation($locale: String) {
site {
id
name
code
domain
defaultLanguageCode
}
navigation(locale: $locale) {
id
name
url
openInNewTab
children {
id
name
url
}
}
}{ "locale": "en" }To fetch a published page by path:
query PageByPath($path: String!, $locale: String) {
page(path: $path, locale: $locale) {
id
name
slug
locale
blocks {
id
kind
regionCode
content {
__typename
}
contents {
__typename
}
}
}
}{ "path": "/about", "locale": "en" }Your content-model fields are generated#
Content-type root names and fields come from the current tenant's content model. A content type with code article becomes an article root. A code such as blog_post becomes blogPost.
For a collection type, the root returns a connection with nodes, totalCount, and pageInfo. This example is valid only when the tenant has an enabled article collection with title and slug fields:
query Articles($locale: String, $after: String) {
article(first: 10, after: $after, locale: $locale) {
nodes {
id
title
slug
}
totalCount
pageInfo {
hasNextPage
endCursor
}
}
}Do not copy a sample field name blindly. Inspect the schema for your tenant or generate its types first.
Filter and paginate a collection#
Collection roots accept these common arguments:
| Argument | Meaning |
|---|---|
first | Page size from 1 to 100; default is 20 |
after | Cursor from the previous response's endCursor |
locale | Requested language code |
where: { id } | Return a specific entry by ID |
where: { slug } | Return a specific entry by slug |
sort: { direction } | ASC for oldest first or the default descending direction |
Example: request the next page only when the previous response says there is one.
let after: string | undefined;
do {
const data = await graph.request<{
article: { nodes: unknown[]; pageInfo: { hasNextPage: boolean; endCursor?: string } };
}>(ARTICLE_QUERY, { after, locale: "en" });
save(data.article.nodes);
after = data.article.pageInfo.hasNextPage ? data.article.pageInfo.endCursor : undefined;
} while (after);Use the GraphQL client package#
The framework-neutral client appends /graphql, adds the correct header, supports timeouts, and distinguishes transport errors from GraphQL response errors.
npm install @contoprix/graphql-clientimport { createContoprixGraphQLClient } from "@contoprix/graphql-client";
export const graph = createContoprixGraphQLClient({
endpoint: process.env.CONTOPRIX_BASE_URL!,
auth: {
type: "deliveryKey",
deliveryKey: process.env.CONTOPRIX_GRAPHQL_KEY!,
},
locale: "en",
timeout: 10_000,
});Use graph.request() when any GraphQL error should throw. Use graph.requestWithErrors() when a UI can safely render partial data alongside the returned GraphQL errors.
const site = await graph.getSite();
const page = await graph.getPage({ path: "/about", locale: "en" });Generate types from the live schema#
The public /graphql endpoint disables arbitrary introspection outside development by default. For reliable schema tooling, use the scope-gated schema export through the CLI:
npx contoprix graphql pull
npx contoprix graphql generate
# Or run both steps:
npx contoprix graphql syncThese commands need a CLI API client with schema:read. They save the introspection export to .contoprix/schema/graphql-schema.json and generate TypeScript output at the configured graphqlGeneratedTypesFile path, normally src/contoprix/graphql-generated.ts.
Keep GraphQL requests safe#
- Send only published delivery queries; the endpoint is a read-only delivery surface.
- Use variables for changing IDs, paths, slugs, and locales.
- Request only the fields the UI needs.
- Keep the credential server-side.
- Handle both HTTP failures and GraphQL
errorsin the JSON response. - Respect cursor pagination instead of asking for large collections.
For a browser interface that helps explore a development schema, see Graph Playground. For standard website delivery, the SDK is the shortest path.