A recent Markdoc release introduced a new feature that makes it possible for validation functions to access the parents of the current node. This feature is useful for writing custom validation logic that analyzes document structure or enforces rules based on a node’s position in the hierarchy.
For example, you might want to impose restrictions on where an image
can be nested in a document, prohibiting the use of images in specific tags like callouts and asides. In previous versions of Markdoc, you would have to define that sort of logic in custom validation functions on the disallowed parent tags themselves, using a recursive walk()
to evaluate all the children and determine if any are image nodes. With the newly-added parent tag feature, this sort of validation is now much easier to implement.
In a custom validation function, the config
object has a new validation.parents
property that contains an array with the current node’s parents, all the way up to the top of the node hierarchy. The array is like a stack of open tree nodes, with the first item representing the document root and the last item representing the immediate parent of the node
argument. You can iterate over the parents
array in order to perform checks on the current node’s parents.
Example
The following example demonstrates how create a custom schema for the image
node that uses the parents
property to prevent the user from placing an image inside of a callout
or aside
tag:
const image = {
...Markdoc.nodes.image,
validate(node, config) {
for (const parent of config.validation.parents)
if (["callout", "aside"].includes(parent.tag))
return [{
id: 'image-nesting-error',
level: 'error',
message: `Can't nest an image inside of a '${parent.tag}'`
}];
return [];
}
};
The function iterates over config.validation.parents
, checks each entry to determine if it is a callout
or aside
tag, and creates a validation error when it encounters an invalid nesting. This works no matter how deeply the image is nested inside of one of the forbidden tags.
Limitations
The parents
array reflects the hierarchy of a parsed document at validation time, which may not strictly correlate with the document hierarchy that results during Markdoc’s transform
phase. When validating the contents of a partial, for example, the parents
array only contains the hierarchy of the partial itself and does not include any parent nodes from the place where the partial is used.
Why validate parent nodes?
I added this feature to Markdoc because there were a number of cases internally at Stripe where the capabilities of the children
schema property were too limiting.
In cases like the example above, using config.validation.parents
inside of the image
node’s validate
function instead of using a recursive check on child nodes inside of a callout
ensures that the error message is tied to the image node instead of the enclosing callout. This makes the underlying issue a lot clearer to writers when validation errors display in an editor like Visual Studio Code using the Markdoc language server.
The ability to enforce structural norms in content helps improve consistency, resulting in a better experience for readers. It also prevents visual bugs in cases where unexpected markup nesting conflicts with assumptions made in stylesheets. Using validation to catch and highlight these issues at authoring time improves the experience for writers and reduces the overhead for reviewers.