outline/outline · error · Error

msg

Error message

msg

What it means

This is a Sequelize custom validator (`TextLength`) attached to the `Comment.data` JSONB column in server/models/Comment.ts:53. It renders the stored ProseMirror document to plain text via `ProsemirrorHelper.toPlainText`, counts Unicode code points with `Array.from(text).length` (so emoji and CJK characters count as one each, not by UTF-16 code unit), and throws the supplied `msg` when the count falls outside `[min, max]`. The active ceiling is `CommentValidation.maxLength = 1000` characters, so the thrown message reads "Comment must be less than 1000 characters". It fires during model validation on any create or update that persists the `data` field.

Source

Thrown at server/models/validators/TextLength.ts:34

  msg?: string;
  min?: number;
  max: number;
}): (target: object, propertyName: string) => void {
  return (target: object, propertyName: string) =>
    addAttributeOptions(target, propertyName, {
      validate: {
        validLength(value: ProsemirrorData) {
          let text;

          try {
            text = ProsemirrorHelper.toPlainText(Node.fromJSON(schema, value));
          } catch (_err) {
            throw new Error("Invalid data");
          }

          const length = text ? Array.from(text).length : 0;
          if (length > max || length < min) {
            throw new Error(msg);
          }
        },
      },
    });
}

View on GitHub (pinned to 935a44d4c0)

Solutions

  1. Render the candidate ProseMirror doc to plain text and measure `Array.from(text).length` client-side before submitting; truncate or block the post when it exceeds 1000.
  2. Split the content across multiple comments or move long content into a Document (DocumentValidation.maxLength is far higher).
  3. If you control the editor, enforce the limit with the MaxLength ProseMirror extension (shared/editor/extensions/MaxLength.ts) so the UI prevents the over-length state.
  4. Verify the `data` blob is valid ProseMirror JSON — a malformed node throws the separate "Invalid data" error at TextLength.ts:29 before the length check.

Example fix

// before — submit unvalidated comment data
await client.comments.create({ documentId, data });

// after — measure rendered text length first
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
import { Node } from "prosemirror-model";
import { schema } from "@server/editor";
import { CommentValidation } from "@shared/validations";

const text = ProsemirrorHelper.toPlainText(Node.fromJSON(schema, data));
if (Array.from(text).length > CommentValidation.maxLength) {
  throw new Error(`Comment must be less than ${CommentValidation.maxLength} characters`);
}
await client.comments.create({ documentId, data });
Defensive patterns

Strategy: validation

Validate before calling

import { CommentValidation } from "@shared/validations";
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
import { Node } from "prosemirror-model";
import { schema } from "@server/editor";

/**
 * Returns true when the rendered text length fits the Comment limit.
 *
 * @param data ProseMirror document data.
 * @return true if the comment is safe to persist.
 */
function isCommentLengthValid(data: ProsemirrorData): boolean {
  const text = ProsemirrorHelper.toPlainText(Node.fromJSON(schema, data));
  return Array.from(text).length <= CommentValidation.maxLength;
}

Type guard

/**
 * Narrows a value to a valid-length Comment data object.
 *
 * @param value Candidate ProseMirror document data.
 * @return true when value parses and is within the length limit.
 */
function isValidCommentData(value: unknown): value is ProsemirrorData {
  try {
    const node = Node.fromJSON(schema, value as ProsemirrorData);
    const text = ProsemirrorHelper.toPlainText(node);
    return Array.from(text).length <= CommentValidation.maxLength;
  } catch {
    return false;
  }
}

Try / catch

try {
  await comment.save();
} catch (err) {
  if (err instanceof Sequelize.ValidationError
      && err.errors.some((e) => e.message.startsWith("Comment must be less than"))) {
    // truncate or prompt the user to shorten the comment
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Creating or updating a Comment (`POST /api/comments.create`, `/api/comments.update`) whose rendered plain text exceeds 1000 Unicode code points. Because the count is on extracted text, a document containing many short nodes (list items, mentions) can hit the limit even when the raw JSON feels small. It also fires when `min > 0` is configured and the text is too short, though the Comment usage leaves `min` at its default of 0.

Common situations: Pasting a large block of text or a long threaded reply into a comment; programmatic imports/bot integrations that submit untruncated bodies; migrations or test fixtures that seed oversized comments; emoji-heavy content where byte-length estimates undercount because the validator measures code points, not bytes.

Related errors


AI-assisted analysis of outline/outline@935a44d4c0 (2026-08-12). Data as JSON: /api/errors/2a29ad5ece543441. Report an issue: GitHub.