@optimarvin/opti-cms-client
v0.1.3
Published
A robust, framework-agnostic TypeScript client for Optimizely CMS
Maintainers
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-clientor
yarn add opti-cms-clientFeatures
- 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
- Navigate to Settings > API Keys in your CMS
- Click Create API Key
- Provide a name (letters, numbers, hyphens, underscores only)
- 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 secondsUser 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 userYou 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 tohttps://api.cms.optimizely.com/{version}/tokenEndpoint(string, optional): OAuth token endpoint. Defaults tohttps://api.cms.optimizely.com/oauth/tokenaccessToken(string, optional): Direct bearer token for authenticationcredentials(OAuthCredentials, optional): OAuth client credentials for authenticationclientId(string): OAuth client IDclientSecret(string): OAuth client secretactAs(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(): stringReturns the configured base URL.
getVersion
getVersion(): ApiVersionReturns the configured API version.
getTokenEndpoint
getTokenEndpoint(): stringReturns the configured OAuth token endpoint.
hasAccessToken
hasAccessToken(): booleanReturns whether an access token is available.
hasCredentials
hasCredentials(): booleanReturns whether OAuth credentials are configured.
getTokenExpiresAt
getTokenExpiresAt(): number | undefinedReturns the timestamp when the current token expires.
setAccessToken
setAccessToken(token: string, expiresIn?: number): voidManually set an access token and its expiration time.
Types
The library exports the following TypeScript types:
ClientConfig- Client configuration optionsRequestOptions- Request-specific options including headers, params, and etagContentItem- Content item structureApiResponse<T>- API response wrapper with data, status, and etagErrorResponse- RFC 7807 compliant error responsePaginatedResponse<T>- Paginated list response with items and pagination metadataApiVersion- API version type ('preview2'|'preview3')ValidationError- Validation error detailsOAuthCredentials- OAuth client credentials with optional impersonationTokenResponse- 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 lintVersioning
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:patchLicense
MIT
Author
Marvin Oey [email protected]
Repository
https://github.com/marvoey/opti-cms-client
