# Migrating from the JavaScript Delivery SDK to the TypeScript Delivery SDK

- **Authors:** Tim Benniks
- **Published:** 2026-04-27
- **Updated:** 2026-04-27T10:14:31.619Z
- **Tags:** sdk, api
- **Chapters:** 8
- **Source:** [https://developers.contentstack.com/guides/migrating-from-the-javascript-delivery-sdk-to-the-typescript-delivery-sdk](https://developers.contentstack.com/guides/migrating-from-the-javascript-delivery-sdk-to-the-typescript-delivery-sdk)

---

## Table of Contents

1. [Why migrate, and what actually changes](#why-migrate-and-what-actually-changes)
2. [Install and initialize the TypeScript Delivery SDK](#install-and-initialize-the-typescript-delivery-sdk)
3. [Convert stack, entry, asset, and content type calls](#convert-stack-entry-asset-and-content-type-calls)
4. [Rewrite queries, pagination, sorting, and field selection](#rewrite-queries-pagination-sorting-and-field-selection)
5. [Caching, utilities, preview behavior, and runtime differences](#caching-utilities-preview-behavior-and-runtime-differences)
6. [Using the TypeScript Delivery SDK in JavaScript projects](#using-the-typescript-delivery-sdk-in-javascript-projects)
7. [Migration checklist, troubleshooting, and next steps](#migration-checklist-troubleshooting-and-next-steps)
8. [AI agent migration playbook](#ai-agent-migration-playbook)

## What this guide is for

This guide is for developers who already use the Contentstack JavaScript Delivery SDK and want to move to the TypeScript Delivery SDK without rewriting their entire website.

The migration is mostly an SDK API migration, not a language migration. You will update imports, initialization, stack calls, entry calls, asset calls, query chains, caching setup, and utility package usage. If your frontend is written in JavaScript, you can still use the TypeScript Delivery SDK from JavaScript files.

## Why this guide exists

The TypeScript Delivery SDK gives teams a modern Contentstack delivery client with stronger typing for projects that want it, a cleaner chainable API, and current package boundaries for caching and utility behavior.

That does not mean every project has to become a TypeScript project on day one. A JavaScript codebase can install and call @contentstack/delivery-sdk just like any other npm package. TypeScript support is available when you want it, but it is not a requirement for running the SDK.

Use this guide as a practical migration path:

- start by replacing the package and stack initialization


- migrate reads behind a small Contentstack wrapper


- update entries, assets, content types, and queries in predictable groups


- handle caching and utilities as separate packages when your app needs them


- verify behavior with existing pages before removing the old SDK



Useful references:

- [TypeScript Delivery SDK reference](https://www.contentstack.com/docs/developers/sdks/content-delivery-sdk/typescript/reference)


- [JavaScript Delivery SDK reference](https://www.contentstack.com/docs/developers/sdks/content-delivery-sdk/javascript-browser/reference)


- [Migration from JavaScript to TypeScript Delivery SDK](https://www.contentstack.com/docs/developers/sdks/content-delivery-sdk/typescript/migrate-from-javascript-to-typescript)



## How to use this guide

- If you are planning the migration, start with chapters 1 and 2.


- If you are already editing code, chapters 3 and 4 are the main translation map.


- If your app uses cache policies, Live Preview, rich text utilities, or Visual Builder helpers, read chapter 5 before you merge.


- If your team is worried that the new SDK forces a full TypeScript conversion, read chapter 6 first.


- If you want an AI coding agent to help with the migration, point it at chapter 8.






---

## Why migrate, and what actually changes

Moving from the JavaScript Delivery SDK to the TypeScript Delivery SDK is best treated as a careful delivery-layer upgrade. Your Contentstack stack, content types, entries, assets, environments, locales, and delivery tokens do not need to change just because the SDK package changes.

The code that talks to Contentstack does change. Most of the work is updating how you create the stack client and how you build requests for entries, assets, content types, sync, and queries.

## What you'll learn

- What the TypeScript Delivery SDK changes in day-to-day development


- What does not need to change in your Contentstack stack


- Why the migration is safer when you isolate SDK calls first


- Which parts of the old JavaScript SDK map directly to the new SDK


- Where the migration needs extra attention



## The migration in practical terms

The JavaScript Delivery SDK commonly uses uppercase builders:

```javascript
import contentstack from 'contentstack';

const Stack = contentstack.Stack({
  apiKey: process.env.CONTENTSTACK_API_KEY,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
  environment: process.env.CONTENTSTACK_ENVIRONMENT,
});

const result = await Stack.ContentType('blog')
  .Entry('entry_uid')
  .toJSON()
  .fetch();
```

The TypeScript Delivery SDK uses the @contentstack/delivery-sdk package and lowercase builders:

```javascript
import contentstack from '@contentstack/delivery-sdk';

const stack = contentstack.stack({
  apiKey: process.env.CONTENTSTACK_API_KEY,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
  environment: process.env.CONTENTSTACK_ENVIRONMENT,
});

const result = await stack.contentType('blog')
  .entry('entry_uid')
  .fetch();
```

The change is not just capitalization. The newer SDK has different package boundaries, different method names for some query operations, and optional TypeScript generics for teams that want typed response shapes.

## What does not change

Your content model stays the same. A blog content type is still a blog content type. An entry UID is still an entry UID. A delivery token still reads published content from an environment. Locale and fallback behavior still depend on what your stack has published.

In most migrations, these things do not need to change:

- stack API key


- delivery token


- environment name


- branch strategy


- locale strategy


- content type UIDs


- entry UIDs


- asset UIDs


- reference field UIDs


- frontend routes



If your app already has good separation between Contentstack fetch logic and rendering components, the migration can stay small. If SDK calls are scattered across pages, components, route handlers, and helper files, start by collecting the most common calls behind a small wrapper.

## What changes

The most visible changes are:

- contentstack becomes @contentstack/delivery-sdk


- contentstack.Stack() becomes contentstack.stack()


- Stack.ContentType() becomes stack.contentType()


- Entry() becomes entry()


- Assets() or Asset() becomes asset()


- Query() becomes entry().query() for entry queries


- language() becomes locale()


- ascending() and descending() become orderByAscending() and orderByDescending()


- toJSON() is usually removed because fetch() and find() return data directly


- cache persistence moves into @contentstack/persistance-plugin


- rich text and other utility helpers move into @contentstack/utils



Those changes are mechanical, but they are still worth doing in small batches so you can compare responses as you go.

## A good migration shape

Start with the file that creates your Contentstack client. Then migrate one read path at a time.

A useful order is:

- Install the new SDK and create a new stack client.


- Convert single-entry reads.


- Convert entry collection queries.


- Convert asset reads.


- Convert content type reads.


- Convert sync calls.


- Add caching and utility packages only if your current app uses those behaviors.


- Run the same pages or tests against old and new responses before deleting the old package.



This keeps the migration observable. A delivery SDK sits close to production content, so it is better to make a few boring changes and verify them than to do one large invisible rewrite.

## Where to be careful

Be careful around code that assumes a specific response shape. If a component expects the old SDK's transformed JSON, compare the old response and the new response before changing rendering logic.

Also check any custom query helpers. Method names such as includeReference, includeCount, only, except, skip, and limit still exist, but their placement in the chain may change depending on whether you are fetching a single entry, an entry collection, or a query object.

## Key takeaways

- This is an SDK migration, not a mandatory TypeScript rewrite.


- The Contentstack content model and published content do not need to change.


- Most code changes are package, initialization, and method-chain updates.


- Migrate through a small Contentstack wrapper when possible.


- Compare old and new responses around high-traffic pages before removing the JavaScript SDK.






---

## Install and initialize the TypeScript Delivery SDK

The safest migration starts with a clean SDK boundary. Replace the package, create the new stack client in one place, and keep the rest of your app calling that wrapper until each fetch path is migrated.

## What you'll learn

- How to replace the JavaScript Delivery SDK package


- How to initialize the TypeScript Delivery SDK


- How to keep environment variables stable during the migration


- How to create a wrapper that works from JavaScript or TypeScript


- How to verify the first request before converting the rest of the app



## Replace the package

Remove the old JavaScript SDK when you are ready to switch:

```bash
npm uninstall contentstack
npm install @contentstack/delivery-sdk@latest
```

If you need to migrate gradually, you can keep both packages temporarily, but avoid using both in the same helper file. Name the old and new clients clearly so reviewers can see which paths have moved.

## Initialize the new stack client

The JavaScript SDK initialization usually looks like this:

```javascript
import contentstack from 'contentstack';

export const Stack = contentstack.Stack({
  apiKey: process.env.CONTENTSTACK_API_KEY,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
  environment: process.env.CONTENTSTACK_ENVIRONMENT,
});
```

The TypeScript Delivery SDK uses the new package and lowercase stack() initializer:

```javascript
import contentstack from '@contentstack/delivery-sdk';

export const stack = contentstack.stack({
  apiKey: process.env.CONTENTSTACK_API_KEY,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
  environment: process.env.CONTENTSTACK_ENVIRONMENT,
});
```

Keep your existing environment variable names if they already work for your app. The SDK does not require you to rename CONTENTSTACK_API_KEY, CONTENTSTACK_DELIVERY_TOKEN, or CONTENTSTACK_ENVIRONMENT.

## Use one wrapper module

A wrapper gives the migration a stable place to happen:

```javascript
// lib/contentstack.js
import contentstack from '@contentstack/delivery-sdk';

export const stack = contentstack.stack({
  apiKey: process.env.CONTENTSTACK_API_KEY,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
  environment: process.env.CONTENTSTACK_ENVIRONMENT,
});

export function contentType(uid) {
  return stack.contentType(uid);
}
```

Now the rest of the app can move from direct SDK calls to helper functions:

```javascript
import { contentType } from './lib/contentstack';

export async function getBlogPost(uid) {
  return contentType('blog').entry(uid).fetch();
}
```

This is useful even in TypeScript projects because it keeps credentials, region configuration, plugins, cache policy, and preview settings in one place.

## Add response types when you want them

In a TypeScript file, you can add response types gradually:

```javascript
import contentstack, { BaseEntry } from '@contentstack/delivery-sdk';

interface BlogPost extends BaseEntry {
  title: string;
  url: string;
}

const stack = contentstack.stack({
  apiKey: process.env.CONTENTSTACK_API_KEY!,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN!,
  environment: process.env.CONTENTSTACK_ENVIRONMENT!,
});

export async function getBlogPost(uid: string) {
  return stack.contentType('blog').entry(uid).fetch<BlogPost>();
}
```

You do not need to add all response types before shipping the migration. Start with high-value content types where typed fields prevent real mistakes.

## Verify one request

Before converting every query, verify a single published entry:

```javascript
const entry = await stack.contentType('blog').entry('entry_uid').fetch();

console.log(entry.uid, entry.title);
```

If this fails, check credentials and environment first. If the old SDK worked and the new SDK does not, make sure the new stack client uses the same region, environment, delivery token, and branch assumptions as the old client.

## Key takeaways

- Install @contentstack/delivery-sdk to use the TypeScript Delivery SDK.


- Use contentstack.stack() instead of contentstack.Stack().


- Keep SDK setup in one wrapper so the migration stays controlled.


- You can add TypeScript response types incrementally.


- Verify one entry fetch before migrating every page.






---

## Convert stack, entry, asset, and content type calls

Once the new stack client exists, the migration becomes a series of request-chain translations. The important pattern is that the TypeScript Delivery SDK uses lowercase builders and returns data directly from fetch() and find().

## What you'll learn

- How to migrate single-entry reads


- How to migrate entry collections


- How to migrate assets


- How to migrate content type reads


- How to migrate sync calls


- How to avoid common chain-order mistakes



## Stack sync

In the JavaScript SDK, an initial sync often passes init: true:

```javascript
const result = await Stack.sync({ init: true });
```

In the TypeScript Delivery SDK, initialize sync with sync():

```javascript
const result = await stack.sync();
```

Other sync options stay familiar:

```javascript
await stack.sync({ locale: 'en-us' });
await stack.sync({ start_date: '2026-01-01' });
await stack.sync({ content_type_uid: 'blog' });
await stack.sync({ type: 'entry_published' });
await stack.sync({ pagination_token: paginationToken });
await stack.sync({ sync_token: syncToken });
```

Treat sync carefully if your app stores sync tokens. Verify that token persistence, retry behavior, and scheduled jobs still behave as expected after the SDK switch.

## Single entries

JavaScript SDK:

```javascript
const result = await Stack.ContentType('blog')
  .Entry('entry_uid')
  .toJSON()
  .fetch();
```

TypeScript Delivery SDK:

```javascript
const result = await stack.contentType('blog')
  .entry('entry_uid')
  .fetch();
```

With TypeScript response typing:

```javascript
const result = await stack.contentType('blog')
  .entry('entry_uid')
  .fetch<BlogPost>();
```

Remove toJSON() during this migration unless you have a specific compatibility layer that still depends on it.

## Entry options

The method names stay close, but the chain changes:

```javascript
await stack.contentType('blog')
  .entry('entry_uid')
  .includeBranch()
  .fetch();

await stack.contentType('blog')
  .entry('entry_uid')
  .includeFallback()
  .fetch();

await stack.contentType('blog')
  .entry('entry_uid')
  .only('title')
  .fetch();

await stack.contentType('blog')
  .entry('entry_uid')
  .except('internal_notes')
  .fetch();

await stack.contentType('blog')
  .entry('entry_uid')
  .locale('fr-fr')
  .fetch();
```

The old language() method becomes locale().

## Entry collections

JavaScript SDK:

```javascript
const result = await Stack.ContentType('blog')
  .Query()
  .toJSON()
  .find();
```

TypeScript Delivery SDK:

```javascript
const result = await stack.contentType('blog')
  .entry()
  .find();
```

For more complex filters, use query():

```javascript
const result = await stack.contentType('blog')
  .entry()
  .query()
  .equalTo('featured', true)
  .find();
```

Use entry('entry_uid') when you already know the entry UID. Use entry() when you are fetching a collection.

## Assets

JavaScript SDK:

```javascript
const asset = await Stack.Assets('asset_uid')
  .toJSON()
  .fetch();

const assets = await Stack.Assets()
  .toJSON()
  .find();
```

TypeScript Delivery SDK:

```javascript
const asset = await stack.asset('asset_uid').fetch();

const assets = await stack.asset().find();
```

Asset queries can also use familiar collection helpers:

```javascript
const assets = await stack.asset()
  .includeCount()
  .limit(20)
  .orderByDescending('updated_at')
  .find();
```

## Content types

Fetch one content type:

```javascript
const contentType = await stack.contentType('blog').fetch();
```

Fetch content types:

```javascript
const contentTypes = await stack.contentType().find();
```

Include global field schema details when retrieving content type information:

```javascript
const contentTypes = await stack.contentType()
  .includeGlobalFieldSchema()
  .find();
```

This is useful when schema inspection is part of your application or tooling. It is not usually needed for normal page rendering.

## Key takeaways

- Use lowercase SDK builders: stack, contentType, entry, and asset.


- Use entry(uid).fetch() for one entry and entry().find() for collections.


- Replace language() with locale().


- Remove toJSON() from migrated TypeScript Delivery SDK chains.


- Verify response shapes anywhere rendering code depends on transformed fields.






---

## Rewrite queries, pagination, sorting, and field selection

Query migration is where most applications spend their time. A page may render correctly with a single entry fetch, but listing pages, search pages, reference-heavy pages, and route discovery usually rely on query chains.

## What you'll learn

- How JavaScript SDK query chains map to TypeScript Delivery SDK query chains


- How to migrate filters, references, sorting, and pagination


- How to select or exclude fields


- How to keep query helpers readable


- Which limitations to remember while testing



## Start from the query object

JavaScript SDK:

```javascript
const Query = Stack.ContentType('blog').Query();

const result = await Query
  .where('featured', true)
  .includeReference('author')
  .descending('published_date')
  .limit(10)
  .toJSON()
  .find();
```

TypeScript Delivery SDK:

```javascript
const query = stack.contentType('blog').entry().query();

const result = await query
  .equalTo('featured', true)
  .includeReference('author')
  .orderByDescending('published_date')
  .limit(10)
  .find();
```

The main changes are the query starting point and the sorting method names.

## Common query translations

Use this table as a migration checklist:

JavaScript Delivery SDK

TypeScript Delivery SDK

Stack.ContentType('blog').Query()

stack.contentType('blog').entry().query()

.ascending('title')

.orderByAscending('title')

.descending('date')

.orderByDescending('date')

.language('fr-fr')

.locale('fr-fr')

.addParam(key, value)

.param(key, value) or .addParams({ [key]: value })

.toJSON().find()

.find()

.toJSON().fetch()

.fetch()

Do not migrate every query by search-and-replace alone. The old SDK has patterns such as Query().findOne() and query-level cache policy calls that may need a more deliberate rewrite.

## Field selection

Use only() when a route or listing page needs a small payload:

```javascript
const result = await stack.contentType('blog')
  .entry()
  .only('title', 'url', 'published_date')
  .find();
```

Use except() when most fields are useful but a few should stay out of the response:

```javascript
const result = await stack.contentType('blog')
  .entry()
  .except('internal_notes')
  .find();
```

If your app includes large references, rich text fields, or asset-heavy modular blocks, field selection can be part of the migration instead of a later optimization.

## References and embedded items

Reference-heavy pages are common in Contentstack projects. Migrate them explicitly:

```javascript
const page = await stack.contentType('page')
  .entry('entry_uid')
  .includeReference('sections.author', 'related_posts')
  .includeEmbeddedItems()
  .fetch()
```

For collection queries:

```javascript
const pages = await stack.contentType('page')
  .entry()
  .query()
  .includeReference('sections.author', 'related_posts')
  .includeEmbeddedItems()
  .find();
```

After migration, compare reference depth and embedded item behavior on pages that use rich text, modular blocks, or shared components.

## Filtering

Simple equality filters can be expressed with equalTo():

```javascript
const result = await stack.contentType('product')
  .entry()
  .query()
  .equalTo('category', 'rings')
  .find();
```

For list-style filters, use methods such as containedIn() and notContainedIn():

```javascript
const result = await stack.contentType('product')
  .entry()
  .query()
  .containedIn('tags', ['featured', 'new'])
  .find();
```

For range, existence, search, tags, taxonomy, and reference filters, migrate each helper by checking the TypeScript Delivery SDK reference. The methods are familiar, but exact names and argument order matter.

## Pagination

Traditional skip and limit still work:

```javascript
const result = await stack.contentType('blog')
  .entry()
  .query()
  .skip(20)
  .limit(20)
  .find();
```

The TypeScript Delivery SDK also supports pagination helpers:

```javascript
const query = stack.contentType('blog').entry().query();

const firstPage = await query.paginate().find();
const nextPage = await query.next().find();
const previousPage = await query.previous().find();
```

Use the style that best matches your UI and existing route behavior. For numbered pages, skip() and limit() may be easiest to keep. For cursor-like navigation, pagination helpers can keep the code clearer.

## Sorting

Replace old sort helpers with the explicit ordering methods:

```javascript
const newest = await stack.contentType('blog')
  .entry()
  .query()
  .orderByDescending('published_date')
  .find();

const alphabetical = await stack.contentType('blog')
  .entry()
  .query()
  .orderByAscending('title')
  .find();
```

Be careful with routes that relied on default ordering. If the old SDK and the new SDK return entries in a different order, add an explicit sort.

## Limits to remember

The current TypeScript Delivery SDK has a few practical limits to keep in mind:

- Content Delivery API requests that exceed the CDN URL size limit can fail.


- Multiple content type referencing in a single query is not supported.


- Global field schema querying is not exposed as a standalone query, but content type details can include global field schema data.



These limits are easiest to catch with real migrated pages, not isolated examples.

## Key takeaways

- Start entry queries with stack.contentType(uid).entry().query().


- Replace ascending() and descending() with orderByAscending() and orderByDescending().


- Keep skip() and limit() when they match your current pagination model.


- Compare reference and embedded item responses on complex pages.


- Add explicit sorting when page order matters.






---

## Caching, utilities, preview behavior, and runtime differences

The base delivery migration is only part of the story. Many production apps also rely on cache policies, rich text rendering utilities, Live Preview, Visual Builder behavior, plugins, or runtime-specific imports. Move those pieces deliberately.

## What you'll learn

- How caching changes in the TypeScript Delivery SDK


- When to install the persistence plugin


- When to install the utilities package


- How to think about Live Preview and Visual Builder during migration


- What runtime behavior to check before shipping



## Cache policies

In the JavaScript SDK, cache policy is commonly set directly on the stack or query:

```javascript
Stack.setCachePolicy(Contentstack.CachePolicy.NETWORK_ELSE_CACHE);
```

In the TypeScript Delivery SDK, caching uses a separate persistence plugin:

```javascript
npm install @contentstack/persistance-plugin@latest
```

Then configure cache options when creating the stack:

```javascript
import contentstack, { Policy } from '@contentstack/delivery-sdk';
import PersistanceStore from '@contentstack/persistance-plugin';

export const stack = contentstack.stack({
  apiKey: process.env.CONTENTSTACK_API_KEY,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
  environment: process.env.CONTENTSTACK_ENVIRONMENT,
  cacheOptions: {
    policy: Policy.CACHE_THEN_NETWORK,
    persistanceStore: new PersistanceStore({
      storeType: 'localStorage',
    }),
  },
});
```

If your app does not use caching, or if your policy is Policy.IGNORE_CACHE, you do not need the persistence plugin.

## Utilities

If your JavaScript SDK code uses utility helpers for rich text, embedded entries, or rendering support, install the utilities package separately:

```javascript
npm install @contentstack/utils@latest
```

Then import it where rendering or transformation happens:

```javascript
import * as Utils from '@contentstack/utils';
```

Keep utilities out of your low-level stack client unless every request needs them. Rich text rendering and embedded item transformation usually belong close to rendering code or content normalization helpers.

## Live Preview and Visual Builder

Live Preview and Visual Builder integrations often combine SDK configuration, preview tokens, host settings, URL handling, and frontend rendering behavior.

During migration, verify these flows separately from published delivery:

- a normal published page load


- a draft entry opened from the editorial interface


- a locale-specific preview


- a page that includes references


- a page with rich text embedded items


- a page composed from modular blocks



Do not assume that a successful published fetch() proves preview is working. Published delivery and preview behavior use different credentials and runtime conditions.

## Plugins

If your JavaScript SDK setup uses custom plugins, move plugin setup into the new contentstack.stack() call:

```javascript
class RequestPlugin {
  onRequest(request) {
    return request;
  }

  async onResponse(request, response) {
    return response;
  }
}

export const stack = contentstack.stack({
  apiKey: process.env.CONTENTSTACK_API_KEY,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
  environment: process.env.CONTENTSTACK_ENVIRONMENT,
  plugins: [new RequestPlugin()],
});
```

Use plugins for cross-cutting request behavior. Avoid hiding content modeling decisions or page-specific query logic inside plugins.

## Browser and server runtimes

A delivery SDK can run in browser-based apps and server-rendered apps, but credentials and caching choices still matter.

Check these runtime details before shipping:

- Are delivery credentials intentionally exposed to the browser?


- Are preview tokens only exposed where preview actually needs them?


- Does localStorage caching run only in browser contexts?


- Does server-side rendering create one stack client safely?


- Are environment variables available in the runtime that calls the SDK?


- Does your bundler handle ESM imports from @contentstack/delivery-sdk?



If the app renders on both server and client, keep stack creation predictable and avoid accessing browser-only APIs while the server is importing modules.

## Key takeaways

- Caching is no longer only a stack method; use @contentstack/persistance-plugin when needed.


- Install @contentstack/utils separately when your rendering code needs Contentstack utilities.


- Verify preview flows separately from published delivery flows.


- Keep browser-only cache stores out of server-only code paths.


- Move custom plugins into the new contentstack.stack() configuration.






---

## Using the TypeScript Delivery SDK in JavaScript projects

One of the most common migration worries is also one of the easiest to clear up: using the TypeScript Delivery SDK does not mean your whole website has to be written in TypeScript.

The package is called the TypeScript Delivery SDK because it ships strong TypeScript support and can type responses in TypeScript projects. It still runs as JavaScript in your application. A JavaScript app can import it, initialize it, and fetch Contentstack content without renaming every file to .ts.

## What you'll learn

- Why the SDK works in JavaScript projects


- What you lose when you do not use TypeScript syntax


- How to migrate a JavaScript codebase safely


- How to add optional type checking with JSDoc


- How to create a path toward TypeScript later without forcing it today



## A plain JavaScript example

This is valid JavaScript:

```javascript
import contentstack from '@contentstack/delivery-sdk';

const stack = contentstack.stack({
  apiKey: process.env.CONTENTSTACK_API_KEY,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
  environment: process.env.CONTENTSTACK_ENVIRONMENT,
});

export async function getHomePage() {
  return stack.contentType('page')
    .entry()
    .query()
    .equalTo('url', '/')
    .includeReference('sections')
    .find();
}
```

There are no interfaces, no generics, and no TypeScript compiler requirement in this file. The SDK is just an npm dependency.

## What changes if you stay in JavaScript

If your files stay as .js, you should not use TypeScript-only syntax:

```javascript
// TypeScript-only syntax
const entry = await stack.contentType('page')
  .entry(uid)
  .fetch<PageEntry>();
```

In JavaScript, call the same SDK without the generic:

```javascript
const entry = await stack.contentType('page')
  .entry(uid)
  .fetch();
```

You still get the runtime behavior of the new SDK. You just do not get compile-time field checking unless your project adds TypeScript or JavaScript type checking later.

## Use JSDoc if you want lighter type help

Some JavaScript teams want editor hints without converting the project. JSDoc can help:

```javascript
/**
 * @typedef {object} PageEntry
 * @property {string} uid
 * @property {string} title
 * @property {string} url
 */

/**
 * @param {string} uid
 * @returns {Promise<PageEntry>}
 */
export async function getPage(uid) {
  return stack.contentType('page').entry(uid).fetch();
}
```

This is optional. It can be a useful middle step for teams that want safer content access but are not ready for a full TypeScript conversion.

## A practical message for teams

The migration does not have to become a larger engineering program than intended.

You can choose one of three paths:

- Use the TypeScript Delivery SDK from JavaScript and keep the app as JavaScript.


- Use the SDK from JavaScript now, then add JSDoc for the most important content shapes.


- Use the SDK from JavaScript now, then gradually move selected helper files to TypeScript later.



All three are valid. The SDK migration should improve your delivery layer without forcing unrelated decisions about framework, routing, rendering, or project language.

## A good JavaScript migration boundary

For JavaScript codebases, keep SDK usage in a small set of files:

```javascript
// lib/contentstack.js
import contentstack from '@contentstack/delivery-sdk';

export const stack = contentstack.stack({
  apiKey: process.env.CONTENTSTACK_API_KEY,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
  environment: process.env.CONTENTSTACK_ENVIRONMENT,
});

export async function fetchEntry(contentTypeUid, entryUid) {
  return stack.contentType(contentTypeUid).entry(entryUid).fetch();
}

export async function fetchEntries(contentTypeUid, buildQuery) {
  const query = stack.contentType(contentTypeUid).entry().query();
  return buildQuery ? buildQuery(query).find() : query.find();
}
```

The rest of the app can import these helpers instead of importing the SDK everywhere. Later, if the team decides to adopt TypeScript, this file is a natural first candidate.

## Key takeaways

- The TypeScript Delivery SDK can be used from JavaScript projects.


- You do not need to convert your whole codebase to TypeScript.


- JavaScript files should skip TypeScript-only syntax such as interfaces and generics.


- JSDoc can provide optional editor help in JavaScript codebases.


- A small SDK wrapper makes future TypeScript adoption easier without forcing it now.






---

## Migration checklist, troubleshooting, and next steps

The migration is finished when your app can fetch the same published and preview content through the new SDK, not when the import statement compiles. Use this final chapter as a practical closeout list.

## What you'll learn

- How to review the migration before merge


- Which behaviors to test manually


- Which errors usually point to configuration issues


- How to roll the migration out safely


- Where to go next in the Contentstack docs



## Migration checklist

Before merging, confirm that you have handled the core SDK changes:

- contentstack package replaced with @contentstack/delivery-sdk


- contentstack.Stack() replaced with contentstack.stack()


- Stack.ContentType() calls replaced with stack.contentType()


- Entry() calls replaced with entry()


- Assets() or Asset() calls replaced with asset()


- Query() calls replaced with entry().query()


- language() replaced with locale()


- ascending() and descending() replaced with orderByAscending() and orderByDescending()


- toJSON() removed from migrated SDK chains


- cache policy usage moved to @contentstack/persistance-plugin when needed


- utility usage moved to @contentstack/utils when needed



Then confirm the application behavior:

- homepage renders published content


- listing pages render expected entries


- detail pages resolve by UID or URL


- references are included at the expected depth


- rich text embedded items still render


- assets include the fields your components expect


- locale and fallback behavior matches the old implementation


- preview mode can read draft content


- route generation or static path generation returns the same routes


- build, lint, and type checks pass where the project defines them



## Troubleshooting

### The app cannot initialize the stack

Check the import and initializer first:

```javascript
import contentstack from '@contentstack/delivery-sdk';

const stack = contentstack.stack({
  apiKey,
  deliveryToken,
  environment,
});
```

If this code is correct, check environment variable availability in the runtime. Server-only variables may not exist in browser code, and browser-exposed variables may need a framework-specific prefix.

### A single entry fetch works, but a listing page fails

The entry fetch path and query path are different. Convert old Query() chains to the new query starting point:

```javascript
const query = stack.contentType('blog').entry().query();
```

Then reapply filters, sorting, references, limits, and locale behavior one by one.

### Sorting changed

Add explicit sorting:

```javascript
await stack.contentType('blog')
  .entry()
  .query()
  .orderByDescending('published_date')
  .find();
```

Do not rely on implicit ordering when the UI depends on order.

### Locale content is missing

Replace language() with locale() and verify that the requested locale has published entries:

```javascript
await stack.contentType('page')
  .entry('entry_uid')
  .locale('fr-fr')
  .includeFallback()
  .fetch();
```

If fallback content is expected, confirm that fallback behavior is configured in Contentstack and included in the request chain.

### Cache behavior changed

If the old app set cache policies directly on the stack or query, install and configure the persistence plugin. If the app renders on the server, do not configure a browser-only store such as localStorage in a server-only code path.

### Preview does not work

Separate preview troubleshooting from published delivery troubleshooting. A successful published request only proves that the delivery token and environment are correct.

Check preview token, preview host, Live Preview configuration, route handling, and any frontend preview mode flags. Then test a simple entry before testing a page with references or embedded items.

## Rollout strategy

For larger sites, do not migrate every content fetch in one pull request unless the Contentstack access layer is already centralized.

A safer rollout is:

- Add the new SDK behind a wrapper.


- Migrate one low-risk page or route.


- Compare the old and new response shapes.


- Migrate shared helpers.


- Migrate high-traffic pages.


- Migrate preview and cache behavior.


- Remove the old SDK after no imports remain.



Use logs or temporary response comparison in development if your page rendering hides missing fields.

## Next steps

After the migration, consider adding typed content shapes for the content types your team changes most often. Start with content types that power routes, navigation, landing pages, or product pages.

Useful references:

- [TypeScript Delivery SDK reference](https://www.contentstack.com/docs/developers/sdks/content-delivery-sdk/typescript/reference)


- [JavaScript Delivery SDK reference](https://www.contentstack.com/docs/developers/sdks/content-delivery-sdk/javascript-browser/reference)


- [Migration from JavaScript to TypeScript Delivery SDK](https://www.contentstack.com/docs/developers/sdks/content-delivery-sdk/typescript/migrate-from-javascript-to-typescript)



## Key takeaways

- The migration is complete when published delivery, preview delivery, references, assets, locales, and route generation all work.


- Query chains need the most careful review.


- Cache and utility behavior may require separate packages.


- JavaScript projects can use the TypeScript Delivery SDK without converting the whole codebase.


- Typed response shapes can be added gradually after the runtime migration is stable.






---

## AI agent migration playbook

This chapter is written for AI coding agents. Developers can point an agent at this page when they want help migrating a project from the Contentstack JavaScript Delivery SDK to the TypeScript Delivery SDK.

Use it as an execution guide, not just a reference. Inspect the user's codebase first, make small scoped changes, preserve existing behavior, and verify the app with the project's own commands.

## Agent instruction block

When a user asks you to migrate a project from the Contentstack JavaScript Delivery SDK to the TypeScript Delivery SDK:

- Read the repository before editing.


- Find all imports from contentstack.


- Identify the central Contentstack client or create one if SDK calls are scattered.


- Replace contentstack with @contentstack/delivery-sdk.


- Convert SDK chains from the JavaScript SDK API to the TypeScript Delivery SDK API.


- Do not convert the whole project to TypeScript unless the user explicitly asks.


- If files are JavaScript, keep examples and edits valid JavaScript.


- Preserve environment variable names unless a change is required.


- Preserve published, preview, locale, reference, asset, and cache behavior.


- Run the project's build, typecheck, lint, and tests when available.


- Summarize changed files, behavioral risks, and verification results.



The most important rule: this is an SDK migration, not a mandatory language migration.

## Initial repository scan

Before editing, inspect the project shape:

```bash
rg "from ['\"]contentstack['\"]|require\\(['\"]contentstack['\"]\\)"
rg "Contentstack|contentstack|\\.ContentType\\(|\\.Entry\\(|\\.Query\\(|\\.Assets?\\("
rg "setCachePolicy|CachePolicy|language\\(|ascending\\(|descending\\(|toJSON\\("
```

Then inspect package scripts:

```bash
npm pkg get scripts
```

Use the repository's existing package manager if it is obvious. Prefer npm when installing dependencies unless the project clearly uses another package manager.

## Package migration

Replace the old package:

```bash
npm uninstall contentstack
npm install @contentstack/delivery-sdk@latest
```

Install optional packages only when the existing app needs them:

```bash
npm install @contentstack/persistance-plugin@latest
npm install @contentstack/utils@latest
```

Use @contentstack/persistance-plugin only when the old implementation uses cache policies. Use @contentstack/utils only when the app uses Contentstack utility behavior such as rich text or embedded item helpers.

## Core API translations

Apply these translations carefully:

JavaScript Delivery SDK

TypeScript Delivery SDK

import contentstack from 'contentstack'

import contentstack from '@contentstack/delivery-sdk'

contentstack.Stack(config)

contentstack.stack(config)

Stack.ContentType(uid)

stack.contentType(uid)

.Entry(uid)

.entry(uid)

.Query()

.entry().query()

Stack.Assets(uid) or Stack.Asset(uid)

stack.asset(uid)

.language(locale)

.locale(locale)

.ascending(field)

.orderByAscending(field)

.descending(field)

.orderByDescending(field)

.toJSON().fetch()

.fetch()

.toJSON().find()

.find()

Do not use blind search-and-replace for every chain. Read the call site and preserve whether it fetches one entry, a collection, an asset, a content type, or a sync result.

## JavaScript-safe migration examples

For a JavaScript project, keep code valid JavaScript:

```javascript
import contentstack from '@contentstack/delivery-sdk';

export const stack = contentstack.stack({
  apiKey: process.env.CONTENTSTACK_API_KEY,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
  environment: process.env.CONTENTSTACK_ENVIRONMENT,
});
```

Do not add TypeScript-only syntax to .js files:

```javascript
// Do not add this to JavaScript files.
const entry = await stack.contentType('page').entry(uid).fetch<PageEntry>();
```

Use the runtime call instead:

```javascript
const entry = await stack.contentType('page').entry(uid).fetch();
```

Only add interfaces, generics, or non-null assertions in TypeScript files.

## Common code transformations

Single entry:

```javascript
// Before
const entry = await Stack.ContentType('page')
  .Entry(uid)
  .toJSON()
  .fetch();

// After
const entry = await stack.contentType('page')
  .entry(uid)
  .fetch();
```

Entry collection:

```javascript
// Before
const entries = await Stack.ContentType('blog')
  .Query()
  .toJSON()
  .find();

// After
const entries = await stack.contentType('blog')
  .entry()
  .find();
```

Filtered query:

```javascript
// Before
const entries = await Stack.ContentType('blog')
  .Query()
  .where('featured', true)
  .descending('published_date')
  .toJSON()
  .find();

// After
const entries = await stack.contentType('blog')
  .entry()
  .query()
  .equalTo('featured', true)
  .orderByDescending('published_date')
  .find();
```

Asset:

```javascript
// Before
const asset = await Stack.Assets(assetUid)
  .toJSON()
  .fetch();

// After
const asset = await stack.asset(assetUid).fetch();
```

Locale and fallback:

```javascript
const entry = await stack.contentType('page')
  .entry(uid)
  .locale('fr-fr')
  .includeFallback()
  .fetch();
```

## Caching migration

If the old code uses setCachePolicy() or Contentstack.CachePolicy, configure cache options on the new stack client:

```javascript
import contentstack, { Policy } from '@contentstack/delivery-sdk';
import PersistanceStore from '@contentstack/persistance-plugin';

export const stack = contentstack.stack({
  apiKey: process.env.CONTENTSTACK_API_KEY,
  deliveryToken: process.env.CONTENTSTACK_DELIVERY_TOKEN,
  environment: process.env.CONTENTSTACK_ENVIRONMENT,
  cacheOptions: {
    policy: Policy.CACHE_THEN_NETWORK,
    persistanceStore: new PersistanceStore({
      storeType: 'localStorage',
    }),
  },
});
```

Avoid browser-only cache stores in server-only code paths. If the app renders on the server and client, check how the old code guarded access to browser APIs.

## Troubleshooting flow

If initialization fails, check import, package installation, and stack config.

If one entry fetch works but listings fail, focus on Query() migration and sorting/filter helpers.

If content is missing in a locale, check language() to locale() migration and includeFallback() behavior.

If references disappear, check includeReference() and includeEmbeddedItems() chains.

If preview fails, test published delivery and preview delivery separately. Verify preview token, preview host, Live Preview settings, route handling, and runtime flags.

If cache behavior changes, check whether the persistence plugin is installed and whether the selected store is valid in the current runtime.

If TypeScript errors appear in JavaScript files, remove TypeScript-only syntax and keep the SDK calls plain JavaScript.

## Completion criteria

The migration is complete when:

- no app code imports the old contentstack package


- package files include @contentstack/delivery-sdk


- single-entry, collection, asset, content type, and sync calls are migrated where used


- preview behavior is preserved where the project supports preview


- cache and utility behavior are migrated only when needed


- JavaScript projects remain JavaScript unless the user requested TypeScript conversion


- build, typecheck, lint, and tests pass where available


- the final response explains what changed and what was verified



## Final response template for agents

When you finish, report:

- files changed


- package changes


- SDK call patterns migrated


- commands run and their results


- any behavior that needs manual preview validation


- any remaining old SDK references, or state that none remain



Keep the response short and concrete. The user needs to know whether the migration is ready to review and what still deserves attention.



---

## Frequently asked questions

### Do I need to convert my entire project to TypeScript to use the TypeScript Delivery SDK?

No. You can install and use @contentstack/delivery-sdk from JavaScript files; TypeScript typing is optional.

### What changes are typically required when migrating to the TypeScript Delivery SDK?

Expect updates to imports, SDK initialization, stack calls, entry and asset reads, query chains, caching setup, and utility package usage.

### Is this migration primarily a language migration or an SDK API migration?

It is primarily an SDK API migration. The goal is to translate calls and patterns without rewriting the site.

### What is a practical approach to migrating without breaking existing pages?

Replace the package and stack initialization first, migrate reads behind a small wrapper, update entries/assets/queries in groups, then verify pages before removing the old SDK.

### When should I review caching, Live Preview, rich text utilities, or Visual Builder helpers during the migration?

Review these before merging if your app depends on cache policies, Live Preview, rich text utilities, or Visual Builder helpers, since they may move to separate packages or change usage.

