Skip to content

Quick Start

LUNO is a free AI-era backend platform (Headless CMS + forms + secure APIs) on Cloudflare Workers. Start on the path that matches your goal.

Choose a path

PathBest forTimeDone state → start
A. Agents (MCP)Operate from Cursor / Claude Code / Codex~5 minDone stateoverview
B. ConsoleLearn create → approve → publish in admin~10 minDone statesteps
C. API onlyRead published content and wire your site~3 minDone statePublic API
bash
# Path A (recommended / fastest)
npx @luno-cms/mcp setup

Path B continues below. For A and C, follow the links above.

Start with Console

Sign in to the admin panel, publish your first entry, and fetch it from the public API.

Step 1: Log In to the Admin Panel

Open your luno admin URL (e.g., https://cms.example.com) and sign in.

MethodDescription
Email + PasswordFollow the invitation link from your admin to set your password, then log in
Google AccountClick "Sign in with Google" to authenticate via OAuth

Didn't receive an invitation?

Contact your project admin and ask them to send an invitation to your email address.

Step 2: Understand the Layout

After signing in, the left sidebar shows your navigation:

MenuDescription
DashboardSummary of published entries, drafts, and recent activity
Form Set nameEntry list for each content type (e.g., Blog, News)
MediaUpload and manage images and files
Master DataManage shared reference data like categories and tags
Contact FormsView and manage contact form submissions
MembersInvite team members and manage roles
WidgetsConfigure embeddable content widgets
SettingsForm sets, Webhooks, API keys, and more

Step 3: Create Your First Entry

Select a Form Set

Click on a form set in the sidebar (e.g., "Blog", "News"). Form sets define the structure of your content. Your admin will have created them in advance.

Create a New Entry

Click New Entry and fill in the fields:

  • Text fields: Type a title or short description
  • Rich text (tiptap): Use the WYSIWYG editor for formatted body content
  • Media fields: Upload and attach an image

When done, click Save. The entry is saved as a draft.

Submit for Review → Approve → Publish

Use the status button in the top-right corner to move through the publishing workflow:

Draft → Submit for Review → Approve → Published
  1. Submit for Review: Notify the reviewer that the content is ready.
  2. Approve: The reviewer confirms and approves.
    • "Publish now" → status becomes Published
    • "Schedule" → auto-publishes at the specified date and time
  3. Published: The entry is live and available via the public API.

About roles

tenant_admin (administrator) can perform all operations including approval and publishing. tenant_user (regular user) can create, edit, and submit for review. See Content Management for details.

Step 4: Fetch Your Content via the Public API

Once published, you can retrieve the content without any authentication. Prefer /public/p/{projectId}/v1 in production.

List entries

bash
# Recommended: projectId base
curl "https://api.luno.rest/public/p/{projectId}/v1/form-sets/blog/entries?include_snapshot=true"

# Host-resolved (when you have a public host)
curl "https://cms.example.com/public/v1/form-sets/blog/entries?include_snapshot=true"
ts
const BASE = 'https://api.luno.rest/public/p/{projectId}/v1'

const res = await fetch(
  `${BASE}/form-sets/blog/entries?include_snapshot=true`
)
if (!res.ok) throw new Error(`API error: ${res.status}`)
const { items } = await res.json()
bash
# Once in your site repo
npx @luno-cms/mcp setup

# Then ask your agent, e.g.:
# "List published entries in the blog form set"

Response:

json
{
  "formSet": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "slug": "blog",
    "name": "Blog"
  },
  "total": 1,
  "limit": 20,
  "offset": 0,
  "items": [
    {
      "entry": {
        "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
        "slug": "my-first-post"
      },
      "published": {
        "revisionId": "a2f3d4e5-...",
        "revision": 1,
        "updatedAt": "2025-01-15T10:00:00Z"
      }
    }
  ]
}

Fetch a single entry with field values

bash
curl "https://api.luno.rest/public/p/{projectId}/v1/form-sets/blog/entries/my-first-post?include_snapshot=true"
ts
const BASE = 'https://api.luno.rest/public/p/{projectId}/v1'

async function getPost(slug: string) {
  const res = await fetch(
    `${BASE}/form-sets/blog/entries/${slug}?include_snapshot=true`
  )
  if (!res.ok) {
    if (res.status === 404) throw new Error('Post not found')
    throw new Error(`API error: ${res.status}`)
  }
  return res.json()
}
bash
# Ask your agent, e.g.:
# "Fetch the published body for slug my-first-post"

Response:

json
{
  "formSet": { "slug": "blog", "name": "Blog" },
  "entry": { "id": "...", "slug": "my-first-post" },
  "revision": { "revision": 1, "updatedAt": "2025-01-15T10:00:00Z" },
  "data": {
    "title": "My First Post",
    "body": "<p>Hello, world!</p>",
    "cover": "asset-uuid-here",
    "published_date": "2025-01-15"
  },
  "mediaUrls": {
    "cover": "https://cms.example.com/public/v1/media/asset-uuid-here"
  }
}

data keys match form-set field keys. mediaUrls holds fully-qualified asset URLs.

Framework recipes

For paste-ready list/detail examples in Next.js, Astro, and Nuxt, see Framework recipes.

Troubleshooting

SymptomCauseFix
Empty entry listNo published entries yetApprove and publish your draft
404 NOT_FOUNDWrong form set slugCheck the slug in Settings → Form Sets
Field values not returnedMissing include_snapshot=trueAdd the query parameter
Image URL missingLooking in wrong placeUse mediaUrls[fieldKey] for full URLs

Next Steps