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

@lemonadecms/client

v0.2.0

Published

Framework-independent content delivery and developer tooling for Lemonade CMS

Readme

@lemonadecms/client

Framework-independent JavaScript and TypeScript SDK for reading Lemonade CMS content. Published-content requests use REST. The package includes TypeScript declarations, schema type generation and the lemonade developer CLI.

Install

Install the published package in your application:

npm install @lemonadecms/client
# or
pnpm add @lemonadecms/client

Maintainers can test a future version using its reviewed candidate archive. Check the supplied SHA256SUMS, then use the actual archive path:

npm install --ignore-scripts /absolute/path/to/reviewed-client.tgz
# or
pnpm add /absolute/path/to/reviewed-client.tgz

The package uses ESM and declares Node.js 22+. TypeScript consumers need TypeScript 5+. Browser reads require standard fetch, URL, URLSearchParams and AbortController; browser builds must use public credentials only. No runtime library or framework dependency is required.

Configure and read content

Version 0.2.0 connects to https://api.lemonadecms.com automatically. Strict hostname routing returns 404 for API URLs on https://lemonadecms.com. Upgrade earlier clients to 0.2.0 and change explicit CLI/configured-target URLs to https://api.lemonadecms.com. Explicit CLI settings still take precedence; no redirect or fallback is attempted. Configure your workspace and delivery key; there is no URL or local CMS option. Your website can run locally while reading content from Lemonade production. Installing the package does not create an account, workspace or delivery key.

Replace the workspace and key placeholders with values from your Lemonade workspace. Replace content_item and title with your content type and field API identifiers, and publish an entry before fetching it.

import { createCMSClient } from '@lemonadecms/client';

const cms = createCMSClient({
  projectId: 'YOUR_WORKSPACE_UUID',
  publicKey: 'YOUR_PUBLIC_DELIVERY_KEY',
});

const page = await cms.content('content_item').list({
  select: ['title'],
  limit: 10,
});

for (const entry of page.data) {
  console.log(entry.id, entry.fields.title);
}

const first = page.data[0];
if (first) {
  const entry = await cms.content('content_item').get(first.id);
  console.log(entry.fields, entry.publishedAt);
}

Entry values are under fields; metadata includes id and publishedAt. The client also exposes contentType (ID/key/name), projectId, environment (ID/key) and resolved locale. Field projections retain metadata. For older CMS responses, the SDK derives only the request's model key/workspace and explicit locale; unavailable metadata stays absent. Metadata properties are optional in TypeScript for compatibility with existing manually constructed entries. Use entry.contentType?.key, not a custom fields.contentType field. Generated Models describes fields; ContentEntry<Models['content_item'], 'content_item'> from the SDK describes the complete entry. Expanded reference metadata remains raw snake_case. Published 0.1.0 does not include these additions. An empty collection is data: []. Historical or selected fields can be missing or null, so handle both when rendering. Continue a collection using page.meta.nextCursor as the next request's after value while keeping the other query options unchanged.

Use an existing pk_ public delivery key or an lmd_pk_ application token with the required published-content grants. Anyone with that credential can read the content it permits. Browser login cookies do not authorize SDK requests. The credential selects the content environment; there is no separate SDK environment override. Ask your Owner for a credential belonging to the intended environment. The server rechecks current access, expiry and revocation on every request.

Find one entry

In the updated source build, findOne() returns the first matching complete entry or null. It makes one request with limit=1 and preserves existing filters, search, sort, select, locale, includeRemote and signal options. It does not check uniqueness. Pagination/limit options are rejected; HTTP/authorization, transport, cancellation/deadline and malformed-response errors still reject. get(id) keeps its existing UUID lookup and 404 behavior. Published 0.1.0 lacks these helpers.

const entry = await cms.content('content_item').findOne({
  filters: [{ field: 'title', op: 'eq', value: 'Welcome' }],
  select: ['title'],
});
console.log(entry?.fields.title ?? 'No matching content');

Counts, existence and bulk reads

These methods require the current SDK and CMS backend. They are available in both published and server preview clients:

