---
name: genko-me
description: Use when working in a project that uses genko.me as its headless CMS - it has genko.config.json, depends on @genko-me/sdk or @genko-me/cli, or imports lib/client.ts or lib/schema.ts. Covers setup with genko init, regenerating types with genko pull after a schema change, fetching content with the typed client, the server-side-only API key rule, and draft preview / scheduled publishing behaviour.
---

# genko.me

genko.me is a headless CMS. Content types (called APIs) are defined in the genko.me Studio and delivered as JSON from `https://api.genko.me`. `@genko-me/sdk` is the typed client; `@genko-me/cli` generates TypeScript from the workspace schema.

This file is procedure, not reference. Look details up instead of guessing:

- OAuth MCP server `https://genko.me/mcp` (docs and content tools). Add it with `npx @genko-me/cli mcp` and sign in. Public docs are available without login through llms.txt.
- `https://docs.genko.me/llms.txt`, or any docs page plus `.md` (e.g. `https://docs.genko.me/sdk/fetching.md`).

## Setup

`genko.config.json` in the project root means the project is already set up. It records the workspace, the base URL and the generated paths (`schemaOutput`, `src/lib/schema.ts` for src routers, otherwise `lib/schema.ts`; `clientOutput`, `src/lib/client.ts` for src routers, otherwise `lib/client.ts`).

Otherwise run `npx @genko-me/cli init` in the project root. It prompts for the workspace ID, API key and APIs to sync, so run it interactively - or pass `--workspace <id> --yes` and `--apis blog,works` with `GENKO_API_KEY` already set. It installs `@genko-me/sdk` (and `@genko-me/cli` as a dev dependency), detects root app/pages versus src/app or src/pages and writes the two files under lib/ or src/lib/, plus genko.config.json, and stores the key in `.env.local`. It refuses to overwrite existing files unless `--force` is passed; read what is already there before reaching for `--force`.

## Astro

For Astro 7.4+, init detects astro in package.json and installs @genko-me/astro. It always writes src/lib/schema.ts and src/lib/client.ts; the client uses getSecret from astro:env/server, never process.env or PUBLIC_ keys. Import it only in Astro frontmatter/server code. init --pages optionally scaffolds /genko and /genko/entries/[id] using the first selected list API; it never overwrites existing pages, even with --force. Set site in astro.config for canonical/share URLs. The generated routes assume a root deployment.
Use Field with explicit api, entry, id; RichText with sanitized html; Pagination with the page from Astro paginate(). ShareButtons needs no React integration or client:load. For draft previews, use a separate prerender=false route and fetchGenkoEntry from @genko-me/astro/server. Set private no-store, noindex, and no-referrer on that route. Rebuild static public pages on all relevant genko webhook events including deletion and scheduled unpublication. See https://docs.genko.me/frameworks/astro/tutorial.

## Keep the generated types in sync

This is the usual cause of confusing failures. `lib/schema.ts` is generated - never edit it by hand.

Whenever someone changes a content type in the Studio (adds, renames or removes a field, flips required, edits select options), regenerate:

```bash
npx @genko-me/cli pull
```

`pull` reads `genko.config.json` and `GENKO_API_KEY`, and rewrites `lib/schema.ts` only. `lib/client.ts` is never touched, so hand-written additions there (plugins, extra options) survive.

If a field exists in the Studio but TypeScript says it does not - or a field you type-checked against is `undefined` at runtime - the generated schema is stale. Run `pull` before debugging anything else.

Two sharp edges in the generated types:

- `reference` / `references` fields are typed `unknown` / `unknown[]`. Narrow or cast at the use site; their runtime shape depends on the `depth` query.
- Fields that are not required are emitted as optional, so handle `undefined` instead of assuming a value.

## Display components (default for Next.js App Router)

Selection is saved as endpoints in genko.config.json. Use genko pull --select-apis to change it, --apis blog,works for noninteractive selection, or --all-apis to sync every API including future additions. Referenced schemas are included automatically. Existing configs without endpoints retain all-API behavior. Watch uses saved selection.
Import client from lib/client.ts and List/ListItem/View/ViewItem from @genko-me/react/next. List repeats normal children for each entry. Always name fields explicitly, e.g. <ListItem id="title" />. Place Pagination, Loading, Error and Null directly under List; state slots are opt-in and accept JSX or template. List, View, fields, Pagination and state slots have no default visual styling; use application CSS. Only PreviewNotice has default styling, independent of parent unstyled; disable it with previewNotice={{unstyled:true}}.
Pass page searchParams to View for saved draft previews; keep the preview URL in Studio only. No extra Route Handler or Proxy file is required. Public reads default to no-store; cache seconds are explicit. Pagination defaults to local state, with mode="url" optional. API keys stay server-side.
genko pull updates field metadata and the API registry; client.ts imports that registry. Use pull --watch for optional development polling.

## Fetch content with the generated client

Import `genko` from `lib/client.ts`. Do not build a second client with an inline key.

```tsx
// app/blog/page.tsx - Server Component
import { genko } from "@/lib/client";

export default async function Page() {
  const { contents } = await genko.apis.blog.list({ limit: 10, orders: "-publishedAt" });

  return (
    <ul>
      {contents.map((post) => <li key={post.id}>{post.title}</li>)}
    </ul>
  );
}
```

- Keys of `genko.apis` are the Studio endpoint names, so a hyphenated endpoint needs bracket access: `genko.apis["site-settings"].get()`.
- List API: `list(queries?)` returns `{ contents, totalCount, offset, limit }`; also `get(id, opts?)`, `getOrNull(id, opts?)`, `getAll(queries?)`.
- Object API: `get(opts?)` and `getOrNull(opts?)` only - there is no `list`.
- Every entry carries `id`, `createdAt`, `updatedAt` and `publishedAt` on top of its own fields.
- `get` throws `GenkoApiError` (which has `status`) when the entry is missing. For route params prefer `getOrNull` and call `notFound()` on `null`.
- `getAll` pages internally 100 at a time and rejects `limit` / `offset`; do not use it where a `list` with `limit` would do.
- Lists accept `limit`, `offset`, `orders`, `filters`, `q`, `depth`, `fields`. Single reads accept `depth`, `fields` and `draftKey`. Lists and single reads of multilingual APIs also accept `lang` (absent = main language), `langFallback` (use the main-language version when that language has none) and `include: ["_langs"]` (adds `_langs: { current, available }` to each item). `filters` is a microCMS-compatible string - build it with `buildFilters()` from `@genko-me/sdk`, and note that `[and]` and `[or]` cannot be mixed in one expression.
- `image` / `images` fields come back as `{ url, alt }`, `file` as a URL string. These URLs are resolved per request - render them, do not persist them in your own storage.
- `richtext` is HTML that genko already sanitized server-side. Render it with `<RichText />` from `@genko-me/react`, or with `dangerouslySetInnerHTML`.

## The API key is server-side only

`GENKO_API_KEY` grants read access to the whole workspace.

- Read it as `getSecret("GENKO_API_KEY")` in Astro or `process.env.GENKO_API_KEY` in `lib/client.ts`, which is what `init` generates. Keep it in `.env.local`, out of git.
- Never expose it to the browser: no `NEXT_PUBLIC_` prefix, no literal in source, and never import `lib/client.ts` from a Client Component.
- Fetch in Server Components, Route Handlers or server actions, then pass plain data to the client.

## Draft preview and scheduled publishing

An entry is `draft`, `published`, `scheduled` or `closed`, and the delivery API re-evaluates the publish window on every request. What an agent must not get wrong:

- List responses contain published entries only. Drafts, scheduled entries whose start time has not passed, and closed entries never appear in `list` / `getAll`, and there is no status field to filter on. An entry that plainly exists in the Studio can legitimately 404.
- `draftKey` works on single-entry and object reads only, never on a list. A preview page therefore has to take the id from the URL instead of finding it by listing.
- Treat `draftKey` as a secret: do not log it, commit it, or inline it at build time.
- Scheduled entries start and stop being delivered on time without a deploy. Do not cache responses indefinitely - use ISR / revalidation, and a webhook if the site must update the moment something is published.
- `publishedAt` is the scheduled start time, not the time the status actually flipped.

In Next.js, `@genko-me/sdk/next` provides the `nextPreview` and `nextRevalidate` plugins, passed through `plugins` in `lib/client.ts`; they expose `genko.preview.GET` and `genko.revalidate.POST` for the matching route handlers. Details: `https://docs.genko.me/guides/preview` and `https://docs.genko.me/guides/webhooks`.
