@cachely-io/sdk
Universal JavaScript SDK for Cachely. Routes CMS API and asset requests through your project's edge domain — adding edge caching, image optimization, and server-side token injection without changing your application logic.
Works in any JavaScript environment: Node.js 18+, browsers, Cloudflare Workers, Deno.
Built-in providers: Contentful, Prismic — plus any CMS via createGenericProvider
Install
CLI setup (recommended)
The SDK ships a deterministic CLI that handles detection and boilerplate generation.
Generic setup — detects framework + CMS + package manager automatically:
npx @cachely-io/sdk setup
Generates a typed helper file (lib/cachely.ts for Next.js, composables/useCachely.ts for Nuxt, cachely.ts for Vite/generic), adds CACHELY_PROXY_URL to .env.example, and installs the SDK.
# Specify tenant explicitly (generic helper flow)
npx @cachely-io/sdk setup --tenant my-project
# Preview without writing
npx @cachely-io/sdk setup --dry-run
# Non-interactive CI mode (generic helper for a framework you pick)
npx @cachely-io/sdk setup --yes --tenant my-project --framework next
Since 0.12.0, passing
--provider prismic/--provider contentful(andsanity/storyblok) routes to the provider's onboarder, not the generic helper flow. See the CLI reference and provider-specific onboarding below.
Provider-specific onboarding — deeper, CMS-aware setup. Available for Prismic (full integration) and Contentful (client-file patching):
npx @cachely-io/sdk setup --provider prismic --tenant my-project
npx @cachely-io/sdk setup prismic --tenant my-project
npx @cachely-io/sdk init prismic --tenant my-project
All three forms are equivalent (since 0.12.0).
Prismic onboarding generates composables/useCachelyPrismicClient.ts as the primary integration surface. On greenfield or trivially-patchable projects, it also writes app/prismic/client.js and patches config/nuxt/prismic.{js,ts}. Complex existing clients are not modified — manual instructions are printed. nuxt.config is never auto-patched.
See the CLI reference for the full flag set, integration-mode matrix, and what each step writes.
Manual install
npm install @cachely-io/sdk
# or: pnpm add @cachely-io/sdk
# or: yarn add @cachely-io/sdk
# or: bun add @cachely-io/sdk
Then write your own helper using createCachelyFetch — see Quick start below.
How it works
Instead of calling Contentful or Prismic directly, requests go through your project's edge domain:
Your app
→ Cachely edge (https://my-project.cachely.io)
→ Contentful / Prismic / ...
@cachely-io/sdk handles the URL rewriting at the transport layer. You configure it once and drop it into your CMS SDK — no other code changes needed.
Quick start
Contentful
import { createCachelyFetch } from '@cachely-io/sdk'
import { createClient } from 'contentful'
const cachelyFetch = createCachelyFetch({
tenant: 'my-project', // your Cachely project slug
provider: 'contentful',
providerConfig: {
spaceId: 'abc123xyz', // your Contentful space ID
},
})
const client = createClient({
space: 'abc123xyz',
accessToken: '', // leave empty — injected server-side by the proxy
adapter: cachelyFetch,
})
// All API calls go through the proxy automatically:
// https://cdn.contentful.com/spaces/abc123xyz/entries
// → https://my-project.cachely.io/~api/spaces/abc123xyz/entries
Prismic
import { createCachelyFetch } from '@cachely-io/sdk'
import * as prismic from '@prismicio/client'
const cachelyFetch = createCachelyFetch({
tenant: 'my-project',
provider: 'prismic',
})
const client = prismic.createClient('my-repo', {
fetch: cachelyFetch,
})
// All API calls go through the proxy automatically:
// https://my-repo.cdn.prismic.io/api/v2/documents/search?ref=...
// → https://my-project.cachely.io/~api/api/v2/documents/search?ref=...
Any CMS via the generic provider
import { createGenericProvider, createCachelyFetch } from '@cachely-io/sdk'
const storyblok = createGenericProvider({
id: 'storyblok',
apiHosts: ['api.storyblok.com'],
assetHosts: ['a.storyblok.com'],
})
const cachelyFetch = createCachelyFetch({
tenant: 'my-project',
provider: storyblok, // pass ProviderDefinition directly — no registry needed
})
// https://api.storyblok.com/v2/stories → https://my-project.cachely.io/~api/v2/stories
// https://a.storyblok.com/f/123/hero.jpg → https://my-project.cachely.io/f/123/hero.jpg
URL rewriting without fetch
Use createCachelyUrlRewriter when you need to transform URLs without intercepting fetch — useful for server-side response body processing, logging, or building custom adapters.
import { createCachelyUrlRewriter } from '@cachely-io/sdk'
const rewrite = createCachelyUrlRewriter({
tenant: 'my-project',
provider: 'contentful',
providerConfig: { spaceId: 'abc123xyz' },
})
// API URL
rewrite('https://cdn.contentful.com/spaces/abc123xyz/entries?locale=en')
// → { url: 'https://my-project.cachely.io/~api/spaces/abc123xyz/entries?locale=en', kind: 'api', rewritten: true }
// Asset URL — image optimization params are preserved
rewrite('https://images.ctfassets.net/abc123xyz/assetId/token/hero.jpg?w=1200&fm=webp')
// → { url: 'https://my-project.cachely.io/assetId/token/hero.jpg?w=1200&fm=webp', kind: 'asset', rewritten: true }
// Non-CMS URL — passes through unchanged
rewrite('https://fonts.googleapis.com/css2?family=Inter')
// → { url: 'https://fonts.googleapis.com/css2?family=Inter', kind: 'unknown', rewritten: false }
Response transformer
When you fetch a CMS response on the server and want to rewrite the asset URLs inside the JSON body (rather than intercepting fetch calls), use the response transformer. It's pure JavaScript with zero deps and ships in the same @cachely-io/sdk package.
import { transformAssetUrls } from '@cachely-io/sdk'
// process.env.CACHELY_PROXY_URL = 'https://my-project.cachely.io'
const data = await fetch('https://cdn.contentful.com/spaces/abc/entries').then(r => r.json())
const transformed = transformAssetUrls(data, {
cms: 'contentful',
spaceId: 'abc',
// Reads from process.env.CACHELY_PROXY_URL automatically.
// Pass `proxyUrl: 'https://...'` to override.
})
Per-CMS exports
import {
transformPrismicAssetUrls,
transformContentfulAssetUrls,
transformSanityAssetUrls,
transformShopifyAssetUrls,
transformCloudinaryAssetUrls,
transformImgixAssetUrls,
transformGenericAssetUrls,
} from '@cachely-io/sdk'
// Prismic
const out = transformPrismicAssetUrls(data, {
repository: 'my-repo',
proxyUrl: 'https://my-project.cachely.io',
})
// Contentful
const out2 = transformContentfulAssetUrls(data, {
spaceId: 'abc123xyz',
proxyUrl: 'https://my-project.cachely.io',
})
getCachelyProxyBase(options)
Resolve the proxy base URL the same way the transformer does:
options.proxyUrl(explicit override)process.env.CACHELY_PROXY_URLprocess.env.CMS_ASSETS_URL— temporary launch-window fallback for projects migrating from the legacy env var
import { getCachelyProxyBase } from '@cachely-io/sdk'
const base = getCachelyProxyBase()
// 'https://my-project.cachely.io/' (trailing slash; query/hash stripped)
Required options per CMS
cms | Required option |
|---|---|
prismic | repository: string |
contentful | spaceId: string |
sanity | projectId: string (dataset?: string, default 'production') |
shopify | storeDomain: string |
cloudinary | cloudName: string |
imgix | imgixDomain: string |
generic | originUrl: string |
API reference
createCachelyFetch(options)
Returns a fetch-compatible function that rewrites CMS URLs through the proxy. Safe to use as a global fetch replacement — non-CMS requests pass through unchanged.
const cachelyFetch = createCachelyFetch({
tenant: 'my-project', // required — Cachely project slug
provider: 'contentful', // required — provider id string OR a ProviderDefinition object
providerConfig: { // optional — provider-specific config
spaceId: 'abc123xyz', // Contentful: spaceId
},
enableApiProxy: true, // optional — rewrite API requests (default: true)
enableAssetProxy: true, // optional — rewrite asset requests (default: true)
registry: myRegistry, // optional — custom provider registry (used when provider is a string)
})
Selective proxying:
// Asset proxy only — API calls go directly to Contentful
const cachelyFetch = createCachelyFetch({
tenant: 'my-project',
provider: 'contentful',
enableApiProxy: false,
})
// API proxy only — images are not proxied
const cachelyFetch = createCachelyFetch({
tenant: 'my-project',
provider: 'prismic',
enableAssetProxy: false,
})
createCachelyUrlRewriter(options)
Returns a bound (url: string | URL) => RewriteResult function. Same options as createCachelyFetch.
import { createCachelyUrlRewriter } from '@cachely-io/sdk'
const rewrite = createCachelyUrlRewriter({
tenant: 'my-project',
provider: 'prismic',
})
const { url, kind, rewritten } = rewrite('https://images.prismic.io/my-repo/hero.jpg?w=800&fm=webp')
// url: 'https://my-project.cachely.io/my-repo/hero.jpg?w=800&fm=webp'
// kind: 'asset'
// rewritten: true
classifyUrl(url, provider)
Classify a URL as 'api', 'asset', or 'unknown' based on its hostname.
import { classifyUrl, contentful, prismic } from '@cachely-io/sdk'
classifyUrl('https://cdn.contentful.com/spaces/abc/entries', contentful) // 'api'
classifyUrl('https://images.ctfassets.net/abc/assetId/token/img.jpg', contentful) // 'asset'
classifyUrl('https://my-repo.cdn.prismic.io/api/v2/documents/search', prismic) // 'api'
classifyUrl('https://images.prismic.io/my-repo/photo.jpg', prismic) // 'asset'
classifyUrl('https://example.com/logo.svg', contentful) // 'unknown'
rewriteUrl(url, kind, provider, tenant, config?)
Low-level rewrite — takes a pre-classified URL and returns a RewriteResult.
import { classifyUrl, rewriteUrl, contentful } from '@cachely-io/sdk'
const url = new URL('https://cdn.contentful.com/spaces/abc/entries?locale=en')
const kind = classifyUrl(url, contentful)
const result = rewriteUrl(url, kind, contentful, 'my-project', { spaceId: 'abc' })
// result.url → 'https://my-project.cachely.io/~api/spaces/abc/entries?locale=en'
// result.kind → 'api'
// result.rewritten → true
ProviderRegistry
Register custom or extended provider definitions.
import { ProviderRegistry, contentful, prismic } from '@cachely-io/sdk'
const registry = new ProviderRegistry()
.register(contentful)
.register(prismic)
const cachelyFetch = createCachelyFetch({
tenant: 'my-project',
provider: 'contentful',
registry,
})
The defaultRegistry is a shared singleton pre-populated with contentful and prismic. You only need a custom registry if you want to override a built-in provider or register your own.
createGenericProvider(config)
Create a ProviderDefinition from a config object — use any CMS or API origin without writing a custom provider from scratch.
import { createGenericProvider } from '@cachely-io/sdk'
const provider = createGenericProvider({
id: 'storyblok', // unique provider id
apiHosts: ['api.storyblok.com'], // API hostnames (exact or wildcard)
assetHosts: ['a.storyblok.com'], // asset hostnames
// Optional — stored for future Worker-side support, not yet processed by proxy
auth: {
header: 'Authorization',
format: 'Bearer {token}',
},
previewBypass: {
queryParams: ['preview', 'draft'],
},
})
The returned ProviderDefinition plugs directly into createCachelyFetch, createCachelyUrlRewriter, classifyUrl, rewriteUrl, and ProviderRegistry.
URL rewriting follows standard Cachely conventions:
API: https://api.storyblok.com/v2/stories → https://{tenant}.cachely.io/~api/v2/stories
Asset: https://a.storyblok.com/f/123/img.jpg → https://{tenant}.cachely.io/f/123/img.jpg
API_PREFIX
The /~api path prefix used by the Cachely API proxy.
import { API_PREFIX } from '@cachely-io/sdk'
console.log(API_PREFIX) // '/~api'
Providers
Contentful
| Kind | Hosts |
|---|---|
| API | cdn.contentful.com, preview.contentful.com |
| Assets | images.ctfassets.net, videos.ctfassets.net, assets.ctfassets.net, downloads.ctfassets.net |
providerConfig.spaceId — Required for correct asset path stripping. When provided, the space ID segment is removed from the proxy URL path.
// Without spaceId:
// https://images.ctfassets.net/abc/assetId/token/img.jpg
// → https://my-project.cachely.io/abc/assetId/token/img.jpg
// With spaceId: 'abc':
// https://images.ctfassets.net/abc/assetId/token/img.jpg
// → https://my-project.cachely.io/assetId/token/img.jpg ← spaceId stripped
Prismic
| Kind | Hosts |
|---|---|
| API | *.cdn.prismic.io (wildcard — matches any repo subdomain) |
| Assets | images.prismic.io, prismic-io.imgix.net |
Prismic image optimization params (w, h, q, fm, auto, fit) are preserved end-to-end.
// Image optimization is preserved:
// https://images.prismic.io/my-repo/hero.jpg?auto=format,compress&w=920&fm=webp&q=80
// → https://my-project.cachely.io/my-repo/hero.jpg?auto=format,compress&w=920&fm=webp&q=80
Prismic's ref parameter changes on every content publish, making each published version a distinct cache key — no manual cache invalidation needed.
TypeScript
All types are exported from the package root:
import type {
// Fetch / rewrite
RequestKind, // 'api' | 'asset' | 'unknown'
ProviderDefinition, // shape of a provider
ProviderConfig, // Record<string, string | undefined>
RewriteResult, // { url, kind, rewritten }
CachelyFetchOptions, // options for createCachelyFetch
UrlRewriterOptions, // options for createCachelyUrlRewriter
GenericProviderConfig, // config shape for createGenericProvider
// Response transformer
TransformOptions, // shared base options
PrismicTransformOptions,
ContentfulTransformOptions,
SanityTransformOptions,
ShopifyTransformOptions,
CloudinaryTransformOptions,
ImgixTransformOptions,
GenericTransformOptions,
CmsDispatchOptions, // discriminated union for transformAssetUrls
SupportedCms, // 'prismic' | 'contentful' | … | 'generic'
StringTransformer, // (jsonStr, ctx) => jsonStr
TransformerContext, // { base, nodeEnv }
} from '@cachely-io/sdk'
Custom providers
Using createGenericProvider (recommended)
The easiest way to add support for any CMS:
import { createGenericProvider, createCachelyFetch } from '@cachely-io/sdk'
const sanity = createGenericProvider({
id: 'sanity',
apiHosts: ['*.api.sanity.io'],
assetHosts: ['cdn.sanity.io'],
auth: { header: 'Authorization', format: 'Bearer {token}' },
})
// Pass directly — no registry needed
const cachelyFetch = createCachelyFetch({ tenant: 'my-project', provider: sanity })
// Or register and use by id string
import { defaultRegistry } from '@cachely-io/sdk'
defaultRegistry.register(sanity)
const cachelyFetch2 = createCachelyFetch({ tenant: 'my-project', provider: 'sanity' })
Using ProviderDefinition directly (advanced)
For full control over rewrite logic, implement the interface manually:
import { ProviderDefinition, defaultRegistry } from '@cachely-io/sdk'
const sanity: ProviderDefinition = {
id: 'sanity',
apiHosts: ['*.api.sanity.io'],
assetHosts: ['cdn.sanity.io'],
rewriteApiUrl(url, tenant) {
return `https://${tenant}.cachely.io/~api${url.pathname}${url.search}`
},
rewriteAssetUrl(url, tenant) {
return `https://${tenant}.cachely.io${url.pathname}${url.search}`
},
}
defaultRegistry.register(sanity)
Requirements
- Node.js 18+ (or any runtime with the Fetch API)
- No dependencies