import { CMSNotFoundError } from '@lemonadecms/client';

const content = cms.content('content_item');
const filters = [{ field: 'title', op: 'eq', value: 'Welcome' }];
const total = await content.count({ filters });
const available = await content.exists({ filters });
try {
  const required = await content.findOneOrThrow({ filters, select: ['title'] });
  const entries = await content.getMany([required.id], { select: ['title'] });
  console.log(total, available, entries[0]?.fields.title);
} catch (error) {
  if (!(error instanceof CMSNotFoundError)) throw error;
  console.log('Required content is unavailable');
}

count()/exists() accept filters, search, locale and signal, using direct database queries without downloading entries or expanding references. Count includes all matches across pages; separate requests can see different publication states. getMany() accepts up to 100 UUIDs and select/locale/includeRemote/signal. One request returns entries in input order, preserving duplicate positions and using null for missing/unpublished/foreign IDs in an otherwise authorized model. Empty input returns [] without I/O after validation. Split larger ID sets into batches; use the existing lazy entries()/pages() methods for collections. findOneOrThrow() shares findOne options and throws CMSNotFoundError only for a successful empty match. Authorization, HTTP, malformed-response, network, cancellation and deadline failures retain their existing errors. Preview fields may still be missing/null. No automatic collection-download fallback occurs.

Advanced filters, search and sorting

Updated source builds support nested and/or/not, ne/not_in, and text eqi, contains/containsi, starts_with/starts_withi, ends_with/ends_withi. Existing filter arrays remain AND. Limits are ten leaves, 31 nodes, depth four and 4 KiB; groups are nonempty. SQL wildcard characters are treated literally. Positive comparisons on missing values are false; NOT and ne/not_in complement them, including missing values. Use eq/ne null for explicit null checks.

const matches = await cms.content('content_item').search('welcome', {
  fields: ['title'],
  select: ['title'],
  filters: {
    or: [
      { field: 'title', op: 'containsi', value: 'WELCOME' },
      { not: { field: 'title', op: 'eq', value: 'Archived' } },
    ],
  },
  sort: [
    { field: 'position', direction: 'desc' },
    { field: 'title', direction: 'asc' },
  ],
  limit: 10,
});
console.log(matches.data);

fields scopes matching to 1–24 text/long_text/markdown fields; select projects the returned fields. Omit fields to search all top-level fields of those types. Query accepts 1–200 Unicode codepoints, up to 512 UTF-8 bytes and 16 whitespace terms. PostgreSQL simple-config plain search requires every parsed term, with cover-density relevance order by default. Explicit 1–3 unique sort fields override relevance; NULLs stay last and UUID breaks ties. No stemming, fuzzy/prefix/phrase search or recursive rich-text/reference search is implied. Matching and ranking use only the authorized locale/fallback and published/preview snapshot.

search() makes one normal paginated collection request. Pass the same search: {query, fields} object to list/findOne/findOneOrThrow/count/exists/pages/ entries to count or traverse matches. Keep the full query and ordered sort fields unchanged when following cursors. Existing single-sort syntax remains supported. Signed cursors have an 8 KiB limit. Unusually large Unicode text anchors that exceed it after compression return invalid_query; choose more compact scalar sort fields in that case. These methods require the updated SDK/backend. Runtime/peer/optional dependencies remain empty; no external search service is used.

Rich-text helpers

import { richTextToHtml, richTextToPlainText } from '@lemonadecms/client';

// Replace rich_body with a rich_text field from your schema.
const richEntry = await cms.content('content_item').findOne({ select: ['rich_body'] });
const html = richTextToHtml(richEntry?.fields.rich_body);
const plainText = richTextToPlainText(richEntry?.fields.rich_body);

Both accept nullable/unknown version-1 rich-text JSON and work in browsers or servers without a framework or DOM. HTML escapes text/attributes and renders only supported nodes/marks, safe links and matching expanded image/entry-caption resources. Unavailable embeds are omitted; entry routes are never invented. Plain text retains authored text with block/list/hard-break newlines, omitting embeds and link URLs. Plain text is not escaped HTML: use text interpolation.

