Validation Helpers
ctkit ships two sets of helper functions that return validation objects. Use them instead of writing raw validation objects to keep your schemas readable and typo-free.
import { validators, richTextValidators } from "@ctkit/core";Text validators
Section titled “Text validators”All text validators return a TextValidation object.
snakeCase()
Section titled “snakeCase()”Validates that the value matches snake_case format.
validators.snakeCase()// → { regexp: { pattern: "^[a-z][a-z0-9_]*$" } }{ id: "apiKey", name: "API Key", type: "Symbol", required: true, validations: [validators.snakeCase()],}kebabCase()
Section titled “kebabCase()”Validates that the value matches kebab-case format.
validators.kebabCase()// → { regexp: { pattern: "^[a-z][a-z0-9-]*$" } }camelCase()
Section titled “camelCase()”Validates that the value matches camelCase format.
validators.camelCase()// → { regexp: { pattern: "^[a-z][a-zA-Z0-9]*$" } }slug()
Section titled “slug()”Validates a URL-safe slug (lowercase-words-separated-by-hyphens).
validators.slug()// → { regexp: { pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" } }{ id: "slug", name: "Slug", type: "Symbol", required: true, validations: [validators.slug(), validators.unique()],}email()
Section titled “email()”Validates a basic email address format.
validators.email()// → { regexp: { pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" } }Validates that the value starts with http:// or https://.
validators.url()// → { regexp: { pattern: "^https?:\\/\\/.+" } }hexColor()
Section titled “hexColor()”Validates a 6-digit hex color code (e.g. #FF5733).
validators.hexColor()// → { regexp: { pattern: "^#[0-9A-Fa-f]{6}$" } }{ id: "brandColor", name: "Brand Color", type: "Symbol", required: false, validations: [validators.hexColor()],}uuid()
Section titled “uuid()”Validates a standard UUID format (case-insensitive).
validators.uuid()// → { regexp: { pattern: "^[0-9a-f]{8}-...", flags: "i" } }textLength(min?, max?)
Section titled “textLength(min?, max?)”Restricts string length. Both parameters are optional.
validators.textLength(1, 120)// → { size: { min: 1, max: 120 } }
validators.textLength(10)// → { size: { min: 10 } }
validators.textLength(undefined, 500)// → { size: { max: 500 } }textIn(values)
Section titled “textIn(values)”Restricts the value to an array of allowed strings.
validators.textIn(["draft", "review", "published"])// → { in: ["draft", "review", "published"] }{ id: "status", name: "Status", type: "Symbol", required: true, validations: [validators.textIn(["active", "inactive", "archived"])],}unique()
Section titled “unique()”Marks the field as unique across all entries.
validators.unique()// → { unique: true }Also available as the more specific uniqueText():
validators.uniqueText()// → { unique: true }customRegex(pattern, flags?)
Section titled “customRegex(pattern, flags?)”Validates against a custom regular expression.
validators.customRegex("^[A-Z]{2}-\\d{4}$")// → { regexp: { pattern: "^[A-Z]{2}-\\d{4}$" } }
validators.customRegex("^(foo|bar)$", "i")// → { regexp: { pattern: "^(foo|bar)$", flags: "i" } }Number validators
Section titled “Number validators”All number validators return a NumberValidation object.
numberRange(min?, max?)
Section titled “numberRange(min?, max?)”Restricts the number to a range. Both parameters are optional.
validators.numberRange(1, 100)// → { range: { min: 1, max: 100 } }
validators.numberRange(0)// → { range: { min: 0 } }{ id: "rating", name: "Rating", type: "Integer", required: true, validations: [validators.numberRange(1, 5)],}numberIn(values)
Section titled “numberIn(values)”Restricts the value to an array of allowed numbers.
validators.numberIn([1, 2, 3, 4, 6, 12])// → { in: [1, 2, 3, 4, 6, 12] }uniqueNumber()
Section titled “uniqueNumber()”Marks the number field as unique across all entries.
validators.uniqueNumber()// → { unique: true }Array validators
Section titled “Array validators”arraySize(min?, max?)
Section titled “arraySize(min?, max?)”Restricts the number of items in an array field.
validators.arraySize(1, 10)// → { size: { min: 1, max: 10 } }{ id: "tags", name: "Tags", type: "Array", required: false, items: { type: "Symbol" }, validations: [validators.arraySize(1, 5)],}Rich text validators
Section titled “Rich text validators”All rich text validators return a RichTextValidation object.
import { richTextValidators } from "@ctkit/core";basicFormatting()
Section titled “basicFormatting()”Enables only bold, italic, underline, and strikethrough marks.
richTextValidators.basicFormatting()// → { enabledMarks: ["bold", "italic", "underline", "strikethrough"] }{ id: "summary", name: "Summary", type: "RichText", required: true, validations: [richTextValidators.basicFormatting()],}paragraphsOnly()
Section titled “paragraphsOnly()”Restricts the field to plain paragraphs with no formatting at all.
richTextValidators.paragraphsOnly()// → { enabledNodeTypes: ["paragraph", "text"], enabledMarks: [] }noHeadings()
Section titled “noHeadings()”Allows all standard block elements except headings.
richTextValidators.noHeadings()// → {// enabledNodeTypes: [// "paragraph", "ordered-list", "unordered-list",// "list-item", "blockquote", "hr", "text"// ]// }headingLevels(levels)
Section titled “headingLevels(levels)”Allows paragraphs plus only the specified heading levels.
richTextValidators.headingLevels([2, 3])// → { enabledNodeTypes: ["paragraph", "text", "heading-2", "heading-3"] }{ id: "body", name: "Body", type: "RichText", required: true, validations: [ richTextValidators.headingLevels([2, 3, 4]), richTextValidators.basicFormatting(), ],}noEmbeddedContent()
Section titled “noEmbeddedContent()”Allows all standard text and structure nodes but disables all embedded entries, embedded assets, and embedded resources.
richTextValidators.noEmbeddedContent()// → {// enabledNodeTypes: [// "paragraph", "heading-1", "heading-2", "heading-3",// "heading-4", "heading-5", "heading-6",// "ordered-list", "unordered-list", "list-item",// "blockquote", "hr", "text"// ]// }embeddedEntries(contentTypes)
Section titled “embeddedEntries(contentTypes)”Restricts which content types can be embedded as block or inline entries.
richTextValidators.embeddedEntries(["cta", "codeBlock"])// → {// nodes: {// "embedded-entry-block": [{ linkContentType: ["cta", "codeBlock"] }],// "embedded-entry-inline": [{ linkContentType: ["cta", "codeBlock"] }],// }// }{ id: "body", name: "Body", type: "RichText", required: true, validations: [ richTextValidators.embeddedEntries(["callout", "videoEmbed"]), ],}allowedMarks(marks)
Section titled “allowedMarks(marks)”Enables exactly the specified inline marks.
richTextValidators.allowedMarks(["bold", "italic", "code"])// → { enabledMarks: ["bold", "italic", "code"] }allowedNodeTypes(nodeTypes)
Section titled “allowedNodeTypes(nodeTypes)”Enables exactly the specified node types.
richTextValidators.allowedNodeTypes([ "paragraph", "heading-2", "heading-3", "unordered-list", "hyperlink", "text",])// → { enabledNodeTypes: ["paragraph", "heading-2", ...] }Combining helpers
Section titled “Combining helpers”Validators compose naturally — just spread them into the validations array:
import { validators, richTextValidators } from "@ctkit/core";import type { ContentTypeSchema } from "@ctkit/core";
const article: ContentTypeSchema = { id: "article", name: "Article", displayField: "title", fields: [ { id: "title", name: "Title", type: "Symbol", required: true, validations: [ validators.textLength(1, 120), validators.unique(), ], }, { id: "slug", name: "Slug", type: "Symbol", required: true, validations: [validators.slug(), validators.unique()], }, { id: "body", name: "Body", type: "RichText", required: true, validations: [ richTextValidators.headingLevels([2, 3]), richTextValidators.basicFormatting(), richTextValidators.embeddedEntries(["cta", "codeBlock"]), ], }, { id: "rating", name: "Rating", type: "Integer", required: false, validations: [validators.numberRange(1, 5)], }, { id: "tags", name: "Tags", type: "Array", required: false, items: { type: "Symbol" }, validations: [validators.arraySize(1, 10)], }, ],};
export default article;