payload-plugin-llms
v0.9.2
Published
Payload global for llms.txt / llms-full.txt, Lexical → Markdown, and Next.js markdown helpers.
Maintainers
Readme
payload-plugin-llms
Helpers for llms.txt / llms-full.txt content stored in a Payload global, Lexical → Markdown
conversion, and Next.js utilities for markdown responses and Accept-based rewrites.
Install
pnpm add payload-plugin-llmsnpm install payload-plugin-llmsPeer dependencies
payload^3.80.0@payloadcms/richtext-lexical^3.80.0next^15.0.0 || ^16.0.0— optional; required forpayload-plugin-llms/next
payload.config.ts
import { buildConfig } from "payload"
import { llmsPlugin } from "payload-plugin-llms"
export default buildConfig({
plugins: [
llmsPlugin({
enabled: true,
routes: {
llmsTxt: "/llms.txt",
markdownSegment: "md",
},
global: {
slug: "llms",
label: "LLMs",
localized: false,
},
}),
],
})normalizeOptions(options) returns defaults including global:
import { normalizeOptions } from "payload-plugin-llms"
const normalized = normalizeOptions({})Options (PayloadPluginLlmsOptions)
| Option | Default | Purpose |
| ------------------------ | ------------- | -------------------------------------------------------- |
| enabled | true | Toggle plugin |
| routes.llmsTxt | "/llms.txt" | Documented path for llms.txt in your app |
| routes.markdownSegment | "md" | Segment name for markdown URLs (rewrites) |
| global.slug | "llms" | Global slug |
| global.label | "LLMs" | Admin label |
| global.localized | false | Localize tab fields |
| global.overrides | — | Partial<GlobalConfig> merged into the generated global |
If a global with the same slug already exists in config, the plugin does not add another.
Core helpers (payload-plugin-llms)
buildLlmsTxt({ payload, locale?, draft?, depth?, globalSlug?, lexical? })— reads the llms.txt tab and returns markdown text (lexical.resolveLexicalBlockhandles top-level serializedblocknodes)buildLlmsFullTxt({ … })— same for the llms-full.txt tabbuildLlmsTxtFromStructured(content)— legacy structuredLlmsTxtContent→llms.txtlines (no Lexical)formatLlmsTxtLink(link)lexicalToMarkdown(root, options?)— sync Lexical JSON → Markdown (blocknodes are omitted)lexicalToMarkdownAsync(root, options?)— async; optionalresolveLexicalBlockfor custom Lexical blockscreateLlmsGlobal(normalizeOptions())— use the generated global config directly if you are not using the plugincreateLlmsLexicalEditor()— Lexical adapter for custom fields that should match the llms editor
Example: Next.js llms.txt route
import { cacheLife } from "next/cache"
import { getPayload } from "payload"
import { buildLlmsTxt } from "payload-plugin-llms"
import { LlmsResponse } from "payload-plugin-llms/next"
import config from "@payload-config"
export async function GET() {
"use cache"
cacheLife("days")
const payload = await getPayload({ config })
const body = await buildLlmsTxt({
payload,
locale: "en",
depth: 2,
lexical: {
resolveInternalLink: ({ relationTo, value }) =>
relationTo && value && typeof value === "object" && value !== null && "slug" in value
? `/${String((value as { slug?: string }).slug)}`
: null,
resolveLexicalBlock: async (fields) => {
if (fields.blockType === "my-custom-block") {
// Load data, format markdown, etc.
return "\n---\n\n"
}
return ""
},
},
})
return new LlmsResponse(body)
}lexical.resolveLexicalBlock runs only for Lexical nodes with type: "block" that are direct
children of the field’s root; omit it if you do not use custom blocks in llms rich text.
Lexical → Markdown
import { lexicalToMarkdown, lexicalToMarkdownAsync } from "payload-plugin-llms"
lexicalToMarkdown(payloadLexicalRoot, {
resolveInternalLink: (reference) =>
reference.relationTo && reference.value
? `/${reference.relationTo}/${String(reference.value)}`
: (reference.url ?? null),
})
await lexicalToMarkdownAsync(payloadLexicalRoot, {
resolveInternalLink: (reference) => reference.url ?? null,
resolveLexicalBlock: async (fields) => (fields.blockType === "my-block" ? "custom output\n" : ""),
})Types include LlmsTxtContent, LlmsTxtSection, LlmsTxtLink, BuildLlmsTxtFromGlobalOptions,
LexicalToMarkdownOptions, LexicalToMarkdownAsyncOptions, SerializedLexicalNode.
Next.js (payload-plugin-llms/next)
Use your own App Router handlers and choose caching (dynamic, revalidate, unstable_cache,
etc.). This entry exports small helpers only:
LlmsResponse— subclass ofResponse; constructor mergesDEFAULT_LLMS_TXT_HEADERSwith optionalinit.headersand forwards otherResponseInitfieldscreateMarkdownResponse(markdown, { headers? })createMarkdownRewrites(...)— Accept:text/markdownrewritesDEFAULT_LLMS_TXT_HEADERS,DEFAULT_MARKDOWN_HEADERS,mergeHeaders,isMarkdownAccepted
Rewrites for clients that prefer Markdown
CreateMarkdownRewritesOptions: either { locales, localizedRoutes?: true, ... } for
/:locale/... routes, or { localizedRoutes: false, ... } when there is no locale prefix. Optional
fields: markdownSegment, acceptHeaderPattern, includeIndexRewrite, has.
import type { NextConfig } from "next"
import { createMarkdownRewrites } from "payload-plugin-llms/next"
const nextConfig: NextConfig = {
async rewrites() {
return createMarkdownRewrites({
locales: ["en", "de"],
markdownSegment: "md",
})
},
}
export default nextConfigLicense
Apache-2.0 (see repository root).
