diff --git a/docs/src/demos/Api/Schema/index.vue b/docs/src/demos/Api/Schema/index.vue
new file mode 100644
index 00000000..e9f6b1d9
--- /dev/null
+++ b/docs/src/demos/Api/Schema/index.vue
@@ -0,0 +1,38 @@
+
+ {{ this.html }}
+
+
+
\ No newline at end of file
diff --git a/docs/src/docPages/api/schema.md b/docs/src/docPages/api/schema.md
index 5776e9c1..b15ae0d7 100644
--- a/docs/src/docPages/api/schema.md
+++ b/docs/src/docPages/api/schema.md
@@ -1,5 +1,19 @@
# Schema
+## Get the ProseMirror schema without initializing the editor
+
+```js
+import { getSchema } from '@tiptap/core'
+
+const schema = getSchema(extensions)
+```
+
+## Generate HTML from ProseMirror JSON without initializing the editor
+
+
+
+## Old Content
+
:::warning Out of date
This content is written for tiptap 1 and needs an update.
:::
diff --git a/packages/core/index.ts b/packages/core/index.ts
index f82559e8..7571b28c 100644
--- a/packages/core/index.ts
+++ b/packages/core/index.ts
@@ -8,4 +8,7 @@ export { default as Node } from './src/Node'
export { default as Mark } from './src/Mark'
export { default as markInputRule } from './src/inputRules/markInputRule'
-export { default as markPasteRule } from './src/pasteRules/markPasteRule'
\ No newline at end of file
+export { default as markPasteRule } from './src/pasteRules/markPasteRule'
+
+export { default as getSchema } from './src/utils/getSchema'
+export { default as generateHtml } from './src/utils/generateHtml'
\ No newline at end of file
diff --git a/packages/core/src/utils/generateHtml.ts b/packages/core/src/utils/generateHtml.ts
new file mode 100644
index 00000000..ae70cbbf
--- /dev/null
+++ b/packages/core/src/utils/generateHtml.ts
@@ -0,0 +1,21 @@
+import Extension from '../Extension'
+import Node from '../Node'
+import Mark from '../Mark'
+import getSchema from './getSchema'
+import { Node as ProseMirrorNode, DOMSerializer } from "prosemirror-model"
+
+export default function generateHtml(doc: object, extensions: (Extension | Node | Mark)[]): string {
+ const schema = getSchema(extensions)
+
+ let contentNode = ProseMirrorNode.fromJSON(schema, doc)
+ let temporaryDocument = document.implementation.createHTMLDocument()
+ const div = temporaryDocument.createElement('div')
+
+ const fragment = DOMSerializer
+ .fromSchema(schema)
+ .serializeFragment(contentNode.content)
+
+ div.appendChild(fragment)
+
+ return div.innerHTML
+}
diff --git a/packages/core/src/utils/getSchema.ts b/packages/core/src/utils/getSchema.ts
new file mode 100644
index 00000000..ec8bae1c
--- /dev/null
+++ b/packages/core/src/utils/getSchema.ts
@@ -0,0 +1,35 @@
+import Extension from '../Extension'
+import Node from '../Node'
+import Mark from '../Mark'
+import { Schema } from 'prosemirror-model'
+import collect from 'collect.js'
+
+export default function getSchema(extensions: (Extension | Node | Mark)[]): Schema {
+ return new Schema({
+ topNode: getTopNodeFromExtensions(extensions),
+ nodes: getNodesFromExtensions(extensions),
+ marks: getMarksFromExtensions(extensions),
+ })
+}
+
+function getNodesFromExtensions(extensions: (Extension | Node | Mark)[]): any {
+ return collect(extensions)
+ .where('extensionType', 'node')
+ .mapWithKeys((extension: Node) => [extension.name, extension.schema()])
+ .all()
+}
+
+function getTopNodeFromExtensions(extensions: (Extension | Node | Mark)[]): any {
+ const topNode = collect(extensions).firstWhere('topNode', true)
+
+ if (topNode) {
+ return topNode.name
+ }
+}
+
+function getMarksFromExtensions(extensions: (Extension | Node | Mark)[]): any {
+ return collect(extensions)
+ .where('extensionType', 'mark')
+ .mapWithKeys((extension: Mark) => [extension.name, extension.schema()])
+ .all()
+}