npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@optimarvin/opti-cms-client

v0.1.3

Published

A robust, framework-agnostic TypeScript client for Optimizely CMS

Readme

opti-cms-client

A robust, framework-agnostic TypeScript client for Optimizely CMS. Works seamlessly with any JavaScript framework including Astro, Next.js, Vue, React, or vanilla JavaScript.

Installation

npm install opti-cms-client

or

yarn add opti-cms-client

Features

  • Framework-agnostic - works with any JavaScript environment
  • Full TypeScript support with comprehensive type definitions
  • Modern ESM and CommonJS support
  • Zero dependencies for core functionality
  • Built-in timeout and error handling
  • OAuth 2.0 client credentials authentication with automatic token refresh
  • Support for user impersonation (act_as)
  • Bearer token authentication support
  • Support for Optimizely CMS API preview3 version
  • ETag support for conditional updates
  • RFC 7807 compliant error handling
  • Pagination support for list endpoints
  • Full CRUD operations (Create, Read, Update, Delete)

Usage

Basic Usage

import { OptiCmsClient } from 'opti-cms-client';

// Option 1: OAuth 2.0 with Client Credentials (recommended)
const client = new OptiCmsClient({
  credentials: {
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
  },
  version: 'preview3', // optional, defaults to 'preview3'
});

// Authentication happens automatically on first request
// Tokens are automatically refreshed before expiration
const response = await client.getContent('content-id');

// Or manually authenticate
await client.authenticate();

// Option 2: Direct Bearer Token (if you manage tokens externally)
const tokenClient = new OptiCmsClient({
  accessToken: 'your-bearer-token',
  autoRefreshToken: false, // disable auto-refresh for external tokens
});

// Option 3: Custom base URL
const customClient = new OptiCmsClient({
  baseUrl: 'https://your-custom-api.com',
  credentials: {
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
  },
  timeout: 30000, // optional, defaults to 30000ms
});

// Get a single content item (returns ApiResponse with data and etag)
const contentResponse = await client.getContent('content-id');
console.log(contentResponse.data); // ContentItem
console.log(contentResponse.etag); // ETag for conditional updates

// List content items with pagination
const paginatedContent = await client.listContent({
  pageIndex: 0,
  pageSize: 50,
});
console.log(paginatedContent.items); // ContentItem[]
console.log(paginatedContent.totalItemCount); // Total count

// Create new content
const newContent = await client.createContent({
  contentType: 'PageType',
  name: 'New Page',
  // ... other properties
});

// Update content with ETag for safe updates
const updated = await client.updateContent(
  'content-id',
  { name: 'Updated Name' },
  { etag: contentResponse.etag }
);

// Delete content
await client.deleteContent('content-id');

With Astro

---
import { OptiCmsClient } from 'opti-cms-client';

const client = new OptiCmsClient({
  credentials: {
    clientId: import.meta.env.OPTI_CMS_CLIENT_ID,
    clientSecret: import.meta.env.OPTI_CMS_CLIENT_SECRET,
  },
});

const response = await client.getContent('page-id');
---

<div>
  <h1>{response.data.name}</h1>
</div>

With Next.js

import { OptiCmsClient } from 'opti-cms-client';

export async function getStaticProps() {
  const client = new OptiCmsClient({
    credentials: {
      clientId: process.env.OPTI_CMS_CLIENT_ID!,
      clientSecret: process.env.OPTI_CMS_CLIENT_SECRET!,
    },
  });

  const response = await client.getContent('page-id');

  return {
    props: { content: response.data },
  };
}

With Vue

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { OptiCmsClient } from 'opti-cms-client';

const content = ref(null);
const client = new OptiCmsClient({
  credentials: {
    clientId: import.meta.env.VITE_OPTI_CMS_CLIENT_ID,
    clientSecret: import.meta.env.VITE_OPTI_CMS_CLIENT_SECRET,
  },
});

onMounted(async () => {
  const response = await client.getContent('page-id');
  content.value = response.data;
});
</script>

With Vanilla JavaScript

import { OptiCmsClient } from 'opti-cms-client';

const client = new OptiCmsClient({
  credentials: {
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
  },
});

client.getContent('page-id').then((response) => {
  console.log(response.data);
});

Authentication

The client supports two authentication methods:

OAuth 2.0 Client Credentials (Recommended)

The OAuth 2.0 flow is the recommended authentication method. Tokens are automatically managed and refreshed:

const client = new OptiCmsClient({
  credentials: {
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
  },
});

// Token is obtained automatically on first API call
// Token is automatically refreshed 30 seconds before expiration (tokens last 5 minutes)

Creating API Keys

  1. Navigate to Settings > API Keys in your CMS
  2. Click Create API Key
  3. Provide a name (letters, numbers, hyphens, underscores only)
  4. Save the generated Client ID and Secret securely

Bearer Token

If you manage tokens externally, you can provide a bearer token directly:

const client = new OptiCmsClient({
  accessToken: 'your-bearer-token',
  autoRefreshToken: false, // disable auto-refresh
});

// Update token manually when needed
client.setAccessToken('new-token', 300); // token expires in 300 seconds

User Impersonation

When your API key has impersonation enabled, you can perform operations on behalf of a user:

const client = new OptiCmsClient({
  credentials: {
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
    actAs: '[email protected]', // impersonate this user
  },
});

// All operations will be performed with the permissions of the impersonated user

You can also authenticate with different users:

const client = new OptiCmsClient({
  credentials: {
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
  },
});

// Authenticate as a specific user
await client.authenticate({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  actAs: '[email protected]',
});

Token Management

