The @dotcms/client is a powerful JavaScript/TypeScript SDK designed to simplify the integration of dotCMS content into your applications. Whether building dynamic websites or content-driven apps, this SDK offers an intuitive API for seamless and type-safe content retrieval, enabling you to create engaging experiences effortlessly by accessing and displaying content from dotCMS.
📋 Migration Guides:
- From Alpha Version? If you're upgrading from the alpha version of
@dotcms/client, please see our Migration Guide for step-by-step instructions.- From v1.2.x to v1.3.0? See the Changelog section below for new AI Search API features.
- From v1.0.x to v1.1.1? See the Changelog section below for new features and improvements.
For Production Use:
For Testing & Development:
For Local Development:
[!TIP] Make sure your API Token has read-only permissions for Pages, Folders, Assets, and Content. Using a key with minimal permissions follows security best practices.
This integration requires an API Key with read-only permissions for security best practices:
For detailed instructions, please refer to the dotCMS API Documentation - Read-only token.
Install the SDK and required dependencies:
npm install @dotcms/client@latest @dotcms/types@latest[!TIP] If you are working with pure JavaScript, you can avoid installing the
@dotcms/typespackage.
Here's a basic setup of the dotCMS Client SDK to help you get started:
import { createDotCMSClient } from '@dotcms/client';
// Create a client instance
const client = createDotCMSClient({
dotcmsUrl: 'https://your-dotcms-instance.com',
authToken: 'your-auth-token',
siteId: 'your-site-id' // Optional site identifier
});
// Start using the client!
const { pageAsset } = await client.page.get('/about-us');
console.log(pageAsset.page.title);While there isn't a dedicated example project specifically for the client SDK, you can see it in action within these full-stack examples:
These examples demonstrate how to use the client SDK as part of a complete web application.
The client.page.get() method is your primary tool for retrieving full page content. Here's how to use it effectively:
// Fetch a page with its layout and content
const { pageAsset } = await client.page.get('/about-us');
console.log(pageAsset.page.title);// Fetch with specific language and persona
const { pageAsset } = await client.page.get('/about-us', {
languageId: '2',
fireRules: true,
personaId: '1234'
});// Pull in additional content with GraphQL
const { pageAsset, content } = await client.page.get('/about-us', {
graphql: {
content: {
blogPosts: `
BlogCollection(limit: 3) {
title
urlTitle
}
`,
navigation: `
DotNavigation(uri: "/", depth: 2) {
href
title
}
`
}
}
});The client.content.getCollection() method uses a fluent builder pattern for querying content:
// Fetch first 10 blog posts
const blogs = await client.content.getCollection('Blog').limit(10).page(1);// Filter by title and sort by date
const filtered = await client.content
.getCollection('Blog')
.query((qb) => qb.field('title').equals('dotCMS*'))
.limit(5)
.sortBy([{ field: 'publishDate', direction: 'desc' }]);// Multiple filters with operators
const products = await client.content
.getCollection('Product')
.query((qb) => qb
.field('category').equals('electronics')
.and()
.field('price').raw(':[100 TO 500]')
.not()
.field('discontinued').equals('true')
)
.limit(10);By default, getCollection() scopes queries to the configured siteId. Call .includeSystemHost() to also return content that belongs to the dotCMS System Host — shared content available across all sites.
// Return content from both the configured site AND the System Host
const blogs = await client.content
.getCollection('Blog')
.includeSystemHost()
.limit(10);Under the hood, all positive +conhost: constraints in the assembled query are collected and grouped into a single +(conhost:<siteId> conhost:SYSTEM_HOST) OR group, so dotCMS returns content from any of the matched hosts.
// Multiple sites + System Host (multisite scenario)
const blogs = await client.content
.getCollection('Blog')
.query('+conhost:site-a')
.includeSystemHost()
.limit(10);
// Resulting conhost constraint: +(conhost:site-a conhost:<configured-siteId> conhost:SYSTEM_HOST)[!NOTE] Negative conhost exclusions (
-conhost:excluded-site) in raw queries are preserved as-is and are not affected byincludeSystemHost().
Use client.content.query() when you want to execute a raw Lucene query string (without the query-builder DSL), and you want full control over constraints like contentType, live, languageId, conhost, etc.
[!NOTE]
content.query()does not prefix fields withcontentType.(unlikegetCollection()), and it does not inject system constraints into your query string.
const response = await client.content
.query('+contentType:Blog +title:"Hello World"')
.limit(10)
.page(1);const response = await client.content
.query('+contentType:Blog +title:"Hello World"')
.language(1) // sets languageId in the request body (does not alter the Lucene string)
.limit(10);[!WARNING] Experimental Feature: The AI API is currently experimental and may undergo breaking changes in future releases. Use with caution in production environments.
The client.ai.search() method enables semantic search using AI embeddings to find content based on meaning rather than exact keyword matches.
Before using AI-powered search, ensure your dotCMS instance is properly configured:
[!IMPORTANT] Required Setup:
- dotAI must be activated in your dotCMS instance
- OpenAI API Key must be configured in dotCMS
- PostgreSQL 15+ with pgvector extension must be installed
- Content indexes must be created and configured in dotAI
For detailed setup instructions, see the dotCMS dotAI documentation.
// Search for content semantically related to your query
const response = await client.ai.search(
'articles about machine learning',
'content_index'
);
console.log(response.results);// Fine-tune search with query parameters
const response = await client.ai.search(
'artificial intelligence tutorials',
'content_index',
{
query: {
limit: 20,
offset: 0,
contentType: 'BlogPost',
languageId: '1'
}
}
);import { DISTANCE_FUNCTIONS } from '@dotcms/types';
// Customize AI search behavior with threshold and distance function
const response = await client.ai.search(
'deep learning concepts',
'content_index',
{
config: {
threshold: 0.75, // Higher threshold = more relevant results
distanceFunction: DISTANCE_FUNCTIONS.cosine, // Distance calculation method
responseLength: 2048 // Maximum response length
}
}
);// Combine query and AI parameters for precise control
const response = await client.ai.search(
'best practices for content management',
'articles_index',
{
query: {
limit: 10,
offset: 0,
contentType: 'Article',
languageId: '1',
siteId: 'my-site'
},
config: {
threshold: 0.8,
distanceFunction: DISTANCE_FUNCTIONS.innerProduct,
responseLength: 1024
}
}
);
// Access results with match scores
response.results.forEach(result => {
console.log(result.title);
console.log('Matches:', result.matches); // Distance and extracted text
});The SDK supports multiple distance functions for vector similarity:
import { DISTANCE_FUNCTIONS } from '@dotcms/types';
// Available distance functions:
DISTANCE_FUNCTIONS.cosine // '<=>' - Cosine similarity (default)
DISTANCE_FUNCTIONS.innerProduct // '<#>' - Inner product similarity
DISTANCE_FUNCTIONS.L2 // '<->' - Euclidean distance
DISTANCE_FUNCTIONS.L1 // '<+>' - Manhattan distance
DISTANCE_FUNCTIONS.hamming // '<~>' - Hamming distance
DISTANCE_FUNCTIONS.jaccard // '<%>' - Jaccard similarityThe SDK is built with TypeScript and provides comprehensive type definitions through the @dotcms/types package for enhanced developer experience and type safety.
import { createDotCMSClient } from '@dotcms/client';
import type {
DotCMSPageAsset,
DotCMSPageResponse,
DotCMSBasicContentlet,
DotCMSNavigationItem,
DotCMSAISearchResponse,
DotErrorPage,
DotErrorContent,
DotErrorAISearch
} from '@dotcms/types';
const client = createDotCMSClient({
dotcmsUrl: 'https://your-dotcms-instance.com',
authToken: 'your-auth-token',
siteId: 'your-site-id'
});import type { DotCMSPageAsset } from '@dotcms/types';
// Basic page fetch with type inference
const { pageAsset } = await client.page.get('/about-us');
// pageAsset is automatically typed as DotCMSPageAsset
// Explicit typing
const response: { pageAsset: DotCMSPageAsset } = await client.page.get('/about-us');
// Access typed properties
console.log(pageAsset.page.title); // string
console.log(pageAsset.layout.body.rows); // Row[]
console.log(pageAsset.viewAs.language); // LanguageViewimport type { DotCMSBasicContentlet } from '@dotcms/types';
// Define your custom content type interface
interface BlogPost extends DotCMSBasicContentlet {
title: string;
body: string;
author: string;
publishDate: string;
tags: string[];
}
// Use the generic type parameter for type-safe content
const response = await client.content
.getCollection<BlogPost>('Blog')
.limit(10);
// Now contentlets are typed as BlogPost[]
response.contentlets.forEach(post => {
console.log(post.title); // ✅ Type-safe: string
console.log(post.author); // ✅ Type-safe: string
console.log(post.publishDate); // ✅ Type-safe: string
});import { DISTANCE_FUNCTIONS } from '@dotcms/types';
import type { DotCMSAISearchResponse, DotCMSBasicContentlet } from '@dotcms/types';
// Define your content type
interface Article extends DotCMSBasicContentlet {
title: string;
content: string;
category: string;
}
// Type-safe AI search
const response: DotCMSAISearchResponse<Article> = await client.ai.search<Article>(
'machine learning tutorials',
'content_index',
{
query: {
limit: 20,
contentType: 'Article'
},
config: {
threshold: 0.75,
distanceFunction: DISTANCE_FUNCTIONS.cosine
}
}
);
// Access typed results with match information
response.results.forEach(article => {
console.log(article.title); // ✅ Type-safe: string
console.log(article.category); // ✅ Type-safe: string
// Access AI match data
article.matches?.forEach(match => {
console.log(match.distance); // ✅ Type-safe: number
console.log(match.extractedText); // ✅ Type-safe: string
});
});import type { DotCMSNavigationItem } from '@dotcms/types';
// Navigation is automatically typed
const nav: DotCMSNavigationItem[] = await client.navigation.get('/', {
depth: 2,
languageId: 1
});
// Access typed navigation properties
nav.forEach(item => {
console.log(item.title); // ✅ Type-safe: string
console.log(item.href); // ✅ Type-safe: string
console.log(item.children); // ✅ Type-safe: DotCMSNavigationItem[] | undefined
});import type {
DotHttpError,
DotErrorPage,
DotErrorContent,
DotErrorAISearch
} from '@dotcms/types';
try {
const { pageAsset } = await client.page.get('/about-us');
} catch (error) {
// Type guard for specific error types
if (error instanceof DotErrorPage) {
// TypeScript knows this is DotErrorPage
console.error('Page error:', error.message);
console.error('Context:', error.context);
if (error.httpError) {
console.error('Status:', error.httpError.status); // ✅ Type-safe
}
} else if (error instanceof DotErrorContent) {
// TypeScript knows this is DotErrorContent
console.error('Content error:', error.contentType);
console.error('Operation:', error.operation);
} else if (error instanceof DotErrorAISearch) {
// TypeScript knows this is DotErrorAISearch
console.error('AI Search error:', error.prompt);
console.error('Parameters:', error.params);
}
}import type { DotCMSBasicContentlet } from '@dotcms/types';
// Define related content types
interface Author extends DotCMSBasicContentlet {
name: string;
email: string;
bio: string;
}
interface Category extends DotCMSBasicContentlet {
categoryName: string;
description: string;
}
// Main content type with relationships
interface BlogPost extends DotCMSBasicContentlet {
title: string;
body: string;
author: Author; // Nested type
category: Category; // Nested type
tags: string[];
publishDate: string;
}
// Fetch with depth to include relationships
const response = await client.content
.getCollection<BlogPost>('Blog')
.depth(2)
.limit(5);
// Access nested typed properties
response.contentlets.forEach(post => {
console.log(post.title); // ✅ string
console.log(post.author.name); // ✅ string
console.log(post.author.email); // ✅ string
console.log(post.category.categoryName); // ✅ string
});import type {
DotCMSPageAsset,
GetCollectionResponse
} from '@dotcms/types';
// Async function with typed return
async function fetchBlogPosts(): Promise<GetCollectionResponse<BlogPost>> {
return await client.content
.getCollection<BlogPost>('Blog')
.limit(10);
}
// Promise chain with types
client.page.get('/about-us')
.then((response: { pageAsset: DotCMSPageAsset }) => {
console.log(response.pageAsset.page.title);
return response;
})
.catch((error: DotErrorPage) => {
console.error(error.message);
});GraphQL allows you to fetch exactly the data you need in a single request:
// Use _map to get all page fields including custom ones
const { pageAsset } = await client.page.get('/about-us', {
graphql: {
page: '_map'
}
});// Fetch related content using fragments
const { pageAsset } = await client.page.get('/blog-post', {
graphql: {
page: `
containers {
containerContentlets {
contentlets {
... on Blog {
author {
title
email
}
category {
categoryName
}
tags {
tagName
}
}
}
}
}
`
}
});// Reusable fragments with variables
const response = await client.page.get('/about-us', {
graphql: {
content: {
blogPosts: `
BlogCollection(limit: $limit) {
...blogFragment
}
`
},
fragments: [
`fragment blogFragment on Blog {
title
urlTitle
blogContent {
json
}
}`
],
variables: { limit: 5 }
}
});The @dotcms/client SDK is responsible for fetching your page, while a framework SDK (@dotcms/react or @dotcms/angular) makes that page editable inside the Universal Visual Editor (UVE).
The flow is always the same three steps:
client.page.get().useEditableDotCMSPage in React, DotCMSEditablePageService in Angular).DotCMSLayoutBody, mapping your content types to components.client.page.get()#Fetch the full page response on the server. The complete response object — not just pageAsset — must be forwarded to the editor layer, because it carries the data UVE needs to track changes.
// server-side, e.g. a Next.js Server Component
import { createDotCMSClient } from '@dotcms/client';
const client = createDotCMSClient({
dotcmsUrl: 'https://your-dotcms-instance.com',
authToken: 'your-auth-token',
siteId: 'your-site-id'
});
// Return the whole response so the framework SDK can make it editable
export async function getPage(path: string) {
return await client.page.get(path);
}Pass the full page response into useEditableDotCMSPage. The hook keeps the page in sync with UVE while editing and returns the same pageAsset / content shape you get from client.page.get(), so the component works identically in and out of the editor.
'use client';
import { DotCMSLayoutBody, useEditableDotCMSPage } from '@dotcms/react';
import { pageComponents } from '@/components/content-types';
export function Page({ pageContent }) {
// `pageContent` is the full response from client.page.get()
const { pageAsset } = useEditableDotCMSPage(pageContent);
return (
<DotCMSLayoutBody
page={pageAsset}
components={pageComponents}
mode={process.env.NEXT_PUBLIC_DOTCMS_MODE}
/>
);
}💡 Using Angular? Use
DotCMSEditablePageServicetogether withDotCMSLayoutBody— the same fetch → make-editable → render flow applies.
DotCMSLayoutBody#DotCMSLayoutBody renders the page's rows, columns, and containers, and maps each contentlet to one of your components via the components prop. When loaded inside UVE it automatically applies the data-dot-* attributes that make the page editable — no extra wiring required.
See the page-editing flow end to end in the official Next.js example — examples/nextjs. In particular, src/views/Page.tsx uses useEditableDotCMSPage and DotCMSLayoutBody exactly as shown above.
createDotCMSClient(config: DotCMSClientConfig): DotCMSClient| Option | Type | Required | Description |
|---|---|---|---|
dotcmsUrl | string | ✅ | Your dotCMS instance URL |
authToken | string | ✅ | Authentication token |
siteId | string | ❌ | Site identifier (falls back to default site if not specified) |
requestOptions | DotRequestOptions | ❌ | Additional request options |
httpClient | DotHttpClient | ❌ | Custom HTTP client implementation |
logLevel | 'default' | 'verbose' | ❌ | Controls log verbosity. 'verbose' adds status, code, and variables to error logs. Defaults to 'default' |
const client = createDotCMSClient({
dotcmsUrl: 'https://your-dotcms-instance.com',
authToken: 'your-auth-token',
siteId: 'your-site-id',
httpClient: customHttpClient, // Optional: provide custom HTTP client
logLevel: 'verbose' // Optional: enable detailed error logs
});[!TIP] Enable
logLevel: 'verbose'during development to see HTTP status codes, error codes, and request variables in error logs. In verbose mode, error logs also include a hint to access the full GraphQL query viaerror.graphql.query. Keep it at'default'(or omit it) in production to avoid noisy logs.
The SDK now supports custom HTTP client implementations for advanced use cases. By default, it uses the built-in FetchHttpClient based on the native Fetch API.
// The SDK automatically uses FetchHttpClient if no custom client is provided
const client = createDotCMSClient({
dotcmsUrl: 'https://your-dotcms-instance.com',
authToken: 'your-auth-token'
// No httpClient specified - uses FetchHttpClient internally
});import { BaseHttpClient } from '@dotcms/types';
// Implement your own HTTP client by extending BaseHttpClient
class CustomHttpClient extends BaseHttpClient {
async request<T>(url: string, options?: DotRequestOptions): Promise<T> {
// Your custom implementation
// Must handle JSON parsing, error conversion, etc.
return customRequest(url, options);
}
}
const client = createDotCMSClient({
dotcmsUrl: 'https://your-dotcms-instance.com',
authToken: 'your-auth-token',
httpClient: new CustomHttpClient()
});DotHttpError instancesget<T extends DotCMSExtendedPageResponse = DotCMSPageResponse>(
url: string,
options?: DotCMSPageRequestParams
): Promise<DotCMSComposedPageResponse<T>>| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | ✅ | Page URL path |
options | DotCMSPageRequestParams | ❌ | Request customization options |
| Option | Type | Description |
|---|---|---|
languageId | string | number | Language version of the page |
mode | string | Rendering mode: LIVE, PREVIEW_MODE |
personaId | string | Personalize content based on persona ID |
graphql | DotCMSGraphQLParams | GraphQL options for extending response |
fireRules | boolean | Whether to trigger page rules |
const { pageAsset } = await client.page.get('/about-us');getCollection<T = DotCMSBasicContentlet>(
contentType: string
): CollectionBuilder<T>| Parameter | Type | Required | Description |
|---|---|---|---|
contentType | string | ✅ | Content type variable name |
| Method | Arguments | Description |
|---|---|---|
query() | string | BuildQuery | Filter content using query builder |
limit() | number | Set number of items to return |
page() | number | Set which page of results to fetch |
sortBy() | SortBy[] | Sort by one or more fields |
language() | number | string | Set content language |
depth() | number | Set depth of related content |
includeSystemHost() | - | Include content from the System Host alongside the configured site |
const blogs = await client.content.getCollection('Blog').limit(10).page(1);query<T = DotCMSBasicContentlet>(
rawQuery: string
): RawQueryBuilder<T>| Parameter | Type | Required | Description |
|---|---|---|---|
rawQuery | string | ✅ | Raw Lucene query string |
| Method | Arguments | Description |
|---|---|---|
limit() | number | Set number of items to return |
page() | number | Set which page of results to fetch |
sortBy() | SortBy[] | Sort by one or more fields |
render() | - | Enable server-side rendering (velocity) for widgets in returned content |
depth() | number | Set depth of related content |
language() | number | string | Set languageId in the request body (does not modify the raw Lucene string) |
const response = await client.content
.query('+contentType:Blog +title:"Hello World"')
.language(1)
.limit(10);[!WARNING] Experimental Feature: The AI API is currently experimental and may undergo breaking changes in future releases. Use with caution in production environments.
Performs semantic search using AI embeddings to find content based on meaning rather than exact keyword matches.
[!NOTE] Prerequisites: This feature requires dotAI to be activated in your dotCMS instance with a configured OpenAI API key. See the dotAI setup documentation for configuration details.
search<T extends DotCMSBasicContentlet>(
prompt: string,
indexName: string,
params?: DotCMSAISearchParams
): Promise<DotCMSAISearchResponse<T>>| Parameter | Type | Required | Description |
|---|---|---|---|
prompt | string | ✅ | Natural language search query |
indexName | string | ✅ | Name of the AI search index to query |
params | DotCMSAISearchParams | ❌ | Search configuration options |
| Option | Type | Description |
|---|---|---|
limit | number | Maximum number of results (default: 1000) |
offset | number | Number of results to skip (default: 0) |
contentType | string | Filter by specific content type |
languageId | string | Filter by language ID |
siteId | string | Filter by site ID |
| Option | Type | Description |
|---|---|---|
threshold | number | Minimum similarity score (0-1, default: 0.5) |
distanceFunction | string | Distance calculation method (default: cosine) |
responseLength | number | Maximum response length (default: 1024) |
interface DotCMSAISearchResponse<T> {
results: Array<T & {
matches?: Array<{
distance: number; // Similarity score
extractedText: string; // Matched text excerpt
}>;
}>;
}Basic Search:
const results = await client.ai.search(
'machine learning articles',
'content_index'
);With Parameters:
import { DISTANCE_FUNCTIONS } from '@dotcms/types';
const results = await client.ai.search(
'AI tutorials',
'content_index',
{
query: {
limit: 20,
contentType: 'BlogPost',
languageId: '1'
},
config: {
threshold: 0.75,
distanceFunction: DISTANCE_FUNCTIONS.cosine
}
}
);Promise-Style:
client.ai.search(
'content management best practices',
'content_index',
{
query: { limit: 10 },
config: { threshold: 0.8 }
}
).then((response) => {
console.log('Found:', response.results.length);
return response;
}).catch((error) => {
console.error('Search failed:', error.message);
});get(
uri: string,
options?: {
depth?: number;
languageId?: number;
}
): Promise<DotCMSNavigationItem[]>| Parameter | Type | Required | Description |
|---|---|---|---|
uri | string | ✅ | Navigation root URI |
options | object | ❌ | Navigation options |
| Option | Type | Description |
|---|---|---|
depth | number | Number of child levels to include |
languageId | number | Language ID for localized navigation names |
const nav = await client.navigation.get('/', { depth: 2 });The SDK provides comprehensive error handling with specific error types for different API operations. These domain-specific errors may wrap DotHttpError instances when HTTP failures occur and include contextual information to help with debugging.
| Error Type | When It's Thrown | Properties |
|---|---|---|
DotHttpError | HTTP/network failures (4xx/5xx, timeouts) | status, statusText, headers, body |
DotErrorPage | Page API failures | httpError?, context (query, variables) |
DotErrorContent | Content API failures | contentType, operation, httpError?, query? |
DotErrorAISearch | AI Search API failures | prompt, params, httpError? |
DotErrorNavigation | Navigation API failures | path, httpError? |
try {
const { pageAsset } = await client.page.get('/about-us');
console.log(pageAsset.page.title);
} catch (error) {
if (error instanceof DotErrorPage) {
console.error('Page error:', error.message);
console.error('Context:', error.context);
} else {
console.error('Unexpected error:', error);
}
}try {
const blogs = await client.content
.getCollection('Blog')
.limit(10);
} catch (error) {
if (error instanceof DotErrorContent) {
console.error('Content error:', error.message);
console.error('Content type:', error.contentType);
console.error('Operation:', error.operation);
if (error.httpError) {
console.error('HTTP status:', error.httpError.status);
}
}
}try {
const nav = await client.navigation.get('/missing-path');
} catch (error) {
if (error instanceof DotErrorNavigation) {
console.error('Navigation error:', error.message);
console.error('Path:', error.path);
if (error.httpError) {
console.error('HTTP status:', error.httpError.status);
}
}
}try {
const results = await client.ai.search('machine learning', 'content_index');
} catch (error) {
if (error instanceof DotErrorAISearch) {
console.error('AI Search error:', error.message);
console.error('Prompt:', error.prompt);
console.error('Index Name:', error.indexName);
console.error('Parameters:', error.params);
if (error.httpError) {
console.error('HTTP status:', error.httpError.status);
}
}
}// Content collections support .then() with error handling
const result = await client.content
.getCollection('Blog')
.limit(10)
.then(
(response) => {
console.log('Success:', response.contentlets);
return response;
},
(error) => {
console.error('Error:', error.message);
// Return fallback data or re-throw
return { contentlets: [], total: 0 };
}
);401 Unauthorized
// Missing or invalid authentication token
DotHttpError: status 401, message: "Authentication required"403 Forbidden
// Valid token but insufficient permissions
DotHttpError: status 403, message: "Access denied"404 Not Found
// Page, content, or navigation path not found
DotErrorPage: "Page /missing-page not found. Check the page URL and permissions."Network Errors
// Connection issues, timeouts, etc.
DotHttpError: "Network request failed"| Term | Description | Documentation |
|---|---|---|
pageAsset | The page data structure containing layout and content | Page API |
contentlet | A single piece of content in dotCMS | Content API |
collection | A group of contentlets of the same type | Content API |
graphql | Query language used to extend API responses | GraphQL |
The dotCMS Client SDK provides five core methods for fetching data. Use this quick guide to decide which one is best for your use case:
| Method | Use When You Need... | Best For |
|---|---|---|
client.page.get() | A full page with layout, containers, and related content | Rendering entire pages with a single request. Ideal for headless setups, SSR/SSG frameworks, and cases where you want everything—page structure, content, and navigation—tied to a URL path. |
client.content.getCollection() | A filtered list of content items from a specific content type | Populating dynamic blocks, lists, search results, widgets, or reusable components using the fluent query builder. |
client.content.query() | Full control over a raw Lucene query string | Advanced search scenarios where you need direct Lucene syntax without the getCollection() query-builder DSL or automatic contentType. field prefixing. |
client.ai.search() | Semantic/AI-powered content discovery based on natural language | Intelligent search experiences where users describe what they're looking for in natural language. Great for search features, content recommendations, and finding relevant content by meaning rather than exact keywords. ⚠️ Experimental API |
client.navigation.get() | Only the site's navigation structure (folders and links) | Standalone menus or use cases where navigation is needed outside of page context. |
page.get(): The One-Request Solution#For most use cases, client.page.get() is all you need. It lets you retrieve:
All in a single request using GraphQL.
Only use content.getCollection(), content.query(), or navigation.get() if you have advanced needs, like real-time data fetching or building custom dynamic components.
🔍 For comprehensive examples of advanced GraphQL querying including relationships and custom fields, see the How to Work with GraphQL section.
The SDK follows a client-builder pattern with four main APIs:
client.page.get()) - Fetches complete page content with layout and containersclient.content.getCollection(), client.content.query()) - Builder pattern for querying content collections or raw Lucene queriesclient.ai.search()) - AI-powered semantic search using embeddings and vector similarity ⚠️ Experimentalclient.navigation.get()) - Fetches site navigation structureAll APIs support:
We offer multiple channels to get help with the dotCMS Client SDK:
dotcms-client when posting questions.When reporting issues, please include:
GitHub pull requests are the preferred method to contribute code to dotCMS. We welcome contributions to the dotCMS Client SDK! If you'd like to contribute, please follow these steps:
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)Please ensure your code follows the existing style and includes appropriate tests.
dotCMS is available under either the Business Source License 1.1 (BSL) or a commercial license.
Under the BSL, dotCMS can be used at no cost by individual developers, small businesses or agencies under $5M in total finances, and by larger organizations in non-production environments. Every BSL release automatically converts to GPL v3 four years after its release date. For full terms and FAQs, visit dotcms.com/bsl and dotcms.com/bsl-faq.
Production use in larger organizations, along with access to managed cloud, SLAs, support, and enterprise capabilities, is available under a commercial license from dotCMS. For details on commercial plans, features, and support options, see dotcms.com/pricing.
⚠️ Experimental Feature: The AI API is experimental and may undergo breaking changes in future releases.
New Features:
client.ai.search() - AI-powered semantic search using embeddings and vector similarityDotErrorAISearch error class for AI-specific error handling with prompt and index contextPrerequisites:
Basic Usage:
// Semantic search with required index name
const results = await client.ai.search(
'machine learning articles',
'content_index'
);
// With advanced configuration
const results = await client.ai.search(
'AI tutorials',
'content_index',
{
query: {
limit: 20,
contentType: 'BlogPost'
},
config: {
threshold: 0.75,
distanceFunction: DISTANCE_FUNCTIONS.cosine
}
}
);Key Features:
Version 1.1.1 introduces significant improvements to error handling and HTTP client architecture. Most applications will continue to work without changes.
New Features:
DotErrorPage, DotErrorContent, DotErrorNavigationDotHttpError instances with contextual informationMigration Required If:
Error instances from SDK calls.then() callbacks on content collections without return valuesBefore (v1.0.x):
try {
const { pageAsset } = await client.page.get('/about');
} catch (error) {
// Generic error handling
console.error('Error:', error.message);
console.error('Status:', error.status); // May not exist
}
// Collection error handling
client.content.getCollection('Blog').then(
(response) => console.log(response),
(error) => {
console.error(error.status); // Raw HTTP status
// No return value required
}
);After (v1.1.1):
try {
const { pageAsset } = await client.page.get('/about');
} catch (error) {
// Specific error type checking
if (error instanceof DotErrorPage) {
console.error('Page Error:', error.message);
console.error('Context:', error.context);
if (error.httpError) {
console.error('HTTP Status:', error.httpError.status);
}
}
}
// Collection error handling with required return values
client.content.getCollection('Blog').then(
(response) => {
console.log(response);
return response; // Return value recommended
},
(error) => {
if (error instanceof DotErrorContent) {
console.error('Content Error:', error.contentType, error.operation);
}
return { contentlets: [], total: 0 }; // Return fallback or re-throw
}
);New Features:
DotHttpClient interface for pluggable HTTP implementationsFetchHttpClient replaces direct fetch() callsMigration Required If:
New Capabilities:
// Custom HTTP client support
const client = createDotCMSClient({
dotcmsUrl: 'https://your-instance.com',
authToken: 'your-token',
httpClient: new CustomHttpClient() // Optional: custom implementation
});Improvements:
RequestOptions renamed to DotRequestOptionsMigration Required If:
RequestOptions directlyUpdate Imports:
// Before
import { RequestOptions } from '@dotcms/types';
// After
import { DotRequestOptions } from '@dotcms/types';latestFound an issue with this documentation? View the source