PhaultLines

Accessing parent nodes in a Markdoc validation function

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.