# Getting a hold of the \`document\` used by ProseMirror from \`toDOM()\`

**URL:** https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392
**Category:** Uncategorized
**Created:** [May 7, 2025, 12:41pm UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392 "2025-05-07T12:41:38Z")
**Posts on this page:** 12
**Page:** 1

<div class="post-metadata">

### Author: ![strogonoff](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/strogonoff/32/4297_2.png) [@strogonoff](https://discuss.prosemirror.net/u/strogonoff)
#### Post date: [May 7, 2025, 12:41pm UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/1 "2025-05-07T12:41:38Z")

</div>

There are two givens:

1. ProseMirror takes a `document` parameter to `serializeNode()`. (It seems to be optional, but I believe it’s required in SSR.)
2. ProseMirror allows `toDOM()` to return a DOM node (a result of `document.createElement()`).

My _assumption_ is that somewhere in the calling context of `toDOM()` a `document` is always present—whether it was passed explicitly in (1) or be it ProseMirror using a global `document` because it runs in a browser.

Thus the question: wouldn’t it make sense to wire it up so that `toDOM()`’s body can get a hold of whichever `document` ProseMirror obtained during DOM serialization (or, more precisely, the `createElement()` that comes with it)? It seems suboptimal that if `toDOM()` wants to `createElement()` it has to come up with its own way of accessing a `document` that was already passed to PM.

Example:

During SSR my build invokes `serializeNode()` outside of browser environment. It rolls with (1) and passes it a document stub from JSDOM. JSDOM is a beast I don’t want to lug around at all, but it works (I recall trying some alternatives, but they did not work for PM).

Simultaneously, there are a couple of times where my schema implementation outputs a DOM element in `toDOM()`. To make those occasions work, I have to either A) (in SSR) patch through a global `document` where I define schema spec, _or_ B) find a creative way of creating schemas on the fly from functions that I pass an explicit `document` (either JSDOM in SSR, or a global in browser).

I currently do (A), but it feels wrong.

What I have read:

