contentstack
nuxt-contentstack

Contentstack integration for Nuxt

Nuxt Contentstack

Nuxt Contentstack

npm versionnpm downloadsLicenseNuxt

Contentstack integration for Nuxt 4 (Nuxt 3.20.1+ also supported).

Notice: This is an OSS project by @timbenniks and not an officially maintained package by the Contentstack team. Support requests can come through Github issues and via direct channels to @timbenniks.

Requirements

  • Nuxt 4 (recommended) or Nuxt 3 >=3.20.1
  • @nuxt/image ^2.0.0 (optional, for image provider functionality)

Features

  • Complete set of Vue composables (entries, assets, by URL)
  • Typed useContentstack() helper for accessing SDKs and config
  • Advanced filtering, pagination, and sorting
  • Live Preview & Visual Builder support
  • Personalization support with server-side middleware
  • Image transformations with useImageTransform composable
  • @nuxt/image integration with automatic optimization
  • TypeScript support with full type safety
  • Exposed SDKs: Delivery SDK, Live Preview Utils SDK, Personalize SDK

Quick Setup

npx nuxi module add nuxt-contentstack
// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    "nuxt-contentstack",
    "@nuxt/image", // optional, for image provider support
  ],

  "nuxt-contentstack": {
    // Required
    apiKey: "your_contentstack_api_key",
    deliveryToken: "your_delivery_token",
    environment: "your_environment",

    // Optional
    region: "eu", // 'us' | 'eu' | 'au' | 'azure-na' | 'azure-eu' | 'gcp-na' | 'gcp-eu'
    branch: "main",
    locale: "en-us",

    // Live Preview
    livePreview: {
      enable: true,
      previewToken: "your_preview_token",
      editableTags: true,
      editButton: true, // or object with enable, position, exclude, includeByQueryParameter
      mode: "builder", // 'builder' | 'preview'
      ssr: false,
    },

    // Personalization
    personalization: {
      enable: true,
      projectUid: "your_project_uid",
    },

    debug: true,
  },
});

Configuration

Core Settings

OptionTypeRequiredDefaultDescription
apiKeystringYes-Contentstack stack API key (starts with "blt")
deliveryTokenstringYes-Delivery token (starts with "cs")
environmentstringYes-Target environment ('preview' | 'production')
regionstringNo'us'Contentstack region
branchstringNo'main'Content branch
localestringNo'en-us'Default locale
hoststringNo-Override the Delivery SDK host URL
extraTranspilestring[]No[]Extra packages to transpile for CJS/ESM compat
debugbooleanNofalseEnable debug logging

Live Preview

OptionTypeDefaultDescription
enablebooleanfalseEnable live preview
previewTokenstring-Preview token (required if enabled)
editableTagsbooleanfalseAdd editable tags for visual building
editButtonboolean | objectfalseEnable/edit button config
mode'builder' | 'preview''builder'Live preview mode
ssrbooleanfalseEnable SSR mode (experimental)
hoststring-Override the Live Preview host URL

Edit button object:

editButton: {
  enable: boolean
  position?: 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'top-center' | 'bottom-left' | 'bottom-right' | 'bottom-center'
  exclude?: ('insideLivePreviewPortal' | 'outsideLivePreviewPortal')[]
  includeByQueryParameter?: boolean
}

Personalization

OptionTypeRequiredDescription
enablebooleanYesEnable personalization
projectUidstringYesPersonalization project UID

Provides

useContentstack()

Typed composable for accessing Contentstack SDKs and configuration. This is the recommended way to access the plugin provides.

const {
  stack, // Delivery SDK Stack instance
  ContentstackLivePreview, // Live Preview Utils SDK
  personalizeSdk, // Personalize SDK instance (client-side only)
  livePreviewEnabled, // boolean
  editableTags, // boolean
  variantAlias, // Variant aliases for personalization
  VB_EmptyBlockParentClass, // Visual Builder empty block class
} = useContentstack();

You can also access the same values via useNuxtApp().$contentstack, but useContentstack() provides full TypeScript types out of the box.

Personalization SDK Usage

The module uses the instance-based Personalize SDK (v1.0.9+). Server-side personalization (variant resolution, cookie management) is handled automatically by the built-in server middleware. The client-side SDK instance is available for user interactions:

const { personalizeSdk } = useContentstack();

// Set user attributes
await personalizeSdk?.set({ age: 20 });

// Trigger impression for an experience
await personalizeSdk?.triggerImpression(experienceShortUid);

// Batch impressions
await personalizeSdk?.triggerImpressions({
  experienceShortUids: ["exp1", "exp2"],
});

// Trigger conversion event
await personalizeSdk?.triggerEvent("eventKey");

// Set explicit user ID
await personalizeSdk?.setUserId("user-123");

Note: personalizeSdk is null on the server. Use optional chaining (?.) or guard with import.meta.client when calling SDK methods.

