Skip to content

Conversation

YousefED
Copy link
Collaborator

@YousefED YousefED commented Oct 13, 2025

Summary

This PR drops our custom schema for Block and Inline Content Props and uses Zod instead.

Rationale

  • Less "non-standard" code to maintain
  • Easier to explain in docs / to LLMs

Changes

Impact

Consumers using custom blocks would need to migrate

  • TODO: Determine if we want to accept old style props for backwards compatibility

Testing

Mostly by existing unit tests. TODO:

  • build of examples doesn't pass. couldn't figure out the build errors for withMultiColumn and withPageBreak examples. Playing around with BlockNoteSchema vs CustomBlockNoteSchema. Perhaps @nperez0111 can help here?
  • Manual testing + code review. @matthewlipski can you help with this (after build + tests pass), and specifically the file blocks and anywhere where block type guards are used
  • add tests / example for more complex schemas (e.g.: a prop with a nested object). @matthewlipski can you help?
  • check compatibility with yjs, does it break with Conversion doesn't check types yjs/y-prosemirror#116? (cc @nperez0111)

Screenshots/Video

N/A

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • Y: The documentation has been updated to reflect the new feature
  • Y: Review and add comments + followup work

Additional Notes

Future work:

  • Migrate style props as well?
  • Refactor File Blocks / File Props?
  • Remove / refactor "Partial" blocks as much as possible

Copy link

vercel bot commented Oct 13, 2025

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

Project Deployment Preview Updated (UTC)
blocknote Error Error Oct 13, 2025 5:03am
blocknote-website Error Error Oct 13, 2025 5:03am

Comment on lines +42 to +48
// TBD: this might not be fault proof, but it's also ugly to store prop=""..."" for strings
try {
const jsonValue = JSON.parse(value);
// it was a number / boolean / json object stored as attribute
return z.parse(spec, jsonValue);
} catch (e) {
// it might have been a string directly stored as attribute
Copy link
Contributor

@nperez0111 nperez0111 Oct 13, 2025

Choose a reason for hiding this comment

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

Comment on lines +26 to +27
const def =
spec instanceof z.$ZodDefault ? spec._zod.def.defaultValue : undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

I've seen this in a number of places, would prefer to make a util function for this, since it isn't obvious & dependent on zod's API not our own. I don't love reach into zod for this, but don't see another way to do this unless we provide defaults separately

"backgroundColor": "default",
"caption": "",
"name": "",
"previewWidth": undefined,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Note, when getting a block you now also get optional props as undefined. I think this is ok?

cc @nperez0111

Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems fine to me, probably better even.

}

try {
// TODO: props.level type should propagate correctly
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@nperez0111 I didn't get this to work with the way blocks are defined. Can you have a look at this or can we pair up?

const BC_NODE = editor.pmSchema.nodes["blockContainer"];

// set default props in case we were passed a partial block
// TODO: should be a nicer way for this / or move to caller
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fyi, I don't think these functions should take PartialBlocks. I want to review the PartialBlock concept later. My thought at this moment is that they should only be on the highest level, to make calling things like updateBlock / insertBlocks more convenient

import { Block } from "./defaultBlocks.js";
import { Selection } from "prosemirror-state";

// TODO: matthew review + tests
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@matthewlipski , you recently refactored this file I think. afaik there are no unit tests covering the type guards. Could you add these? I'm not sure if my implementation matches the scenarios you had in mind

You can also add them to the main branch implementation and then I can migrate them to validate they still work after this zod refactor

Copy link
Collaborator

Choose a reason for hiding this comment

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

Added tests in #2103

*/
comments?: {
schema?: BlockNoteSchema<any, any, any>;
schema?: CustomBlockNoteSchema<any, any, any>;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@nperez0111 please double check my changes in this file (not sure I correctly get the difference between CustomBlockNoteSchema and BlockNoteSchema)

>(
options?: Options,
): Options extends {
schema: CustomBlockNoteSchema<infer BSchema, infer ISchema, infer SSchema>;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@nperez0111 another schema change

@matthewlipski
Copy link
Collaborator

add tests / example for more complex schemas (e.g.: a prop with a nested object). @matthewlipski can you help?

@YousefED so with this PR are we introducing the ability to have objects as props alongside strings/numbers/booleans? Because I remember this was something we considered when first making the custom blocks API, but skipped it mainly because objects need to be set on HTML attributes with JSON.stringify so they can then be parsed properly. IIRC we were concerned about the stringified objects making the DOM super messy, and also issues if you have functions since they can't be stringified. Do we have a solution for this?

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.

3 participants