Logokeyset
  • Product
  • Pricing
  • Docs
  • Contact
LoginGet Started
keyset
Navigation
  • Product
  • Pricing
  • Docs
  • Contact
Login to AccountGet Started Free
keyset

A powerful configuration and content management system for modern applications.

Product

  • Features
  • Pricing
  • Use Cases
  • Documentation

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Follow Us

TwitterGitHub

© 2026 Keyset. All rights reserved.

Logokeyset
  • Product
  • Pricing
  • Docs
  • Contact
LoginGet Started
keyset
Navigation
  • Product
  • Pricing
  • Docs
  • Contact
Login to AccountGet Started Free
Docs/Guides/Custom CMS Panel
Getting Started
Reference
Guides
  • Error Handling
  • Custom CMS Panel

Custom CMS Panel

If you don't want to use the Keyset hosted dashboard — or you want to embed content management directly into your own admin interface — you can build a fully self-hosted CMS panel using only the SDK and a secret key.

What you need

  • A secret key (skey_…)
  • Your project ID (visible in the Keyset dashboard or from your API key creation flow)
  • The keysetapp package

How it works

The SDK exposes four methods that together give you everything a CMS needs:

MethodWhat it does
content.dashboardList(projectId)Fetch all entries with full metadata (id, createdAt, updatedAt)
content.create(entry)Create a new entry
content.update(key, entry)Edit an existing entry's value and/or type
content.delete(key)Remove an entry

Minimal example

TypeScript JSX
import { useEffect, useState, useCallback } from "react";
import { Keyset } from "keysetapp";
import type { DashboardContentEntry } from "keysetapp";

const client = new Keyset({ apiKey: "skey_…", cacheTTL: 0 });
const PROJECT_ID = "your_project_id";

export function MyCMSPanel() {
  const [entries, setEntries] = useState<DashboardContentEntry[]>([]);

  const load = useCallback(async () => {
    const res = await client.content.dashboardList(PROJECT_ID);
    setEntries(res.data);
  }, []);

  useEffect(() => { load(); }, [load]);

  async function handleCreate() {
    await client.content.create({
      key:   "welcome_message",
      value: "Hello from my CMS!",
      type:  "string",
    });
    load(); // refresh the list
  }

  async function handleUpdate(key: string, newValue: string) {
    await client.content.update(key, { value: newValue, type: "string" });
    load();
  }

  async function handleDelete(key: string) {
    await client.content.delete(key);
    load();
  }

  return (
    <div>
      <button onClick={handleCreate}>Create entry</button>
      <ul>
        {entries.map((entry) => (
          <li key={entry.id}>
            <strong>{entry.key}</strong>: {String(entry.value)}
            <button onClick={() => handleUpdate(entry.key, "New value")}>Edit</button>
            <button onClick={() => handleDelete(entry.key)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

The DashboardContentEntry shape

dashboardList returns entries with more metadata than the regular read methods:

TypeScript
interface DashboardContentEntry {
  id:        string;      // server-generated entry ID
  key:       string;      // your config key
  value:     unknown;     // the stored value
  type:      ContentType; // "string" | "number" | "boolean" | "json" | "url" | "image"
  createdAt: string;      // ISO 8601
  updatedAt: string;      // ISO 8601
}

This is enough to power a full CRUD admin interface.

Working demo

A complete, styled CMS panel is included in the demo project at:

/demo/src/demos/CMSPanel.tsx

It demonstrates:

  • Fetching and displaying all entries in a table
  • Creating new entries via a form
  • Inline editing (clicking "Edit" makes the row editable in place)
  • Deleting entries with a confirmation prompt
  • Automatic cache invalidation after every mutation

Run the demo with:

Shell
cd demo && npm install && npm run dev

Then open the Custom CMS Panel tab.

Security considerations

  • Never expose your secret key client-side. If you are embedding the CMS panel in a browser app (like an admin dashboard), proxy the write operations through your own backend API route that holds the secret key server-side.
  • A public key (pkey_…) throws "This operation requires a secret key" before making any network call — the SDK enforces this automatically.
  • For internal admin tools that run in Node.js (Next.js API routes, Express, Edge functions), you can use the secret key directly.

Typical architecture

Browser admin UI
      │
      │  (calls your own API)
      ▼
Your backend (Node.js / Edge)
      │  holds skey_… securely
      │
      ▼
  keysetapp  ──►  Keyset API

This keeps the secret key off the client entirely while still giving you a fully custom management UI.

PreviousError Handling

Use with AI

Copy the full Keyset SDK reference as a ready-to-use AI prompt. Paste it into any AI tool to get instant, accurate integration help.

Resources

  • FAQ
  • Privacy Policy
keyset

A powerful configuration and content management system for modern applications.

Product

  • Features
  • Pricing
  • Use Cases
  • Documentation

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Follow Us

TwitterGitHub

© 2026 Keyset. All rights reserved.