diff --git a/examples/06-custom-schema/08-custom-table-props/.bnexample.json b/examples/06-custom-schema/08-custom-table-props/.bnexample.json
new file mode 100644
index 0000000000..79b7f88d51
--- /dev/null
+++ b/examples/06-custom-schema/08-custom-table-props/.bnexample.json
@@ -0,0 +1,11 @@
+{
+ "playground": true,
+ "docs": true,
+ "author": "Richard Misiak",
+ "tags": [
+ "Intermediate",
+ "UI Components",
+ "Tables",
+ "Appearance & Styling"
+ ]
+}
\ No newline at end of file
diff --git a/examples/06-custom-schema/08-custom-table-props/README.md b/examples/06-custom-schema/08-custom-table-props/README.md
new file mode 100644
index 0000000000..5121e71db2
--- /dev/null
+++ b/examples/06-custom-schema/08-custom-table-props/README.md
@@ -0,0 +1,8 @@
+# Custom table props
+
+This example demonstrates overriding the schema for tables to allow for custom props to be used.
+
+**Relevant Docs:**
+
+- [Tables](/docs/features/custom-schemas)
+- [Custom Schemas](/docs/features/custom-schemas)
diff --git a/examples/06-custom-schema/08-custom-table-props/index.html b/examples/06-custom-schema/08-custom-table-props/index.html
new file mode 100644
index 0000000000..19e0c72013
--- /dev/null
+++ b/examples/06-custom-schema/08-custom-table-props/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+ Custom table props
+
+
+
+
+
+
+
diff --git a/examples/06-custom-schema/08-custom-table-props/main.tsx b/examples/06-custom-schema/08-custom-table-props/main.tsx
new file mode 100644
index 0000000000..677c7f7eed
--- /dev/null
+++ b/examples/06-custom-schema/08-custom-table-props/main.tsx
@@ -0,0 +1,11 @@
+// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
+import React from "react";
+import { createRoot } from "react-dom/client";
+import App from "./src/App.jsx";
+
+const root = createRoot(document.getElementById("root")!);
+root.render(
+
+
+
+);
diff --git a/examples/06-custom-schema/08-custom-table-props/package.json b/examples/06-custom-schema/08-custom-table-props/package.json
new file mode 100644
index 0000000000..7098076d26
--- /dev/null
+++ b/examples/06-custom-schema/08-custom-table-props/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "@blocknote/example-custom-schema-custom-table-props",
+ "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
+ "type": "module",
+ "private": true,
+ "version": "0.12.4",
+ "scripts": {
+ "start": "vite",
+ "dev": "vite",
+ "build:prod": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@blocknote/ariakit": "latest",
+ "@blocknote/core": "latest",
+ "@blocknote/mantine": "latest",
+ "@blocknote/react": "latest",
+ "@blocknote/shadcn": "latest",
+ "@mantine/core": "^8.3.11",
+ "@mantine/hooks": "^8.3.11",
+ "@mantine/utils": "^6.0.22",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3"
+ },
+ "devDependencies": {
+ "@types/react": "^19.2.3",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^4.7.0",
+ "vite": "^5.4.20"
+ }
+}
\ No newline at end of file
diff --git a/examples/06-custom-schema/08-custom-table-props/src/App.tsx b/examples/06-custom-schema/08-custom-table-props/src/App.tsx
new file mode 100644
index 0000000000..06c28ac5d8
--- /dev/null
+++ b/examples/06-custom-schema/08-custom-table-props/src/App.tsx
@@ -0,0 +1,147 @@
+import "@blocknote/core/fonts/inter.css";
+import "@blocknote/mantine/style.css";
+import { BlockNoteView } from "@blocknote/mantine";
+import { useCreateBlockNote } from "@blocknote/react";
+import {
+ BlockNoteSchema,
+ defaultBlockSpecs,
+ createTableBlockSpec,
+} from "@blocknote/core";
+import { useState } from "react";
+
+const customTable = createTableBlockSpec({
+ customProp: {
+ default: "",
+ },
+});
+
+const schema = BlockNoteSchema.create({
+ blockSpecs: {
+ ...defaultBlockSpecs,
+ table: customTable,
+ },
+});
+
+export default function App() {
+ const editor = useCreateBlockNote({
+ schema,
+ initialContent: [
+ {
+ type: "table",
+ props: {
+ textColor: "default",
+ customProp: "custom",
+ },
+ content: {
+ type: "tableContent",
+
+ headerRows: 1,
+ rows: [
+ {
+ cells: [
+ {
+ type: "tableCell",
+ content: [
+ {
+ type: "text",
+ text: "Foo",
+ styles: {},
+ },
+ ],
+ },
+ {
+ type: "tableCell",
+ content: [
+ {
+ type: "text",
+ text: "Bar",
+ styles: {},
+ },
+ ],
+ },
+ ],
+ },
+ {
+ cells: [
+ {
+ type: "tableCell",
+ content: [
+ {
+ type: "text",
+ text: "value",
+ styles: {},
+ },
+ ],
+ },
+ {
+ type: "tableCell",
+ content: [
+ {
+ type: "text",
+ text: "value",
+ styles: {},
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ children: [],
+ },
+ {
+ type: "paragraph",
+ content: [
+ {
+ type: "text",
+ text: "This is a table that has a custom prop `customProp` defined. Click on the button to change it to a random value. The JSON dump of the document is shown.",
+ },
+ ],
+ },
+ ],
+ });
+
+ const [doc, setDoc] = useState(editor.document);
+
+ editor.onChange((editor) => {
+ setDoc(editor.document); // re-render on changes
+ });
+
+ return (
+
+
+
+
+
+
+
Document:
+
+ {JSON.stringify(editor.document, null, 2)}
+
+
+
+ );
+}
diff --git a/examples/06-custom-schema/08-custom-table-props/tsconfig.json b/examples/06-custom-schema/08-custom-table-props/tsconfig.json
new file mode 100644
index 0000000000..dbe3e6f62d
--- /dev/null
+++ b/examples/06-custom-schema/08-custom-table-props/tsconfig.json
@@ -0,0 +1,36 @@
+{
+ "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": [
+ "DOM",
+ "DOM.Iterable",
+ "ESNext"
+ ],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "composite": true
+ },
+ "include": [
+ "."
+ ],
+ "__ADD_FOR_LOCAL_DEV_references": [
+ {
+ "path": "../../../packages/core/"
+ },
+ {
+ "path": "../../../packages/react/"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/06-custom-schema/08-custom-table-props/vite.config.ts b/examples/06-custom-schema/08-custom-table-props/vite.config.ts
new file mode 100644
index 0000000000..f62ab20bc2
--- /dev/null
+++ b/examples/06-custom-schema/08-custom-table-props/vite.config.ts
@@ -0,0 +1,32 @@
+// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
+import react from "@vitejs/plugin-react";
+import * as fs from "fs";
+import * as path from "path";
+import { defineConfig } from "vite";
+// import eslintPlugin from "vite-plugin-eslint";
+// https://vitejs.dev/config/
+export default defineConfig((conf) => ({
+ plugins: [react()],
+ optimizeDeps: {},
+ build: {
+ sourcemap: true,
+ },
+ resolve: {
+ alias:
+ conf.command === "build" ||
+ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src"))
+ ? {}
+ : ({
+ // Comment out the lines below to load a built version of blocknote
+ // or, keep as is to load live from sources with live reload working
+ "@blocknote/core": path.resolve(
+ __dirname,
+ "../../packages/core/src/"
+ ),
+ "@blocknote/react": path.resolve(
+ __dirname,
+ "../../packages/react/src/"
+ ),
+ } as any),
+ },
+}));
diff --git a/packages/core/src/api/nodeConversions/nodeToBlock.ts b/packages/core/src/api/nodeConversions/nodeToBlock.ts
index 5048f91a2b..8d04ab95d1 100644
--- a/packages/core/src/api/nodeConversions/nodeToBlock.ts
+++ b/packages/core/src/api/nodeConversions/nodeToBlock.ts
@@ -425,11 +425,25 @@ export function nodeToBlock<
throw Error("Block is of an unrecognized type: " + blockInfo.blockNoteType);
}
+ const blockConfig = blockSchema[blockInfo.blockNoteType];
+
const props: any = {};
- for (const [attr, value] of Object.entries({
+
+ // Collect attributes from node, blockContent, and table content node (for tables)
+ const attrsToCheck = {
...node.attrs,
...(blockInfo.isBlockContainer ? blockInfo.blockContent.node.attrs : {}),
- })) {
+ };
+
+ // For table blocks, also check the table content node for custom props
+ if (blockConfig.content === "table" && blockInfo.isBlockContainer) {
+ const tableContentNode = blockInfo.blockContent.node;
+ if (tableContentNode && tableContentNode.attrs) {
+ Object.assign(attrsToCheck, tableContentNode.attrs);
+ }
+ }
+
+ for (const [attr, value] of Object.entries(attrsToCheck)) {
const propSchema = blockSpec.propSchema;
if (
@@ -440,8 +454,6 @@ export function nodeToBlock<
}
}
- const blockConfig = blockSchema[blockInfo.blockNoteType];
-
const children: Block[] = [];
blockInfo.childContainer?.node.forEach((child) => {
children.push(
diff --git a/packages/core/src/blocks/Table/block.ts b/packages/core/src/blocks/Table/block.ts
index 9a23d227aa..29afabd60f 100644
--- a/packages/core/src/blocks/Table/block.ts
+++ b/packages/core/src/blocks/Table/block.ts
@@ -9,6 +9,8 @@ import {
createBlockSpecFromTiptapNode,
TableContent,
} from "../../schema/index.js";
+import { propsToAttributes } from "../../schema/blocks/internal.js";
+import { PropSchema } from "../../schema/propTypes.js";
import { mergeCSSClasses } from "../../util/browser.js";
import { createDefaultBlockDOMOutputSpec } from "../defaultBlockHelpers.js";
import { defaultProps } from "../defaultProps.js";
@@ -381,10 +383,23 @@ export type TableBlockConfig = BlockConfig<
"table"
>;
-export const createTableBlockSpec = () =>
- createBlockSpecFromTiptapNode(
- { node: TiptapTableNode, type: "table", content: "table" },
- tablePropSchema,
+export const createTableBlockSpec = (additionalPropSchema?: PropSchema) => {
+ // Merge the base table prop schema with any additional props
+ const mergedPropSchema = {
+ ...tablePropSchema,
+ ...(additionalPropSchema || {}),
+ };
+
+ // Extend the TiptapTableNode to add attributes based on the merged prop schema
+ const extendedTableNode = TiptapTableNode.extend({
+ addAttributes() {
+ return propsToAttributes(mergedPropSchema);
+ },
+ });
+
+ return createBlockSpecFromTiptapNode(
+ { node: extendedTableNode, type: "table", content: "table" },
+ mergedPropSchema,
[
createExtension({
key: "table-extensions",
@@ -452,6 +467,7 @@ export const createTableBlockSpec = () =>
}),
],
);
+};
// We need to declare this here because we aren't using the table extensions from tiptap, so the types are not automatically inferred.
declare module "@tiptap/core" {
diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx
index d73534652c..310d6f788c 100644
--- a/playground/src/examples.gen.tsx
+++ b/playground/src/examples.gen.tsx
@@ -1373,6 +1373,28 @@
},
"readme": "This example shows how you can configure the editor's default blocks. Specifically, heading blocks are made to only support levels 1-3, and cannot be toggleable.\n\n**Relevant Docs:**\n\n- [Editor Setup](/docs/getting-started/editor-setup)\n- [Default Schema](/docs/foundations/schemas)\n- [Custom Schemas](/docs/features/custom-schemas)"
},
+ {
+ "projectSlug": "custom-table-props",
+ "fullSlug": "custom-schema/custom-table-props",
+ "pathFromRoot": "examples/06-custom-schema/08-custom-table-props",
+ "config": {
+ "playground": true,
+ "docs": true,
+ "author": "Richard Misiak",
+ "tags": [
+ "Intermediate",
+ "UI Components",
+ "Tables",
+ "Appearance & Styling"
+ ]
+ },
+ "title": "Custom table props",
+ "group": {
+ "pathFromRoot": "examples/06-custom-schema",
+ "slug": "custom-schema"
+ },
+ "readme": "This example demonstrates overriding the schema for tables to allow for custom props to be used.\n\n**Relevant Docs:**\n\n- [Tables](/docs/features/custom-schemas)\n- [Custom Schemas](/docs/features/custom-schemas)"
+ },
{
"projectSlug": "draggable-inline-content",
"fullSlug": "custom-schema/draggable-inline-content",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 461388700a..f8fc676ff5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3304,6 +3304,52 @@ importers:
specifier: ^5.4.20
version: 5.4.20(@types/node@25.2.1)(lightningcss@1.30.2)(terser@5.46.0)
+ examples/06-custom-schema/08-custom-table-props:
+ dependencies:
+ '@blocknote/ariakit':
+ specifier: latest
+ version: link:../../../packages/ariakit
+ '@blocknote/core':
+ specifier: latest
+ version: link:../../../packages/core
+ '@blocknote/mantine':
+ specifier: latest
+ version: link:../../../packages/mantine
+ '@blocknote/react':
+ specifier: latest
+ version: link:../../../packages/react
+ '@blocknote/shadcn':
+ specifier: latest
+ version: link:../../../packages/shadcn
+ '@mantine/core':
+ specifier: ^8.3.11
+ version: 8.3.11(@mantine/hooks@8.3.11(react@19.2.3))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@mantine/hooks':
+ specifier: ^8.3.11
+ version: 8.3.11(react@19.2.3)
+ '@mantine/utils':
+ specifier: ^6.0.22
+ version: 6.0.22(react@19.2.3)
+ react:
+ specifier: ^19.2.3
+ version: 19.2.3
+ react-dom:
+ specifier: ^19.2.3
+ version: 19.2.3(react@19.2.3)
+ devDependencies:
+ '@types/react':
+ specifier: ^19.2.3
+ version: 19.2.8
+ '@types/react-dom':
+ specifier: ^19.2.3
+ version: 19.2.3(@types/react@19.2.8)
+ '@vitejs/plugin-react':
+ specifier: ^4.7.0
+ version: 4.7.0(vite@5.4.20(@types/node@25.2.1)(lightningcss@1.30.2)(terser@5.46.0))
+ vite:
+ specifier: ^5.4.20
+ version: 5.4.20(@types/node@25.2.1)(lightningcss@1.30.2)(terser@5.46.0)
+
examples/06-custom-schema/draggable-inline-content:
dependencies:
'@blocknote/ariakit':