Composables

All composables support live preview updates, personalization variants, and Nuxt's caching system.

Note: All data-fetching composables are async and must be awaited. This means they need to be called at the top level of <script setup> (which supports top-level await via <Suspense> automatically). They cannot be called conditionally inside setup().

useGetEntryByUrl

Fetch entry by URL field.

const { data, status, refresh } = await useGetEntryByUrl<Page>({
  contentTypeUid: "page",
  url: "/about",
  referenceFieldPath: ["author", "category"],
  jsonRtePath: ["rich_text_field"],
  locale: "en-us",
  replaceHtmlCslp: true,
});

useGetEntry

Fetch single entry by UID.

const { data } = await useGetEntry<Article>({
  contentTypeUid: "article",
  entryUid: "your_entry_uid",
  referenceFieldPath: ["author"],
  jsonRtePath: ["content"],
  locale: "en-us",
});

useGetEntries

Fetch multiple entries with filtering, pagination, and sorting.

const { data } = await useGetEntries<Article>({
  contentTypeUid: "article",
  referenceFieldPath: ["author"],
  locale: "en-us",
  limit: 10,
  skip: 0,
  orderBy: "created_at",
  includeCount: true,
  where: {
    status: "published",
    view_count: { $gt: 1000 },
    created_at: { $gte: "2024-01-01", $lt: "2024-12-31" },
    featured_image: { $exists: true },
    title: { $regex: "nuxt.*contentstack" },
    tags: ["tech", "news"],
    author: { $ne: "guest" },
  },
});

// Access results
console.log(data.value?.entries); // Article[]
console.log(data.value?.count); // number (if includeCount: true)

useGetAsset

Fetch single asset by UID.

const { data } = await useGetAsset<Asset>({
  assetUid: "your_asset_uid",
  locale: "en-us",
});

useGetAssets

Fetch multiple assets with filtering.

const { data } = await useGetAssets<Asset>({
  locale: "en-us",
  limit: 20,
  orderBy: "created_at",
  includeCount: true,
  where: {
    content_type: "image/jpeg",
    // Note: Most asset filters are applied client-side
  },
});

Query Operators

Supported in where clause for entry composables:

  • Exact match: field: "value"
  • Array contains: tags: ["tech", "news"]
  • Comparison: field: { $gt: 100, $gte: 50, $lt: 200, $lte: 150, $ne: "value" }
  • Existence: field: { $exists: true }
  • Regex: field: { $regex: "pattern" }

useImageTransform

Transform Contentstack images programmatically.

const { transformedUrl, updateTransform, resetTransform } = useImageTransform(
  imageUrl,
  {
    width: 800,
    height: 600,
    quality: 80,
    format: "webp",
    fit: "crop",
    blur: 5,
    saturation: 10,
    brightness: 5,
    overlay: {
      relativeURL: "/watermark.png",
      align: "bottom-right",
      width: "20p",
    },
    sharpen: {
      amount: 5,
      radius: 2,
      threshold: 0,
    },
  }
);

// Update reactively
updateTransform({ width: 1200, quality: 90 });

Components

ContentstackModularBlocks

Renders Contentstack modular blocks as Vue components with optional auto-fetch capability. Auto-fetch is disabled by default. To enable auto-fetch, provide both contentTypeUid and url props. When using pre-fetched blocks, pass blocks via the blocks prop without providing contentTypeUid or url.

Pattern 1: Auto-fetch Entry

<script setup>
const componentMapping = {
  hero: Hero,
  grid: Grid,
  text_block: TextBlock,
};
</script>

<template>
  <ContentstackModularBlocks
    content-type-uid="page"
    :url="$route.path"
    blocks-field-path="components"
    :reference-field-path="['author']"
    :json-rte-path="['rich_text']"
    :auto-seo-meta="true"
    :component-map="componentMapping"
  >
    <template #loading>Loading...</template>
    <template #error>Failed to load</template>
    <template #empty>No content</template>
  </ContentstackModularBlocks>
</template>

Pattern 2: Pre-fetched Blocks

<script setup>
const { data: page } = await useGetEntryByUrl({
  contentTypeUid: "page",
  url: useRoute().path,
});

const componentMapping = {
  hero: Hero,
  grid: Grid,
};
</script>

<template>
  <ContentstackModularBlocks
    :blocks="page.components"
    :component-map="componentMapping"
  />
</template>

Props

