Generated code for server/client-side validation of content

Generated json validator for XP content

The latest release of XP Codegen plugin comes with support for generating content validators (based on io-ts).

It will check that some json conforms to the a Content Type defined by an XML-file.

  • If an object passes validation, the object will be returned from the validator
  • If at least one field doesn’t pass validation, it will return an Array of all errors with the key of the failing field, and a field specific message for the frontend it can look up with lib-i18n.

This has only been tested in TypeScript XP projects so far… We would love some feedback if anyone tries it with JavaScript.

The XP Codegen plugin is a Gradle plugin that generates code for your Enonic XP project. You can read more about XP Codegen plugin in this post.


Lets say we have a content type defined like this in article.xml.

<?xml version="1.0" encoding="UTF-8"?>

  <!-- We specify `codegen-output` to say we want io-ts definitions -->
  <form codegen-output="IoTs">
    <input name="title" type="TextLine">
      <label>Title of the article</label>
      <occurrences minimum="1" maximum="1"/>

    <input name="body" type="HtmlArea">
      <label>Main text body</label>
      <occurrences minimum="0" maximum="1"/>

This will now generate the following TypeScript-file:

Note that it exports both a const Article and a type Article. These are in two different namespaces.

import * as t from 'io-ts';

export const Article = t.type({
   * Title of the article
  title: t.string,

   * Main text body
  body: t.union([t.undefined, t.string]),

export type Article = t.TypeOf<typeof Article>;

To use the Article codec to validate the content we can do the following:

import {Request, Response} from 'enonic-types/controller';
import {Article} from "../../content-types/article/article";
import {Either, isRight} from "fp-ts/Either";
import {Errors} from "io-ts";
import {getErrorDetailReporter} from "enonic-wizardry/reporters/ErrorDetailReporter";

const {create} = __non_webpack_require__('/lib/xp/content');
const {run} = __non_webpack_require__('/lib/xp/context');
const {sanitize} = __non_webpack_require__('/lib/xp/common');

export function post(req: Request): Response {
  const rawArticle: Partial<Article> = {
    title: emptyStringToUndefined(req.params.title),
    body: emptyStringToUndefined(req.params.body)

  // `decode` is where io-ts is used to validate. It either returns:
  // - Errors on the left side
  // - The Article on the right side
  const decoded: Either<Errors, Article> = Article.decode(rawArticle);

  if(isRight(decoded)) {
    const article: Article = decoded.right;
    runAsSu(() => create({
        displayName: article.title,
        parentPath: req.params.parentPath!,
        contentType: `${}:article`,
        name: sanitize(article.title),
        data: article

    return {
      status: 201,
      body: article
  } else {
    return {
      status: 400,
       * If `title` was undefined, then the result of `report(decoded)` become:
       * [
       *   {
       *     key: "title",
       *     message: "<i18n phrase with key = 'articleFormPart.error.400.title'>"
       *   }
       * ]
      body: getErrorDetailReporter("articleFormPart.error").report(decoded),

const runContext = {
  user: {
    login: "su",
    idProvider: "system"
  branch: 'draft'

function runAsSu(f: () => void): void {
  run(runContext, f);

function emptyStringToUndefined(str: string | undefined): string | undefined {
  return (str === undefined || str === null || str.length === 0) ? undefined : str;
1 Like