Using ElmapiCMS with Claude Code and Cursor: SDK, Fetch, and Prompts

Step-by-step guide to using ElmapiCMS in Claude Code or Cursor. Get credentials, add env vars, set up the ElmapiCMS SDK or plain fetch, and use prompts for blog lists and single posts.

R
Raşit Apalak
9 min read

With Claude Code or Cursor you edit real code, so you get full control over how you talk to a headless CMS. ElmapiCMS fits in via the ElmapiCMS JavaScript SDK or plain fetch: you add credentials to env vars, create a small CMS client (or call the API directly), and prompt Claude or Cursor to build pages that use it.

This guide walks you through getting ElmapiCMS credentials, understanding the correct API shape (from the official docs), where to put env vars, SDK vs fetch, and example prompts for blog lists and single posts.

ElmapiCMS – headless CMS for Claude Code and Cursor


Table of Contents


Prerequisites

  • Claude Code or Cursor and a project (Next.js, Remix, Astro, or similar).
  • A running ElmapiCMS instance. You can use the demo to try things, or your own install (installation).
  • A project in ElmapiCMS with at least one collection (e.g. “Posts”) and a few entries so you have real data. If you need to create a project and collection, see the getting started and collections docs.

Step 1: Get ElmapiCMS Credentials

ElmapiCMS uses project-scoped API access. Every request must include the project and, for private APIs, a token. You get these from the project’s API Access settings.

Where to find them

  1. Log in to your ElmapiCMS admin and open the project that has your content (e.g. your blog project).
  2. Go to SettingsAPI Access (see API Access in the docs).
  3. On that page you’ll see:
    • Content API Endpoint – The base URL for all API calls, usually https://your-domain.com/api. Copy this; you’ll use it as ELMAPI_URL (or similar) in env.
    • Project ID – A UUID that identifies this project. Every request must send it in the project-id header.
  4. If the project’s API is private (Public API is OFF), you need an access token:
    • Click Create Token.
    • Give it a name (e.g. “Cursor” or “Claude”) and tick the read ability.
    • Copy the token immediately; it won’t be shown again. Store it in your password manager or env.