PropTypeDefaultDescription
blocksContentstackBlock[][]Array of modular blocks
componentMapComponentMapping{}Block type → Vue component mapping
fallbackComponentComponentContentstackFallbackBlockComponent for unmapped blocks
contentTypeUidstringundefinedContent type for auto-fetch (required if using auto-fetch)
urlstringundefinedURL for auto-fetch (required if using auto-fetch)
blocksFieldPathstring'components'Field path to extract blocks
referenceFieldPathstring[][]Reference fields to include
jsonRtePathstring[][]JSON RTE field paths
localestring'en-us'Locale
replaceHtmlCslpbooleaneditableTagsReplace HTML CSLP tags
seoMetaSeoMetaInput-SEO metadata (passed to useSeoMeta)
autoSeoMetaboolean | Record<string, string>falseAuto-generate SEO from entry
containerClassstring'contentstack-modular-blocks'Container CSS class
emptyBlockClassstring'visual-builder__empty-block-parent'Empty block CSS class
showEmptyStatebooleantrueShow empty state
keyFieldstring'_metadata.uid'Key field for blocks
autoExtractBlockNamebooleantrueAuto-extract block name
blockNamePrefixstring''Prefix to remove from block names when mapping components
containerPropsRecord<string, any>{}Additional props to bind to the container element
emptyStateClassstring'contentstack-empty-state'CSS class for empty state container
emptyStateMessagestring'No content blocks available'Message shown in empty state

SEO Metadata

Manual SEO:

<ContentstackModularBlocks
  :seo-meta="{
    title: 'Page Title',
    description: 'Page description',
    ogImage: 'https://example.com/image.jpg',
  }"
/>

Auto-generate SEO:

<!-- Default field mapping -->
<ContentstackModularBlocks :auto-seo-meta="true" />

<!-- Custom field mapping -->
<ContentstackModularBlocks
  :auto-seo-meta="{
    title: 'seo_title|title|name',
    description: 'meta_description|description',
    ogImage: 'featured_image.url',
    robots: 'noindex',
  }"
/>

Default auto-SEO mapping:

  • title: seo_titletitlename
  • description: seo_descriptiondescriptionsummary
  • ogImage: featured_image.urlog_image.urlimage.url

Field mapping syntax:

  • Pipe-separated fallbacks: 'seo_title|title|name' — tries each field in order, uses the first with a value
  • Dot notation: 'featured_image.url' — resolves nested fields
  • Single field: 'title' — looks up the field on the entry
  • Static value: If a single value doesn't match any entry field, it's used as a literal (e.g., robots: 'noindex')

Exposed Methods

The component exposes a refreshEntry() method via template refs that can be used to programmatically refresh the fetched entry data:

<script setup>
const blocksRef = ref();

function handleRefresh() {
  blocksRef.value?.refreshEntry();
}
</script>

<template>
  <ContentstackModularBlocks
    ref="blocksRef"
    content-type-uid="page"
    :url="$route.path"
    :component-map="componentMapping"
  />
  <button @click="handleRefresh">Refresh</button>
</template>

Slots

  • loading - Custom loading state (auto-fetch mode)
  • error - Custom error state (auto-fetch mode)
  • empty - Custom empty state

ContentstackFallbackBlock

Built-in fallback component for unmapped block types. Displays block title, type badge, and props as formatted JSON.

@nuxt/image Integration

The Contentstack image provider is automatically registered when @nuxt/image is installed. No manual provider configuration needed.

Setup

npm install @nuxt/image@^2.0.0
// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    "nuxt-contentstack", // must be listed before @nuxt/image
    "@nuxt/image",
  ],
});

Important: nuxt-contentstack must be listed before @nuxt/image in the modules array so the provider is registered before @nuxt/image processes its configuration.

To set Contentstack as the default image provider (so you don't need provider="contentstack" on every <NuxtImg>):

// nuxt.config.ts
export default defineNuxtConfig({
  image: {
    provider: "contentstack",
  },
});

Usage

<template>
  <!-- Basic usage -->
  <NuxtImg
    :src="image.url"
    :alt="image.title"
    width="800"
    height="400"
    :modifiers="{ auto: 'webp,compress', quality: 90 }"
    provider="contentstack"
  />

  <!-- Responsive -->
  <NuxtImg
    :src="image.url"
    sizes="100vw sm:50vw lg:33vw"
    :modifiers="{ auto: 'webp,compress' }"
    provider="contentstack"
  />

  <!-- Art direction -->
  <NuxtPicture
    :src="image.url"
    sizes="100vw md:50vw"
    :modifiers="{ auto: 'webp,compress' }"
    provider="contentstack"
  />
</template>

Supported Modifiers

  • auto: 'webp' | 'webp,compress'
  • quality: number
  • format: 'webp' | 'png' | 'jpg' | 'jpeg' | 'gif' | 'auto'
  • width, height, dpr: number
  • fit: 'bounds' | 'crop'
  • blur, brightness, contrast, saturation: number
  • sharpen: { amount, radius, threshold }
  • overlay: { relativeURL, align, repeat, width, height, pad }

Development

# Install dependencies
npm install

# Generate type stubs
npm run dev:prepare

# Develop with playground
npm run dev

# Build playground
npm run dev:build

# Lint
npm run lint

# Test
npm run test
npm run test:watch

# Release
npm run release