To publish a rich-text blog post to Payload CMS without the Admin UI, call payload.create() with your posts collection’s slug and a data object whose rich-text field holds a serialized Lexical editor state. Use this Local API route when the code runs inside your Payload application, such as a seed script or a server function. When the caller is an external process, POST the same document data to the collection’s REST endpoint or use the official SDK instead.
Choose the path by where your code runs
Payload offers two ways to write documents programmatically, and the right one depends on where the caller lives. The Local API runs directly inside Node in the same process as your Payload application, and the official docs list seed scripts as a典型 use case. The REST API is an HTTP interface generated for each collection, so any external service can reach it.
| Caller | Transport | Access behavior | Typing |
|---|---|---|---|
| Server code inside the Payload app (seed scripts, server functions) | Local API, payload.create() |
Bypasses access control by default. To run as a user, pass that user and set overrideAccess: false. |
Generated Local API types, when the project’s generated types are available |
| External service, script on another host, or browser-adjacent code | REST API (POST to the collection endpoint), or the Payload SDK create({ collection, data }) |
Requests go through configured authentication and access rules. Credentials must be configured for the installation, not hardcoded in browser code. | The SDK is documented as a typed option for REST clients; detailed REST type behavior is not stated in the reviewed docs |
The sections below follow that split. Start with the Local API because it covers most seed and migration work, then move to the external paths.
Confirm the collection before writing any code
A generic posts collection cannot be assumed. Payload collections define the fields of each document and generate the Local, REST, and GraphQL APIs that manage them, so the real configuration in your project decides every value in your call. Before writing data, check the following in your collection config, as documented in Payload’s collection configuration reference:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
- Collection slug. This is the value you pass as
collection. Your project may call it something other thanposts. - Rich-text field name. Your body field might be
content,body, or something else. - Required fields. Any field marked required must appear in
data, or the create will fail. - Access settings. These decide who can create documents over REST and what server code needs to pass through when it runs as a user.
- Publish workflow, locales, database adapter, and API route prefix. Draft and publish handling, localization, and the route prefix (
/apiby default) differ between projects. A created document is not automatically public; check whether your collection uses drafts before assuming a sample post is live.
Create the post with the Local API
On the server, get your Payload instance and call create with the collection slug and a data object. The Local API documentation at payloadcms.com/docs/local-api/overview describes this direct in-process access.
Seed script pattern
A seed script is the simplest case: it loads your Payload config, creates the instance, and runs one or more create calls. The snippet below shows the shape. Treat it as an illustration, not a drop-in file, because the slug, field names, and editor value must match your own project.
const post = await payload.create({
collection: 'posts', // the slug from your collection config
data: {
title: 'Example post',
content: editorState, // Lexical state built for this field's features
},
})
Generated Payload types can give you type hints for data when your project has them. Regenerate them after you change the collection, or the hints will describe an older schema.
Reading the result
The call returns the created document. Use its id for follow-up writes, such as a later update or a link from another document, rather than searching by title, which may match more than one post.
Recommended Free Tools
Build the rich-text value as Lexical state
The rich-text field expects serialized Lexical editor data, not an arbitrary HTML string. The Payload rich-text documentation at payloadcms.com/docs/rich-text/overview describes the Lexical setup, which uses the @payloadcms/richtext-lexical package and the lexicalEditor() function in your configuration.
Match the editor configuration
Saved Lexical data is a tree of typed nodes. Payload’s TypeScript types for this data, TypedEditorState, can be parameterized with the serialized node types your editor has enabled. The features you turn on in lexicalEditor() therefore determine which node types are valid. If your script produces nodes for a feature the field does not enable, the saved content will not match the field’s model. Generate the state with the same editor configuration the field uses, and keep the feature list in one shared place so the script and the admin editor cannot drift apart.
Where the editor state comes from
The reviewed documentation establishes the data model and the Local API call, but it does not describe a specific helper for converting Markdown or HTML into Lexical state. If your content starts as HTML or Markdown, you need a conversion step that you write and test yourself, and the output must be checked against the enabled features before you save it.
Respect access control in server code
Local API operations bypass access control by default. That is convenient for a trusted seed script, but it means a server-side call does not automatically enforce the same permissions as a logged-in REST request. The access-control guidance at payloadcms.com/docs/local-api/access-control describes the pattern for running an operation under a user’s permissions: pass the authenticated user and set overrideAccess: false.
const post = await payload.create({
collection: 'posts',
data: { title: 'Drafted by an editor', content: editorState },
user: authenticatedUser, // the user whose permissions should apply
overrideAccess: false,
})
Decide this per operation. A seed script that loads initial content can keep the default bypass, while a server action triggered by a visitor or editor should pass that person’s user so that the create is checked against their rights.
Post from outside the Payload application
An external service cannot call the Local API, because the Local API only exists inside the Node process that loads your Payload config. It uses HTTP instead. The REST routes use the configured API prefix, which is /api by default, so creating a document in a collection with the slug posts means sending a POST to /api/posts on your installation’s base URL. The request body carries the same document data as the Local API data object, and the request must be authenticated and permitted by your collection’s access rules. The REST overview is at payloadcms.com/docs/rest-api/overview.
The official SDK documents a create({ collection, data }) operation and file uploads for upload-enabled collections, so it is a typed alternative to hand-written HTTP calls. Either way, store credentials on the server or in a secrets manager. Do not embed them in browser code, and do not hardcode a hostname the reviewed material does not establish for your site.
Call the server action from a Next.js frontend
In a Next.js app, the safest pattern keeps the privileged write in a server function and exposes only one narrow action to the page. The Payload guide at payloadcms.com/docs/local-api/server-functions shows the pattern: obtain the Payload instance with getPayload, call payload.create(), and return the created document. The example below follows that structure. Import your project’s own config where the comment indicates.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsRank #4
'use server'
import { getPayload } from 'payload'
import config from 'your-project-payload-config' // your project's config import
export async function createPost(title: string, editorState: unknown) {
try {
const payload = await getPayload({ config })
const post = await payload.create({
collection: 'posts',
data: { title, content: editorState as any },
})
return post
} catch (error) {
console.error(error)
throw error
}
}
Validate the input inside the function before calling create, and decide who may call it. The example does not check the caller, so add an authentication check and pass the user with overrideAccess: false if the action should be limited to editors.
Attach media only when you need it
Image uploads are optional. For an upload-enabled collection, the Uploads documentation at payloadcms.com/docs/upload/overview describes passing a local file path through filePath to payload.create() in a seed script. The path must be absolute.
const media = await payload.create({
collection: 'media', // the upload-enabled collection in your project
data: { alt: 'Cover image' },
filePath: '/absolute/path/to/cover.jpg',
})
Keep media out of the minimum publishing path. Create the text post first, then attach an image in a separate step if the post needs one, so a file problem does not block the article.
Check the version and verify the first post
The reviewed official docs explain the API shape and the editor data model, but the passages do not pin a release number. Package APIs and generated types change between releases, so check your installed payload and @payloadcms/richtext-lexical versions against the docs you are reading. Regenerate your payload-types.ts file after any schema change, and treat the snippets above as shapes to adapt.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Before you rely on a script, create one post in a non-production database and read it back through the admin panel or the REST API. Confirm that the rich-text content renders, that the status is what you expected (draft or published), and that a request without credentials cannot write the document when access rules should block it.
Once a script has created one post correctly, the same create call can run in a loop over your content files, with one document per entry and an error log for any create that fails.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