Traversal is bounded to 500 nodes, depth 8, 200 children per node, 20 embeds, 20,000 UTF-8 bytes per text and 64 KiB of rendered source strings. Unsupported versions, invalid roots/containers or exhausted budgets return empty strings; unsupported parts are omitted. No raw HTML, resource-field recursion, I/O or automatic Markdown conversion is performed. HTML URL/alt bounds and exact preview-image conventions are documented in the SDK guide. The helpers are also exported from the server entry; preview authorization remains your responsibility.

Types, queries and framework examples

The SDK guide covers generated Models, field selection, filtering, sorting, localization, cursor and numbered pagination, errors, cancellation, images, and JavaScript/React/Vue/Nuxt examples. Generate types from your own authorized schema and regenerate them after model changes. Types help at compile time; they do not authenticate a request or validate every field at runtime.

There is no automatic caching or retry policy. Your application owns rendering, loading and error states, cache invalidation and safe HTML handling. Pass an AbortSignal to cancel a read. Optional timeoutMs sets a per-request deadline; combining a timeout with a caller signal requires AbortSignal.any.

Choose an environment for generated types

The updated source CLI offers an interactive picker from configured targets:

npx lemonade types generate

Create lemonade.config.json in your application. Replace both workspace placeholders with your workspace UUID; the target labels distinguish environments.

{
  "version": 1,
  "targets": {
    "production": {
      "url": "https://api.lemonadecms.com",
      "workspace": "YOUR_WORKSPACE_UUID",
      "environment": "production",
      "credentialEnv": "CMS_PUBLIC_KEY"
    },
    "dev": {
      "url": "https://api.lemonadecms.com",
      "workspace": "YOUR_WORKSPACE_UUID",
      "environment": "dev",
      "credentialEnv": "CMS_DEV_PUBLIC_KEY"
    }
  }
}

Keep actual credentials in your current directory's .env or process environment; process values win. A dev target needs a dev-scoped credential. The credential selects access; the CLI verifies the returned workspace/environment before writing. The picker lists configured targets, not environments discovered from the CMS.

Use arrows and Enter. Production is initially selected when configured; otherwise the first target alphabetically. Escape/Ctrl+C/EOF exits 130 without network or output-file writes. Terminal input mode is restored on success, cancellation/error. Missing/revoked/mismatched credentials fail without replacing existing types.

Explicit --target, --environment, --url, --workspace or --credential-env bypasses the picker, as do LEMONADE_TARGET, LEMONADE_ENVIRONMENT or CMS_ENVIRONMENT. CI, --json, --check, piped input/output, --file and other commands never prompt. --config can choose the target configuration file. Without configured targets, existing defaults remain unchanged. For automation:

npx lemonade types generate --target dev --check

Version 0.2.0 includes the picker; historical 0.1.0 does not. It uses native Node APIs and retains zero runtime, peer or optional dependencies.

Private previews

For draft reads, use createCMSPreviewClient from @lemonadecms/client/server in trusted server code with a server-only secretKey. Authenticate and authorize the requesting user in your application before fetching or returning drafts. Do not create an anonymous preview proxy. Never put preview credentials or private responses in browser bundles, public environment variables, shared caches, static output or logs. The server export rejects browser use; that guard does not replace your application's authorization.

@lemonadecms/client/preview is a separate optional click-to-edit helper for an already authorized website preview. It provides no preview credential or draft fetch client.

Guides

License

This SDK package is licensed under the MIT License. Its license does not grant a license to the entire CMS repository or the separate native clients.

Visual Composer

The optional Visual Composer adds approved nested sections, inspector editing and private live updates. A developer connects the website registry and explicitly enables unsaved projection on its trusted preview destination. Save draft and Publish remain separate. Component text can be translated by stable block ID; each language keeps its live tree until explicitly published. Existing form and saved-draft previews remain supported. The browser helper is the dependency-free @lemonadecms/client/composer export.

See setup, editor workflow, Astro reference and protocol.