What you’ll use in code

  • Base URL = Content API Endpoint (e.g. https://cms.example.com/api).
  • Headers = Accept: application/json, project-id (your project ID), and for private APIs: Authorization: Bearer followed by your token.

If your project has Public API enabled, you only need the project-id header for GET requests; you can omit the Authorization header. This guide assumes a private API and a token.


Step 2: How the ElmapiCMS Content API Works

The ElmapiCMS Content API is collection-based. The path is the collection slug (e.g. posts or blog-posts). Base URL + path + query = full URL.

Base URL and headers

  • Base URL: The Content API Endpoint you copied (e.g. https://your-domain.com/api).
  • Required headers:
    • Accept: application/json
    • project-id = your project ID
    • For private APIs: Authorization: Bearer + your token

Full details: Content API introduction.

List entries (e.g. blog posts)

Paginated: use paginate and page. The parameter name is paginate, not per_page.

GET /posts?paginate=10&page=1

Full URL example: https://your-domain.com/api/posts?paginate=10&page=1

Response: An object with data (array of entries), meta (current_page, last_page, total, etc.), and links (first, next, prev, last). See List Entries in the docs.

Without paginate: Omit paginate and you get a plain array of all entries.

Get one entry by slug

There is no dedicated “get by slug” path. You filter the list using the where parameter, then take the first (or only) item.

GET /posts?where[slug]=my-post-slug

Full URL example: https://your-domain.com/api/posts?where[slug]=my-post-slug

The response is either a single entry or a short array; use the first element. Filtering is described in Advanced Filtering.

Response shape

Each entry has:

  • uuid – Unique id.
  • fields – Object with your collection’s fields (e.g. title, slug, content, excerpt).
  • Optionally locale, published_at, etc.

So in code you’ll use entry.fields.title, entry.fields.slug, entry.fields.content, and so on.


Step 3: Where to Add Env Vars

Never hardcode the token or project ID. Put them in env and load them at build/runtime.

Next.js

  • Local: Create .env.local in the project root. Add ELMAPI_URL, ELMAPI_API_KEY, ELMAPI_PROJECT_ID. Add .env.local to .gitignore (Next.js does this by default).
  • Vercel / Netlify / etc.: Set the same variables in the hosting dashboard so they’re available in server and edge code. Do not expose the token to the client if you can avoid it (e.g. call the CMS from API routes or server components only).

Other frameworks (Remix, Astro, etc.)

  • Use the framework’s env convention (e.g. .env or .env.local) and ensure the file is ignored by git. Use import.meta.env or process.env as the framework expects, and keep the token out of client-side bundles.

When prompting Claude or Cursor

Tell it where the values live, e.g. “We use ElmapiCMS. Credentials are in env: ELMAPI_URL, ELMAPI_API_KEY, ELMAPI_PROJECT_ID. Use them only in server-side or API route code, not in client components.” That keeps the model from inventing wrong env names or putting secrets in the browser.


Step 4: SDK Setup

The ElmapiCMS JavaScript SDK wraps the Content API and handles headers for you.

Install

npm install @elmapicms/js-sdk

Env (example for Next.js)

In .env.local:

ELMAPI_URL=https://your-domain.com/api
ELMAPI_API_KEY=your-token-from-api-access
ELMAPI_PROJECT_ID=your-project-id-uuid

Create a CMS client

Create a small module (e.g. src/lib/cms.ts or lib/cms.js) so the rest of the app uses one client:

import { createClient } from '@elmapicms/js-sdk';

export const cms = createClient(
  process.env.ELMAPI_URL!,
  process.env.ELMAPI_API_KEY!,
  process.env.ELMAPI_PROJECT_ID!
);

Use in code

  • List posts: cms.getEntries('posts', { sort: '-published_at', paginate: 10, page: 1 }) (or whatever sort/options your collection uses). The SDK maps to the right query params.
  • Post by slug: Use the SDK’s filter option for slug (e.g. cms.getEntries('posts', { filters: { slug: 'my-slug' } })) and take the first result. Check the SDK docs for the exact option name; it will correspond to where[slug]= under the hood.

Exact method names may vary by SDK version; the npm package and Content API docs are the source of truth. The important part for prompting is: “We have a cms client from @elmapicms/js-sdk created with ELMAPI_URL, ELMAPI_API_KEY, ELMAPI_PROJECT_ID. Use it in server components or API routes only.”


Step 5: Plain Fetch Alternative

If you don’t use the SDK or the AI generates fetch calls, use the raw API with the right headers and query params.

List posts (paginated)

const baseUrl = process.env.ELMAPI_URL;
const projectId = process.env.ELMAPI_PROJECT_ID;
const token = process.env.ELMAPI_API_KEY;

const res = await fetch(
  `${baseUrl}/posts?paginate=10&page=1`,
  {
    headers: {
      'Accept': 'application/json',
      'project-id': projectId!,
      'Authorization': `Bearer ${token}`,
    },
  }
);
const json = await res.json();
const posts = json.data ?? json; // paginated returns { data }; sometimes a plain array

One post by slug

const res = await fetch(
  `${baseUrl}/posts?where[slug]=${encodeURIComponent(slug)}`,
  {
    headers: {
      'Accept': 'application/json',
      'project-id': projectId!,
      'Authorization': `Bearer ${token}`,
    },
  }
);
const json = await res.json();
const post = Array.isArray(json) ? json[0] : json.data?.[0] ?? json;

Use these only in server-side code or API routes so the token never reaches the client.


Step 6: Example Prompts for Blog Lists and Single Posts

Once env and the client (or fetch helpers) exist, you can prompt Claude or Cursor to build pages.

Blog list page

Add a /blog page that lists posts from ElmapiCMS. We have a cms client in src/lib/cms.ts created with createClient(ELMAPI_URL, ELMAPI_API_KEY, ELMAPI_PROJECT_ID). Use it in a server component to fetch posts (collection slug "posts"), then render each post’s fields.title, fields.slug, fields.excerpt and link to /blog/[slug]. Use the existing cms client; don’t add a new env or fetch from the client.

If you use fetch instead of the SDK:

Add a /blog page that lists posts from ElmapiCMS. We have env vars ELMAPI_URL, ELMAPI_PROJECT_ID, ELMAPI_API_KEY. In a server component or API route, call GET {ELMAPI_URL}/posts?paginate=10&page=1 with headers Accept: application/json, project-id: ELMAPI_PROJECT_ID, Authorization: Bearer ELMAPI_API_KEY. Parse the "data" array and render each entry’s fields.title, fields.slug, fields.excerpt with links to /blog/[fields.slug]. Do not use these env vars in client components.

Single post page

Add a page at /blog/[slug] that fetches one post by slug from ElmapiCMS. Use our cms client in src/lib/cms.ts (or the same env and fetch in a server component). Call the API with where[slug]=<slug> and use the first item. Render fields.title and fields.content. Keep all CMS calls server-side.

Homepage with latest posts

On the home page, show the latest 3 blog posts from ElmapiCMS. Use our cms client (or fetch with ELMAPI_* env) in a server component. Use paginate=3&page=1 or take the first 3 from the list. Display title, excerpt, and link to /blog/[slug] for each. No CMS token in the client bundle.

If the model invents wrong URLs or env names, remind it: “Use ELMAPI_URL, ELMAPI_API_KEY, ELMAPI_PROJECT_ID and the pattern /posts?paginate=10&page=1 for list, /posts?where[slug]=… for single post. All CMS requests must be server-side.”


Tips and Gotchas

  • Token only on server: Use the CMS client or fetch only in server components, API routes, or other server-side code. Never ship the token to the browser.
  • Headers on every request: Raw fetch must send Accept: application/json, project-id, and Authorization: Bearer + token. Missing headers often cause 401 or empty responses.
  • Path = collection slug: The path is the collection slug only, e.g. /posts. Full example: baseUrl + '/posts?paginate=10&page=1'.
  • Paginate vs per_page: The API uses query params paginate and page. Do not use per_page.
  • By slug: Use where[slug]=<slug> on the list endpoint and take the first item. There is no separate “get by id/slug” URL.
  • Response shape: With paginate, the body has data, meta, and links. Without it, you may get a plain array. Use data when you requested pagination.

Next Steps

Once env vars and a small CMS layer (SDK or fetch) are in place, you can iterate with Claude or Cursor to add or change pages and keep all ElmapiCMS access server-side.

Share this post:

Related posts