SDK initialization and query patterns
SDK initialization and query patterns
TL;DR
- Initialize the SDK with your API key, delivery token, environment, and region -- all four must be correct or queries silently fail.
- Use the fluent query builder (.equalTo(), .where(), .includeReference(), .limit(), .skip()) to compose filters, sorting, pagination, and reference resolution before executing with .find().
- Always verify your stack region in Settings > Stack Information; a wrong region returns empty results or 401 with no hint about the actual cause.
Prerequisites
This module assumes familiarity with TypeScript, Node.js, and basic async/await patterns. If you're new to these, review the TypeScript handbook and Node.js getting started guide first.
Before you can render content from Contentstack, you need a reliable way to fetch it. While raw REST calls work, the @contentstack/delivery-sdk provides a structured query interface that handles authentication headers, region routing, pagination, and response parsing for you. This is the recommended SDK for all JavaScript and TypeScript projects. This lesson walks through SDK setup, configuration, and the query patterns you will use daily.
The domain example throughout this lesson is a Product content type from Veda: The Revival Collection - a jewelry e-commerce catalog with products, product lines, and categories.
Installing the SDK
The recommended SDK for JavaScript and TypeScript delivery is @contentstack/delivery-sdk. It is Contentstack's modern, modular SDK with a TypeScript-first design, tree-shaking support, and active development. This course uses @contentstack/delivery-sdk exclusively.
npm install @contentstack/delivery-sdk
Note:
You may encounter references to the older contentstack npm package in legacy codebases. While it connects to the same Content Delivery API, new projects should always use @contentstack/delivery-sdk.
Initializing the stack client
Every SDK interaction begins with a Stack instance. You need three credentials and one configuration choice:
- Stack API Key - identifies your stack. Found in Settings > Stack in the Contentstack dashboard.
- Delivery Token - a read-only token scoped to a specific environment. Found in Settings > Tokens > Delivery Tokens.
- Environment - the publishing environment to query (development, staging, production).
- Region - the data center region where your stack is hosted.
import Contentstack from "@contentstack/delivery-sdk";
const stack = Contentstack.stack({
apiKey: process.env.NEXT_PUBLIC_CONTENTSTACK_API_KEY,
deliveryToken: process.env.NEXT_PUBLIC_CONTENTSTACK_DELIVERY_TOKEN,
environment: process.env.NEXT_PUBLIC_CONTENTSTACK_ENVIRONMENT,
region: Contentstack.Region.US,
});Region configuration
Contentstack operates across multiple cloud providers and geographic regions. The region you specify must match where your stack was created. Using the wrong region silently returns empty results or authentication failures. For a complete breakdown of all regions, cloud providers, and their API endpoint URLs, see Lesson 3.1.2: Regions, clouds, and API endpoints.
The SDK handles URL construction automatically when you set the region correctly using the built-in region constants (e.g., Contentstack.Region.US, Contentstack.Region.EU).
Branch configuration
If your stack uses branches (a feature for parallel content development), you can target a specific branch during initialization.
const stack = Contentstack.stack({
apiKey: process.env.NEXT_PUBLIC_CONTENTSTACK_API_KEY,
deliveryToken: process.env.NEXT_PUBLIC_CONTENTSTACK_DELIVERY_TOKEN,
environment: "production",
region: Contentstack.Region.US,
branch: "feature-digital-dawn-v2",
});When no branch is specified, the SDK queries the main branch. The delivery token must have access to the target branch.
Fetching all entries of a content type
The most common query fetches entries from a content type. Assume you have a content type with UID product that has fields for title, url, price, short_description, and description.
const query = stack.contentType("product").entry().query();
const result = await query.find();
console.log(result.entries); // Array of product entry objectsThe find() method executes the query and returns a response object. The entries property contains the array of matching entries. Each entry includes all fields defined in the content type schema, plus system fields like uid, created_at, updated_at, locale, and _version.
Under the hood, the SDK sends a GET request to:
GET /v3/content_types/product/entries?environment=production
with api_key and access_token headers set automatically.
Fetching a single entry by UID
When you know the specific entry UID (for example, from a route parameter or a reference field), fetch it directly:
const entry = await stack
.contentType("product")
.entry("blt_matrix_link_001")
.fetch();
console.log(entry.title);
console.log(entry.price);The fetch() method returns the entry object directly, not wrapped in an array. This maps to the REST endpoint:
GET /v3/content_types/product/entries/blt_matrix_link_001?environment=production
This approach is faster and more cache-friendly than querying with a filter when you already have the UID.
Fetching by URL
Contentstack entries can have a url field (set in the content type schema). This is especially useful for page-driven content types. Rather than looking up a UID, you query by the URL path:
const query = stack.contentType("product").entry().query();
const result = await query.equalTo("url", "/products/digital-dawn/matrix-link-bracelet").find();
console.log(result.entries[0]); // The product entry matching that URLThis pattern drives most page rendering in frontend frameworks: the route provides the URL path, and you query Contentstack to resolve it to an entry.
Query chaining
The SDK provides a fluent query builder. You chain methods to compose filters, sorting, pagination, and reference inclusion before executing with find().
Filtering with .equalTo() and .where()
For simple equality filters, use .equalTo().
// Products in the Earrings category
const query = stack.contentType("product").entry().query();
const result = await query.equalTo("category", "blt_earrings_category_001").find();For comparison operators, use .where() with a QueryOperation.
import Contentstack, { QueryOperation } from "@contentstack/delivery-sdk";
// Products above a certain price
const query = stack.contentType("product").entry().query();
const result = await query
.where("price", QueryOperation.IS_GREATER_THAN, 100)
.find();The .where() method always takes three arguments: the field UID, a QueryOperation operator, and the value. Available operators include IS_LESS_THAN, IS_GREATER_THAN, EQUALS, INCLUDES, and others that map to Contentstack's underlying query language ($in, $nin, $gt, $lt, $gte, $lte, $ne, $exists, $regex).
Pagination with .limit() and .skip()
Contentstack returns a maximum of 100 entries per request by default. Control pagination explicitly:
const query = stack.contentType("product").entry().query();
// First page: 10 products
const page1 = await query.limit(10).skip(0).find();
// Second page: next 10 products
const page2 = await query.limit(10).skip(10).find();The response includes a count property indicating how many entries were returned in this response. Use this to build pagination controls.
Sorting with .orderByAscending() and .orderByDescending()
const query = stack.contentType("product").entry().query();
// Products sorted by price, lowest first
const result = await query.orderByAscending("price").find();// Most recently created products first
const query = stack.contentType("product").entry().query();
const result = await query.orderByDescending("created_at").find();Including references with .includeReference()
When a product has a reference field (for example, product_line referencing a product_line content type), the default response only includes the UIDs. To resolve the full referenced entries inline:
const query = stack.contentType("product").entry().query();
const result = await query.includeReference("product_line").find();
// Each product now has product_line[] with full entry data, not just UIDs
result.entries.forEach((product) => {
product.product_line.forEach((line) => {
console.log(line.title, line.description);
});
});You can include multiple reference fields by chaining .includeReference() calls.
const result = await query
.includeReference("product_line")
.includeReference("category")
.find();This translates to the REST parameters: include[]=product_line&include[]=category.
Understanding the response object
The SDK response from find() contains structured data:
const result = await query.find(); // result.entries - Array of entry objects // result.count - Number of entries in this response (respects limit)
Each entry object mirrors the JSON structure of the content type. System fields are included at the top level:
{
"uid": "blt_matrix_link_001",
"title": "Matrix Link Bracelet",
"price": 295,
"url": "/products/digital-dawn/matrix-link-bracelet",
"short_description": "A sleek link bracelet composed of interlocking square links...",
"product_line": [
{
"uid": "blt_digital_dawn_001",
"_content_type_uid": "product_line"
}
],
"category": [
{
"uid": "blt_bracelets_001",
"_content_type_uid": "category"
}
],
"locale": "en-us",
"created_at": "2025-01-10T08:30:00.000Z",
"updated_at": "2025-03-22T14:15:00.000Z",
"_version": 3,
"_in_progress": false
}Without reference inclusion, the safest mental model is "reference stubs" rather than fully resolved entries. In typed frontends such as kickstart-veda, keep product_line and category typed as referenced entry arrays and treat unresolved responses as partial objects until you call includeReference() or include_all.
When references are included via includeReference(), those stubs are replaced with the full entry objects.
Typed queries with generics
The SDK supports TypeScript generics on find() and fetch() to provide type safety on the response:
import { Product } from "./types";
const query = stack.contentType("product").entry().query();
const result = await query
.equalTo("url", "/products/digital-dawn/matrix-link-bracelet")
.find();
// result.entries is now typed as Product[]
result.entries[0].title; // string
result.entries[0].price; // number Define your types to match the content type schema (field UIDs as keys, with the correct TypeScript types). The kickstart-veda reference application provides a complete example of typing Contentstack entries.
Putting it together: a real query
Here is a complete example that initializes the SDK and builds a query for products in a product line, sorted by price, with category and product line references resolved:
import Contentstack, { QueryOperation } from "@contentstack/delivery-sdk";
// Initialize
const stack = Contentstack.stack({
apiKey: process.env.NEXT_PUBLIC_CONTENTSTACK_API_KEY!,
deliveryToken: process.env.NEXT_PUBLIC_CONTENTSTACK_DELIVERY_TOKEN!,
environment: "production",
region: Contentstack.Region.EU,
});
// Build and execute query
async function getProductsByLine(lineUid: string, page = 1) {
const perPage = 12;
const query = stack.contentType("product").entry().query();
const result = await query
.equalTo("product_line", lineUid)
.includeReference("product_line")
.includeReference("category")
.orderByAscending("price")
.limit(perPage)
.skip((page - 1) * perPage)
.find();
return {
products: result.entries,
hasMore: result.entries.length === perPage,
};
}
// Usage
const { products, hasMore } = await getProductsByLine("blt_digital_dawn_001");
products.forEach((product) => {
console.log(`${product.title} - $${product.price}`);
console.log(`Line: ${product.product_line.map((l) => l.title).join(", ")}`);
});This single function handles filtering, sorting, reference resolution, and pagination in a clean, composable way.
Common initialization mistakes
Common pitfall:
Initializing with the wrong region (e.g., Region.US when your stack is in EU) produces empty results or 401 errors with no mention of a region mismatch -- verify your stack's region in Settings > Stack Information.
Wrong region: If your stack is in EU and you initialize with Region.US, every request either returns empty results or fails with a 401. The error message does not explicitly say "wrong region." Always verify your stack's region in Settings > Stack Information.
Environment mismatch: The delivery token is scoped to an environment. If you initialize with environment: "production" but the token was created for staging, you get authentication errors.
Missing environment variable: The SDK does not throw during initialization if credentials are empty strings. The error surfaces on the first query, often as a cryptic 401 or 412. Validate credentials at startup.
Branch not published: If you specify a branch but content is not published to the target environment on that branch, queries return empty results without errors.
Exercise: initialize the SDK and fetch products
Set up a small Node.js script that:
- Installs @contentstack/delivery-sdk.
- Creates a stack client pointed at your stack (use environment variables for credentials).
- Fetches all entries from a content type of your choice.
- Filters entries by a field value using .equalTo() or .where() with a QueryOperation.
- Limits results to 5 entries and sorts them by created_at descending.
- Logs the title and UID of each result.
If you do not have a stack with content, use the Veda kickstart seed to create a stack with products, product lines, and categories, then publish to a development environment.