- [https://discuss.prosemirror.net/t/how-to-lookup-document-from-node/800:](https://discuss.prosemirror.net/t/how-to-lookup-document-from-node/800:) I don’t know if it’s quite the same, since my request could be addressed without adding access to `document` via `Node`.

Relevant, but not helping:

- [ProseMirror and Server Side Rendering](https://discuss.prosemirror.net/t/prosemirror-and-server-side-rendering/5419)

---

<div class="post-metadata">

### Author: ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)
#### Post date: [May 7, 2025, 1:40pm UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/2 "2025-05-07T13:40:14Z")

</div>

On the one hand, yes, having this would be a little cleaner. On the other hand, this is a browser library, and running it outside of that context is not a very important use case. I suppose you’re already running some of your tests in the browser. If I were starting from scratch I’d probably also run the prosemirror-model tests in a headless browser (as you already mention, JSDOM isn’t perfect). As such, I don’t really feel comfortable adding complexity to the library for this specific situation, and as you found, it’s not hard to work around this.

---

<div class="post-metadata">

### Author: ![strogonoff](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/strogonoff/32/4297_2.png) [@strogonoff](https://discuss.prosemirror.net/u/strogonoff)
#### Post date: [May 7, 2025, 2:24pm UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/3 "2025-05-07T14:24:38Z")

</div>

> JSDOM is not perfect

Actually, it seems to be pretty close in terms of feature completeness. The problem is size. If anything, “what is the minimum I need to give ProseMirror for it to work” is the big question. Surely it’s not using the entire API surface of browser’s `Document`… If document object(s) need to be passed around while generating the site, it would at least help to pass strictly what’s needed.

I can see how it would make PM itself more testable, too (in addition to helping some users not lug around a massive dependency, if they need to build in Node or say in browser’s worker environment that has no access to DOM).

If one was to contribute at least on the documentation front (maybe even on narrowing down typing signatures) what’d be the best way to go about it?

---

<div class="post-metadata">

### Author: ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)
#### Post date: [May 7, 2025, 9:43pm UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/4 "2025-05-07T21:43:16Z")

</div>

> [@strogonoff](#):
>
> if they need to build in Node or say in browser’s worker environment that has no access to DOM).

That really doesn’t seem like a reasonable thing to do. You can create Node’s just fine in a DOM-less environment. But you cannot render them. And since you cannot display them either, that seems like it isn’t much of a problem.

---

<div class="post-metadata">

### Author: ![strogonoff](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/strogonoff/32/4297_2.png) [@strogonoff](https://discuss.prosemirror.net/u/strogonoff)
#### Post date: [May 8, 2025, 6:22am UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/5 "2025-05-08T06:22:23Z")

</div>

> You can create Node’s just fine in a DOM-less environment. But you cannot render them. And since you cannot display them either, that seems like it isn’t much of a problem.

The context is rendering static HTML deliverables. PM is called “off the DOM” to convert its JSON content tree to a string. (There are many benefits to using PM here, including stable document appearance to human readers and editors, single DRY code path for obtaining content representation while maintaining fast initial page opening times, graceful degradation without JS [sans node views], etc.)

This part seemed obvious enough that I didn’t explain it in the original post—I must be suffering from a bit of tunnel vision.

If I’m missing some way to accomplish this without a `document` global and without having to pass `document` to PM, I’d love to know what it is…

---

<div class="post-metadata">

### Author: ![strogonoff](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/strogonoff/32/4297_2.png) [@strogonoff](https://discuss.prosemirror.net/u/strogonoff)
#### Post date: [May 9, 2025, 10:32am UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/6 "2025-05-09T10:32:16Z")

</div>

1. Getting a hold of DOM serializer’s `document` in `toDOM()` seems to involve:

2. Regarding what `document` entails, for ProseMirror’s purposes (meaning both what callers must pass to `serializeNode()` and what it’d pass to spec’s `toDOM()` per (1) above) it seems to be a tiny subset of `Document` with only the following:

Notes;

- The above changes are backward compatible and don’t affect existing users.

- The changes are independent of each other, though I would say it’d be better to narrow down `Document` first, if it’s to be added to `toDOM()`’s signature.

- In fact, instead of giving `toDOM()` access to `document`, even a narrowed-down version, it could be worth passing _just_ an element constructor function:

---

<div class="post-metadata">

### Author: ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)
#### Post date: [May 10, 2025, 5:49am UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/7 "2025-05-10T05:49:09Z")

</div>

> [@strogonoff](#):
>
> The context is rendering static HTML deliverables.

Aren’t you much better served with the array-style [`DOMOutputSpec`](https://prosemirror.net/docs/ref/#model.DOMOutputSpec) for this situation, since almost anything you can do on a DOM node that you cannot do in such an array will be lost when serializing to HTML text again?

Running DOM creation code outside of the browser just seems like a messy approach. It blurs the lines of what capabilities a piece of code has—authors could be tempted, for example, to try and hook into other client-side APIs in this code.

This situation is kind of on me, for designing the output type of this method this way. But I don’t want to add further oddness to the interface for this use case (I really hope pulling in JSDOM for server-side rendering isn’t something a lot of people are doing). Given that, if you’re committed to that approach, it is just as easy to use your own mechanism to smuggle a reference to a `Document` instance into your methods, I don’t want to add extra parameters to the library for this.

---

<div class="post-metadata">

### Author: ![strogonoff](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/strogonoff/32/4297_2.png) [@strogonoff](https://discuss.prosemirror.net/u/strogonoff)
#### Post date: [May 15, 2025, 8:27am UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/8 "2025-05-15T08:27:10Z")

</div>

> Aren’t you much better served with the array-style [`DOMOutputSpec`](https://prosemirror.net/docs/ref/#model.DOMOutputSpec) for this situation, since almost anything you can do on a DOM node that you cannot do in such an array will be lost when serializing to HTML text again?

@marijn Regarding `toDOM()` specifically, I do use arrays as much as feasible, and aim to return arrays everywhere eventually. However, I’m not sure what you mean about lost data. In this approach, a ProseMirror content structure is maintained separately, and the HTML string is obtained only for the purposes of pre-rendering a nice legible static page.

(During editing, once editor JS runs, the full ProseMirror is initialized with `nodeViews` and everything. Perhaps I’ll share a demo of how it works a bit later.)

> Running DOM creation code outside of the browser just seems like a messy approach.

I distinguish between the two:

1. Returning a DOM node from `toDOM()` does feel messy, though may be inevitable and is officially supported by ProseMirror.

2. Obtaining a static HTML from a ProseMirror structure outside of the browser serves what strikes me personally as the opposite of a messy approach. Wouldn’t you agree that the alternative with two distinct code paths for obtaining content’s HTML representation—one for editing with ProseMirror, one for serving to static readers—would be the messy one in this regard?

If (2) requires running DOM creation code, because that’s how ProseMirror renders content, it’s not such a big deal—if we narrow down what DOM-related functionality is needed, it can be a tiny subset provided by a small library.

> This situation is kind of on me, for designing the output type of this method this way.

I don’t think it’s a big deal, it serves the purpose, and in all regards ProseMirror is better suited for the clean approach I have in mind than any alternative I have seen so far. Headless DOM is a solved problem, though implementations can be of varying heft and feature completeness.

> I don’t want to add further oddness to the interface for this use case

I’d be the first against oddness.

> (I really hope pulling in JSDOM for server-side rendering isn’t something a lot of people are doing)

Between the three evils—1) having two code paths for generating content representation (one with PM and one without), or 2) having a web page that renders content only through PM, but only works with JS, or 3) using PM for everything and pre-rendering with the help of a headless DOM implementation—the third one strikes me as the best option by far.

(To reiterate, I think pulling JSDOM can be avoided if it is made clear what PM needs from the DOM. I have an issue to “get rid of JSDOM” and once I narrow down the minimum needed by ProseMirror I’ll certainly do it.)

> Given that, if you’re committed to that approach, it is just as easy to use your own mechanism to smuggle a reference to a `Document` instance into your methods, I don’t want to add extra parameters to the library for this.

The item (2) in my [post above](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/6) does not add any parameters or change implementation, if anything it aims to _remove_ (strictly on type declaration level) the extraneous features of the ever-growing browser’s `Document` API that ProseMirror does not care about in the first place. This makes it easy for a hypothetical minimal DOM library to say “we implement just this and you can pass our `Document` to PM in DOM-less environments”.

(The item (1) in my post _does_ change the implementation slightly and adds an extra parameter in `toDOM()`. This item separate and is not required by item (2).)

---

<div class="post-metadata">

### Author: ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)
#### Post date: [May 15, 2025, 1:04pm UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/9 "2025-05-15T13:04:37Z")

</div>

> [@strogonoff](#):
>
> The item (2) in my [post above](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/6) does not add any parameters or change implementation

Doesn’t this still require the document or some wrapper to be passed to `toDOM` functions?

In any case, if you’re okay with a narrowed DOM interface, it seems that that would have no more power than the array return format. So I still very much prefer the solution where you port your schema to use that format, and don’t need `document` at all.

---

<div class="post-metadata">

### Author: ![strogonoff](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/strogonoff/32/4297_2.png) [@strogonoff](https://discuss.prosemirror.net/u/strogonoff)
#### Post date: [May 16, 2025, 4:42am UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/10 "2025-05-16T04:42:20Z")

</div>

> [@marijn](#):
>
> Doesn’t this still require the document or some wrapper to be passed to `toDOM` functions?

No, the narrowing is fully independent, `toDOM` functions would be on their own. `toDOM` fix would benefit from narrowing the `Document`, but not the other way around.

> [@marijn](#):
>
> In any case, if you’re okay with a narrowed DOM interface, it seems that that would have no more power than the array return format. So I still very much prefer the solution where you port your schema to use that format, and don’t need `document` at all.

There are two things somewhat related (they both concern DOM) but also fully independent from each other:

- Narrowing the type of `Document` given to ProseMirror when running DOM serializer—this is applicable regardless of whether one returns an array or not.
- Giving `toDOM` a handle on [a part of] the `Document` passed to ProseMirror’s serializer—I noted that you don’t want this, sure.

---

<div class="post-metadata">

### Author: ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)
#### Post date: [May 16, 2025, 8:51am UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/11 "2025-05-16T08:51:20Z")

</div>

> [@strogonoff](#):
>
> Narrowing the type of `Document` given to ProseMirror when running DOM serializer—this is applicable regardless of whether one returns an array or not.

I see what you mean but the result of these methods _is_ returned from the serializer methods, as type `DocumentFragment` or `Node`, and changing that to some narrowed type will break callers who are expecting an actual DOM node.

---

<div class="post-metadata">

### Author: ![strogonoff](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/strogonoff/32/4297_2.png) [@strogonoff](https://discuss.prosemirror.net/u/strogonoff)
#### Post date: [May 16, 2025, 2:01pm UTC](https://discuss.prosemirror.net/t/getting-a-hold-of-the-document-used-by-prosemirror-from-todom/8392/12 "2025-05-16T14:01:47Z")

</div>

I see what you mean: basically, for my pre-rendering purposes, the resulting node or fragment probably only has to implement `.innerHTML` (I grab that and that’s it), but other callers (including potentially PM itself?) may expect a more complete instance.

I rather suspect it should be possible to guarantee with a slightly more conditional typing along the lines of “if you do _not_ provide an explicit `Document`, you get a full version of what your runtime provides, but if you do, then you shall receive whatever you have given”. This should require just a change in the return signature of serializer, I suppose. In fact, this should also be desirable even now, to any current users who provide their own document parameter to the serializer.

I might come up with a more thorough proposal (perhaps one I will have tested in a fork) and create a separate thread for that, if it’s shown to work as I claim without implementation changes and with minimal typing changes…
