Story Block SDK renderers blank the entire field when any node has an empty content array
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Problem Statement
isValidBlocks (core-web/libs/sdk/uve/src/lib/editor/internal.ts:65-70) rejects any node whose content is an empty array, recursively, at any depth:
if (blocks.content.length === 0) {
return { error: 'Error: Blocks content is empty. At least one block is required.' };
}
All four consuming renderers — React, Vue, Angular classic, Angular semantic — treat that as fatal and render nothing for the whole field. This is the delivery-side twin of TipTap's atomic Node.fromJSON: a single unrecognised element discards the entire document.
{"type":"paragraph","content":[]} is a shape the dotCMS editor never emits — it omits the content key on an empty paragraph. But the editor is not the only writer. Migrations, imports, the REST API and third-party tooling all write Story Block JSON.
Customer impact — verified
In FD #38931 (CITGO) a Kentico -> dotCMS migration translated <p></p> spacer paragraphs literally, and 461 published articles rendered a completely blank body while the page shell, title and hero image loaded normally.
Support's verification is on the ticket:
- JSON confirmed via
/api/content/query— thebodyfield contains"content":[]nodes - Console error, reproducible:
Error in nested block at index N: Error: Blocks content is empty. At least one block is required. - Affected count scoped by owner and migration run date — Jul 23: 82, Aug 12: 103, Aug 14: 276
- Zero natively-authored articles affected — 100% migration-borne
- Republishing does not help: re-saving writes the same JSON
The customer's fix was a stripEmptyBlocks sanitiser in their own React code, shipped by dotCMS engineering as a workaround. That ticket's internal note recorded the follow-up — "file an SDK graceful-degradation issue — a single empty paragraph node should be skipped, not blank the entire body" — and no issue was ever filed. This is that issue.
Steps to Reproduce
- Store a Story Block value containing an empty-content node at any depth, e.g.:
(via{"type":"doc","content":[ {"type":"paragraph","content":[{"type":"text","text":"Visible text"}]}, {"type":"paragraph","content":[]} ]}/api/content/or a migration script — the editor will not produce this shape) - Render the field through any SDK renderer:
<DotCMSBlockEditorRenderer blocks={...} />(React), the VueDotCMSBlockEditorRenderer, or either Angular renderer. - Open the browser console.
Expected: the paragraph with content renders; the empty node degrades gracefully.
Actual: the entire field renders nothing. Console shows Error: Blocks content is empty. At least one block is required.
Acceptance Criteria
- An empty
content: []array is no longer treated as a validation failure byisValidBlocks - A node with
content: []renders as its empty element (e.g.{"type":"paragraph","content":[]}-><p></p>), matching what the customer'sstripEmptyBlocksworkaround produces today, so their 461 articles render identically once they drop the patch - Sibling and ancestor nodes render normally — one empty node never suppresses the rest of the document
- Nested empty nodes at any depth behave the same as top-level ones
- Genuinely malformed input still fails loudly:
contentpresent but not an array, or a node with notype, keeps returning a validation error - All four renderers (React, Vue, Angular classic, Angular semantic) are covered — they share the validator, so confirm none carries its own duplicate guard
- Unit test using the exact CITGO shape (
"content":[]at positions 5, 6 and 8 of a doc) asserts the full body renders - No console error is emitted for the empty-node case
dotCMS Version
main @ 667fc831ee (2026-09-17). Affects every consumer of the Angular / React / Vue SDK renderers regardless of FEATURE_FLAG_NEW_BLOCK_EDITOR — this is delivery-side, not editor-side.
Severity
High - Major functionality broken
Links
- Freshdesk ticket #38931 — CITGO, 461 published articles with blank bodies. This issue resolves that ticket's root cause; the customer currently runs a
stripEmptyBlockspatch in their own Next.js code as a workaround. - Parent / umbrella: https://github.com/dotCMS/core/issues/37601 (Defect E)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in core-web/libs/sdk/uve/src/lib/editor/internal.ts at isValidBlocks, then inspect the shared validation path used by the React, Vue, Angular classic, and Angular semantic renderers. Use the reproduction JSON, including nested empty nodes and the CITGO positions 5, 6, and 8, as the test fixture. Done means empty nodes render as empty elements, siblings and ancestors still render, malformed input still errors, and no console error is emitted.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, react, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100