The client automatically manages tokens with these features:

  • Tokens are refreshed 30 seconds before expiration (default 5 minutes)
  • Automatic retry on authentication failure
  • Thread-safe token refresh
  • Manual token management available
// Check token status
console.log(client.hasAccessToken()); // true/false
console.log(client.getTokenExpiresAt()); // timestamp or undefined

// Manually refresh token
await client.authenticate();

// Set external token
client.setAccessToken('external-token', 300);

API Reference

OptiCmsClient

Constructor

new OptiCmsClient(config: ClientConfig)

ClientConfig:

  • baseUrl (string, optional): Base URL of your CMS API. Defaults to https://api.cms.optimizely.com/{version}/
  • tokenEndpoint (string, optional): OAuth token endpoint. Defaults to https://api.cms.optimizely.com/oauth/token
  • accessToken (string, optional): Direct bearer token for authentication
  • credentials (OAuthCredentials, optional): OAuth client credentials for authentication
    • clientId (string): OAuth client ID
    • clientSecret (string): OAuth client secret
    • actAs (string, optional): User email for impersonation
  • version (ApiVersion, optional): API version to use: 'preview2' or 'preview3' (default: 'preview3')
  • timeout (number, optional): Request timeout in milliseconds (default: 30000)
  • autoRefreshToken (boolean, optional): Automatically refresh tokens before expiration (default: true)
  • headers (Record<string, string>, optional): Additional headers to include in requests

Methods

getContent
async getContent(contentId: string, options?: RequestOptions): Promise<ApiResponse<ContentItem>>

Retrieves a single content item by ID. Returns an ApiResponse containing the data and ETag.

listContent
async listContent(options?: RequestOptions & {
  pageIndex?: number;
  pageSize?: number;
}): Promise<PaginatedResponse<ContentItem>>

Retrieves a paginated list of content items. Returns items, pageIndex, pageSize, and totalItemCount.

createContent
async createContent(content: Partial<ContentItem>, options?: RequestOptions): Promise<ApiResponse<ContentItem>>

Creates a new content item.

updateContent
async updateContent(
  contentId: string,
  updates: Partial<ContentItem>,
  options?: RequestOptions
): Promise<ApiResponse<ContentItem>>

Updates an existing content item using PATCH (preview3 only). Supports conditional updates via ETag.

deleteContent
async deleteContent(contentId: string, options?: RequestOptions): Promise<ApiResponse<void>>

Deletes a content item by ID.

authenticate
async authenticate(credentials?: OAuthCredentials): Promise<TokenResponse>

Manually authenticate and obtain an access token using OAuth 2.0 client credentials flow.

getBaseUrl
getBaseUrl(): string

Returns the configured base URL.

getVersion
getVersion(): ApiVersion

Returns the configured API version.

getTokenEndpoint
getTokenEndpoint(): string

Returns the configured OAuth token endpoint.

hasAccessToken
hasAccessToken(): boolean

Returns whether an access token is available.

hasCredentials
hasCredentials(): boolean

Returns whether OAuth credentials are configured.

getTokenExpiresAt
getTokenExpiresAt(): number | undefined

Returns the timestamp when the current token expires.

setAccessToken
setAccessToken(token: string, expiresIn?: number): void

Manually set an access token and its expiration time.

Types

The library exports the following TypeScript types:

  • ClientConfig - Client configuration options
  • RequestOptions - Request-specific options including headers, params, and etag
  • ContentItem - Content item structure
  • ApiResponse<T> - API response wrapper with data, status, and etag
  • ErrorResponse - RFC 7807 compliant error response
  • PaginatedResponse<T> - Paginated list response with items and pagination metadata
  • ApiVersion - API version type ('preview2' | 'preview3')
  • ValidationError - Validation error details
  • OAuthCredentials - OAuth client credentials with optional impersonation
  • TokenResponse - OAuth token response structure

Error Handling

The client throws RFC 7807 compliant errors that you can catch and handle:

try {
  const response = await client.getContent('invalid-id');
} catch (error) {
  const err = error as ErrorResponse;
  if (err.status === 404) {
    console.error('Content not found:', err.title);
  } else if (err.status === 408) {
    console.error('Request timeout');
  } else if (err.status === 400 && err.errors) {
    console.error('Validation errors:', err.errors);
  } else {
    console.error('An error occurred:', err.title, err.details);
  }
}

Pagination

List endpoints support pagination using pageIndex and pageSize:

const page1 = await client.listContent({
  pageIndex: 0,
  pageSize: 100, // default is 100
});

console.log(page1.items); // Array of content items
console.log(page1.totalItemCount); // Total number of items
console.log(page1.pageIndex); // Current page index
console.log(page1.pageSize); // Page size

// Get next page
const page2 = await client.listContent({
  pageIndex: 1,
  pageSize: 100,
});

ETag Support

Use ETags for conditional updates to prevent concurrent modification conflicts:

// Get content with ETag
const response = await client.getContent('content-id');
console.log(response.etag); // ETag value

// Update with ETag to ensure no concurrent modifications
try {
  const updated = await client.updateContent(
    'content-id',
    { name: 'New Name' },
    { etag: response.etag }
  );
} catch (error) {
  if (error.status === 412) {
    console.error('Content was modified by another request');
  }
}

Development

# Install dependencies
yarn install

# Build the library
yarn build

# Watch mode for development
yarn dev

# Type check
yarn type-check

# Lint
yarn lint

Versioning

This project uses standard-version for versioning.

# Create a new release
yarn release

# Create a specific version
yarn release:major
yarn release:minor
yarn release:patch

License

MIT

Author

Marvin Oey [email protected]

Repository

https://github.com/marvoey/opti-cms-client