Skip to content

Examples

These patterns show how to use @lightspeed/ecom-headless in Vue composables within a Crane theme.

All examples below assume you have a shared init composable in your project at shared/composables/useInitStorefrontApi.ts:

typescript
// shared/composables/useInitStorefrontApi.ts
import { useInstantsiteJsApi } from '@lightspeed/crane'
import { initStorefrontApi } from '@lightspeed/ecom-headless'

const ECWID_CONFIG = {
  clientId: 'your-app-client-id',
  baseURL: undefined, // only set for non-production environments
}

export async function useInitStorefrontApi() {
  const publicToken = useInstantsiteJsApi()?.getAppPublicToken(ECWID_CONFIG.clientId) ?? ''
  const storeId = useInstantsiteJsApi()?.getSiteId()

  if (!publicToken) {
    throw new Error('Missing public token — ensure the app is installed and has a valid client ID')
  }

  await initStorefrontApi({
    publicToken,
    storeId,
    ...(ECWID_CONFIG.baseURL ? { baseURL: ECWID_CONFIG.baseURL } : {}),
  })

  return { publicToken, storeId }
}

This calls initStorefrontApi once with credentials from the Crane runtime. All composables below call it before making API requests. See Overview & Setup for what each config field does.

Fetching Products

A composable that fetches products on mount and provides reactive data:

typescript
import { searchProducts, type Product } from '@lightspeed/ecom-headless'
import { ref, computed, onMounted } from 'vue'
import { useInitStorefrontApi } from '../shared/composables/useInitStorefrontApi'

export function useProducts(
  filter: (product: Product) => boolean = () => true,
  limit?: number,
) {
  const productsList = ref<Product[]>([])
  const isLoading = ref(true)
  const hasError = ref(false)
  const errorMessage = ref<string | null>(null)

  const products = computed(() => {
    const filtered = productsList.value.filter(filter)
    const limited = limit ? filtered.slice(0, limit) : filtered
    return limited.map((product) => ({
      id: product.id,
      name: product.name,
      price: product.price,
      formattedPrice: product.defaultDisplayedPriceFormatted,
      imageUrl: product.originalImage?.url,
      url: product.url,
      inStock: product.inStock,
    }))
  })

  const fetchProducts = async (lang?: string) => {
    isLoading.value = true
    hasError.value = false
    errorMessage.value = null

    try {
      await useInitStorefrontApi()
      const response = await searchProducts({
        ...(lang ? { lang } : {}),
        enabled: true,
      })
      productsList.value = (response.items || []) as Product[]
    } catch (error) {
      hasError.value = true
      errorMessage.value = error instanceof Error ? error.message : 'Unknown error'
    } finally {
      isLoading.value = false
    }
  }

  onMounted(() => fetchProducts())

  return { products, isLoading, hasError, errorMessage, fetchProducts }
}

Key patterns:

  • Always call useInitStorefrontApi() before API calls
  • Use response.items || [] defensively
  • Pass enabled: true to only get published products
  • Pass lang for localized content
  • Map raw API data to a view-friendly shape in a computed

Fetching Categories

Same pattern as products, adapted for categories:

typescript
import { searchCategories, type Category } from '@lightspeed/ecom-headless'
import { ref, computed, onMounted } from 'vue'
import { useInitStorefrontApi } from '../shared/composables/useInitStorefrontApi'

export function useCategories(limit?: number, rootOnly = true) {
  const categoriesList = ref<Category[]>([])
  const isLoading = ref(true)
  const hasError = ref(false)
  const errorMessage = ref<string | null>(null)

  const categories = computed(() => {
    let filtered = categoriesList.value.filter((cat) => cat.enabled)

    if (rootOnly) {
      filtered = filtered.filter((cat) => !cat.parentId)
    }

    const limited = limit ? filtered.slice(0, limit) : filtered
    return limited.map((cat) => ({
      id: cat.id,
      name: cat.name,
      url: cat.url,
      imageUrl: cat.originalImageUrl || cat.imageUrl,
      productCount: cat.productCount,
    }))
  })

  const fetchCategories = async (lang?: string) => {
    isLoading.value = true
    hasError.value = false
    errorMessage.value = null

    try {
      await useInitStorefrontApi()
      const response = await searchCategories({ lang })
      categoriesList.value = (response.items || []) as Category[]
    } catch (error) {
      hasError.value = true
      errorMessage.value = error instanceof Error ? error.message : 'Unknown error'
    } finally {
      isLoading.value = false
    }
  }

  onMounted(() => fetchCategories())

  return { categories, isLoading, hasError, errorMessage, fetchCategories }
}