Comments
LeadCMS supports a content-first comments workflow similar to content and media sync. Comments are stored in LeadCMS, pulled into your repository as JSON files, and then read by the SDK from local files during build or runtime.
This gives you a predictable workflow:
- Sync comments from LeadCMS with the CLI
- Read comments locally through the SDK
- Render flat or threaded comment UIs in your site
At the moment, the public SDK surface is focused on reading synchronized comments. This is enough for most static site, SSR and preview scenarios.
Comment synchronization is always anonymous. LeadCMS SDK does not send authentication when pulling comments, so the synchronized files contain only public comment data intended to be visible to clients.
How comments are stored locally
By default, comments are stored in the .leadcms/comments/ directory.
The exact path depends on the comment language and the entity the comments belong to.
Typical structure:
.leadcms/comments/
en/
content/
91.json
en-US/
content/
91.json
ru/
content/
91.json
Each file contains all comments for one entity. For content comments, the file name is the content ID, not the slug. The SDK resolves that automatically for the content-specific helpers, so you can pass either a numeric content ID or a content slug when reading content comments.
For example, comments for content item 91 in English are stored in:
.leadcms/comments/en/content/91.json
Syncing comments
To work with comments locally, pull them from LeadCMS first:
# Pull only comments
pnpm exec leadcms pull-comments
# Or pull everything
pnpm exec leadcms pull
After the pull finishes, the SDK can read the synchronized files directly from your repository.
Reading comments with the SDK
Flat comments list
Use getCommentsForContent() when you want all comments for a content item as a flat array:
import { getCommentsForContent } from '@leadcms/sdk'
const comments = getCommentsForContent(91, 'en')
const commentsBySlug = getCommentsForContent('pricing', 'en')
This is the most common entry point for page-level comment rendering. The helper accepts either the numeric content ID or the content slug.
Generic entity access
If you need comments for a different entity type, use getComments():
import { getComments } from '@leadcms/sdk'
const comments = getComments('Content', 91, 'en')
This is useful when you work with a generic data layer and already know the commentableType and commentableId.
Strict variants
The regular comment functions are intentionally forgiving:
- missing file → empty array
- invalid file → empty array
If you want explicit failures in development, use the strict variants:
import {
getCommentsStrict,
getCommentsForContentStrict,
} from '@leadcms/sdk'
const comments1 = getCommentsStrict('Content', 91, 'en')
const comments2 = getCommentsForContentStrict(91, 'en')
const comments3 = getCommentsForContentStrict('pricing', 'en')
These throw descriptive errors if the file is missing or cannot be parsed. If you pass a slug that does not resolve to local content, the strict variant throws as well.
Building threaded comment trees
For nested replies, use the tree helpers instead of grouping comments manually.
import { getCommentsTreeForContent } from '@leadcms/sdk'
const tree = getCommentsTreeForContent(91, 'en', {
sortOrder: 'newest',
replySortOrder: 'oldest',
})
const treeBySlug = getCommentsTreeForContent('pricing', 'en')
The returned nodes include:
children– nested repliesdepth– nesting depthisLeaf– whether the node has repliesthreadCount– total comments in the thread
This makes it easy to build Reddit-style or forum-style interfaces.
Comment fields
A synchronized comment typically includes fields like:
idparentIdauthorNameavatarUrlbodystatusanswerStatuscreatedAtupdatedAtcommentableIdcommentableTypelanguagetags
The moderation-related fields are especially useful:
- status – current moderation status such as
Approved,NotApproved,Spam, orAnswer - answerStatus – answer workflow state such as
Unanswered,Answered, orClosed
These fields can be used to filter or style comments in your UI.
When present, avatarUrl is the URL of the author's avatar image.
It is server-provided read-only metadata and is not part of client-side create or update payloads.
authorEmail is not preserved in synchronized comment files. It may be supplied only when creating a new comment, after which LeadCMS stores it server-side and the SDK refreshes comments anonymously.
Common usage patterns
Render a flat list
import { getCommentsForContent } from '@leadcms/sdk'
const comments = getCommentsForContent(91, 'en')
const sameComments = getCommentsForContent('pricing', 'en')
for (const comment of comments) {
console.log(`${comment.authorName}: ${comment.body}`)
}
Show only approved comments
import { getCommentsForContent } from '@leadcms/sdk'
const comments = getCommentsForContent(91, 'en')
const approved = comments.filter((comment) => comment.status === 'Approved')
Separate root comments and replies
import { getCommentsForContent } from '@leadcms/sdk'
const comments = getCommentsForContent(91, 'en')
const rootComments = comments.filter((comment) => !comment.parentId)
const replies = comments.filter((comment) => comment.parentId)
Comments and localization
Comments are language-aware in the same way as content. If your project uses multiple locales, comments are stored and loaded per language.
This means you should pass the current locale when reading comments:
const comments = getCommentsForContent(content.slug, locale)
That keeps your comment lists aligned with the language of the current page.
Privacy and personal data
LeadCMS treats synchronized comments as a public read model.
- Comment pulls are always anonymous. The SDK does not send an API key when running
pull-commentsor when refreshing comments after a write. - Pulled files should contain only public comment data that is safe to commit to your repository, including open-source repositories.
authorEmailis write-only from the client perspective. You may provide it when creating a new comment so LeadCMS can store it server-side.authorEmailis not preserved locally after sync. Once the comment is pushed, the SDK refreshes comments anonymously and the stored JSON files no longer contain the email address.- Render comments from synchronized files, not from authenticated write responses, if you want to preserve the same public-data guarantees in your application.
Current workflow scope
Today, LeadCMS offers a strong read workflow for comments through the public SDK and a sync workflow through the CLI.
- Public SDK read APIs – available
- Threaded comment tree helpers – available
- CLI sync (
pull-comments) – available - Comment push/status CLI support – available for local sync workflows
- Public SDK create/update/delete comment methods – not yet part of the public SDK surface
For most websites, this is enough to display synchronized comments and build comment interfaces without querying the CMS at runtime.
Best practices
- Always pull comments before building so your local files are up to date.
- Use the content-specific helpers for slug support. Generic entity helpers like
getComments()still require a numeric entity ID. - Pass the active locale when your site is multilingual.
- Use tree helpers for replies instead of reconstructing parent-child relations yourself.
- Filter by moderation fields like
statusandanswerStatusin the UI if you only want approved or open discussions. - Treat local comment files as synchronized public data, not hand-edited content, unless your workflow explicitly requires local editing.
- Do not rely on
authorEmailin pulled files. Provide it only when creating a new comment, then let the anonymous refresh replace the local file.
Next steps
With comments synchronized and readable through the SDK, you can continue with localization or preview workflows. Continue with: