Skip to content

fix(core): use dynamic propSchema when extending default block specs#2882

Open
nperez0111 wants to merge 1 commit into
mainfrom
feat/dynamic-block-prop-schema
Open

fix(core): use dynamic propSchema when extending default block specs#2882
nperez0111 wants to merge 1 commit into
mainfrom
feat/dynamic-block-prop-schema

Conversation

@nperez0111

@nperez0111 nperez0111 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a crash when extending default block specs with additional props by spreading the spec and modifying config.propSchema. This pattern was recommended in #1840 but didn't work at runtime.

Rationale

Users need to attach metadata to default blocks (paragraphs, headings, etc.) without creating fully custom block types. The recommended pattern is:

const paragraph = createParagraphBlockSpec();
const schema = BlockNoteSchema.create({
  blockSpecs: {
    ...defaultBlockSpecs,
    paragraph: {
      ...paragraph,
      config: {
        ...paragraph.config,
        propSchema: {
          ...paragraph.config.propSchema,
          metadata: { default: "" },
        },
      },
    },
  },
});

This crashed with TypeError: Cannot read properties of undefined (reading 'default') because createBlockSpec captured blockConfig.propSchema in closures at factory time. Spreading the spec creates a new config object, but the render/toExternalHTML closures still referenced the original propSchema.

Changes

  • createSpec.ts: addNodeAndExtensionsToSpec now passes propSchema: blockConfig.propSchema through the this call context in all .call() invocations. The closures in createBlockSpec prefer this dynamic value (this.propSchema ?? blockConfig.propSchema) over the captured one.
  • types.ts: Added propSchema?: TProps to the this parameter types of render and toExternalHTML on BlockImplementation.
  • internal.ts (blocks + inline content): Added spec?.default guard in wrapInBlockStructure and addInlineContentAttributes as a safety net.
  • extendBlockSpec.test.ts: 8 tests covering the extend-by-spreading pattern: editor creation, prop updates, HTML export, default-value omission, selective extension, HTML round-trip, and mounted DOM rendering.

Impact

No breaking changes. The propSchema addition to the this type is optional and only affects internal call context — user-authored render/toExternalHTML implementations are unaffected.

Testing

  • All 8 new tests pass, exercising the exact pattern from the issue
  • All existing tests pass (458 in core, 873 across the full suite)
  • Build and lint checks pass

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

Fixes #1840

Summary by CodeRabbit

  • New Features

    • Block specs can now carry custom property schema data through rendering and HTML export, enabling richer block customization.
    • Extended block properties are now preserved when editing, exporting, and reloading content.
  • Bug Fixes

    • Improved handling for missing property definitions to avoid runtime errors during block and inline HTML generation.
    • Default-valued properties are now omitted from exported HTML, while non-default values are retained consistently.

createBlockSpec captured blockConfig.propSchema in closures at factory
time. When users spread a spec and add new props to the config, the
closures still referenced the original propSchema, causing a crash in
wrapInBlockStructure when iterating block.props.

Thread propSchema through the call context from addNodeAndExtensionsToSpec
(which always has the current config) so the closures prefer the dynamic
value. Add a spec?.default guard as a safety net for both blocks and
inline content.

Fixes #1840
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview Jul 3, 2026 12:57pm
blocknote-website Ready Ready Preview Jul 3, 2026 12:57pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds propSchema to the render/toExternalHTML this-context typing and runtime calls in block spec creation, prefers this.propSchema over blockConfig.propSchema in wrapInBlockStructure calls, and applies optional chaining for default-value lookups to avoid errors on missing prop specs. New tests cover extending default block specs with a metadata prop.

Changes

propSchema context propagation and extension tests

Layer / File(s) Summary
BlockImplementation this-context type updates
packages/core/src/schema/blocks/types.ts
Adds propSchema: TProps to the this context typing for render and toExternalHTML callbacks.
Runtime propSchema passing in render/toExternalHTML
packages/core/src/schema/blocks/createSpec.ts
Passes blockConfig.propSchema into addNodeView, DOM render, and toExternalHTML fallback calls; wrapInBlockStructure calls now prefer this.propSchema ?? blockConfig.propSchema.
Defensive handling of missing prop specs
packages/core/src/schema/blocks/internal.ts, packages/core/src/schema/inlineContent/internal.ts
Replaces direct spec.default access with optional chaining (spec?.default) to avoid errors when a prop spec is undefined.
Tests for extending block specs with metadata prop
packages/core/src/schema/blocks/extendBlockSpec.test.ts
New test suite verifies extending block propSchema with a metadata field, prop updates, HTML export/parse round-trip, selective block extension, and DOM mounting.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Editor
  participant CreateSpec as createBlockSpec
  participant Impl as blockImplementation.render
  participant Wrap as wrapInBlockStructure
  participant DOM

  Editor->>CreateSpec: render/toExternalHTML(block, editor)
  CreateSpec->>Impl: call(this={propSchema, blockContentDOMAttributes, props})
  Impl->>Wrap: build(this.propSchema ?? blockConfig.propSchema)
  Wrap-->>DOM: emit data-* attributes for non-default prop values
Loading

Poem

A little prop named metadata hops in,
Through render's this, it finds its way in.
No more crashes when specs go missing,
Just optional chains, softly listening.
Hop hop hooray, the tests all pass —
This bunny's proud of this propSchema grass! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The changes fix block-spec propSchema extension, but they don't clearly implement metadata support for inline contents required by #1840. Add explicit metadata support for inline contents and verify metadata round-trips in the AST for both blocks and inline nodes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title precisely matches the main fix: dynamic propSchema handling for extended default block specs.
Description check ✅ Passed The PR description covers summary, rationale, changes, impact, testing, checklist, and notes, with only the optional screenshots section omitted.
Out of Scope Changes check ✅ Passed The added guards and tests are directly related to the propSchema fix and issue #1840, with no obvious unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dynamic-block-prop-schema

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/@blocknote/ariakit@2882

@blocknote/code-block

npm i https://pkg.pr.new/@blocknote/code-block@2882

@blocknote/core

npm i https://pkg.pr.new/@blocknote/core@2882

@blocknote/mantine

npm i https://pkg.pr.new/@blocknote/mantine@2882

@blocknote/react

npm i https://pkg.pr.new/@blocknote/react@2882

@blocknote/server-util

npm i https://pkg.pr.new/@blocknote/server-util@2882

@blocknote/shadcn

npm i https://pkg.pr.new/@blocknote/shadcn@2882

@blocknote/xl-ai

npm i https://pkg.pr.new/@blocknote/xl-ai@2882

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/@blocknote/xl-docx-exporter@2882

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/@blocknote/xl-email-exporter@2882

@blocknote/xl-multi-column

npm i https://pkg.pr.new/@blocknote/xl-multi-column@2882

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/@blocknote/xl-odt-exporter@2882

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/@blocknote/xl-pdf-exporter@2882

commit: 7764b48

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://TypeCellOS.github.io/BlockNote/pr-preview/pr-2882/

Built to branch gh-pages at 2026-07-03 13:01 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/schema/blocks/createSpec.ts (1)

402-421: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Forward propSchema into the raw callbacks. blockImplementation.render.call(...) and blockImplementation.toExternalHTML?.call(...) both omit this.propSchema, so custom block implementations can’t read it even though the callback type exposes it. Pass this.propSchema in both call contexts, not just to wrapInBlockStructure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/schema/blocks/createSpec.ts` around lines 402 - 421, The
raw callback contexts for createSpec block rendering are missing propSchema, so
custom implementations cannot access it. Update both
blockImplementation.render.call and blockImplementation.toExternalHTML?.call to
include this.propSchema alongside blockContentDOMAttributes, using the existing
createSpec callback setup so the propSchema is available wherever the callback
type expects it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/core/src/schema/blocks/createSpec.ts`:
- Around line 402-421: The raw callback contexts for createSpec block rendering
are missing propSchema, so custom implementations cannot access it. Update both
blockImplementation.render.call and blockImplementation.toExternalHTML?.call to
include this.propSchema alongside blockContentDOMAttributes, using the existing
createSpec callback setup so the propSchema is available wherever the callback
type expects it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 098dd3c6-0702-4bf7-845b-9c51de969172

📥 Commits

Reviewing files that changed from the base of the PR and between 3094559 and 7764b48.

📒 Files selected for processing (5)
  • packages/core/src/schema/blocks/createSpec.ts
  • packages/core/src/schema/blocks/extendBlockSpec.test.ts
  • packages/core/src/schema/blocks/internal.ts
  • packages/core/src/schema/blocks/types.ts
  • packages/core/src/schema/inlineContent/internal.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attach metadata to blocks and inline contents

1 participant