Skip to main content

Additional Constraints

tl;dr — TypeScript types are the primary constraint (string, number, 'admin' | 'viewer', User[]). These JSDoc annotations add to what the type system provides — range bounds on numbers, format on strings, length on arrays, and so on. They pass through verbatim to typia, so the full typia JSDoc vocabulary is available.

For filling default values, see the separate @default page.


How to write an annotation

Put the annotation in a JSDoc block immediately above the field:

interface Person {
/** @minimum 13 */
age: number;
}

To apply multiple annotations to one field, put them in the same JSDoc block — one per line:

interface Name {
/**
* @minLength 3
* @maxLength 20
*/
value: string;
}

Unknown annotations (e.g., @author Alice, @deprecated) are tolerated — they're left alone. Typia only reacts to the annotations listed below; everything else is documentation for humans.

Two silent footguns

Both of these drop the annotation without any error, which makes them hard to debug:

  • Inline JSDoc doesn't attach. interface R { /** @minimum 13 */ age: number; } puts the comment on the same line as the field; tsc won't attach it. The block must be on its own line(s) above the field.
  • Stacked blocks — only the last one counts. Two separate /** ... */ blocks above a field will silently drop the earlier one. Always put multiple tags in a single block.
// ❌ Stacked blocks — @minimum silently dropped
/** @minimum 1 */
/** @maximum 5 */
stars: number;
// ...
// ✅ Single block with multiple tags — both apply
/**
* @minimum 1
* @maximum 5
*/
stars: number;
Case sensitivity

Annotation names and values are case-sensitive except where explicitly aliased (e.g., datetime and dateTime both mean date-time). @format EMAIL is a compile-time error; write @format email.

Number annotations

Apply to fields typed number or bigint.

AnnotationValueMeaning
@typeint32, uint32, int64, uint64, float, double (shortcuts: intint32, uintuint32)int*/uint* reject non-integers (e.g. 2.5); uint* reject negatives; bounded variants also enforce the type's value range. float/double accept any JS number
@minimum NnumberValue must be ≥ N
@maximum NnumberValue must be ≤ N
@exclusiveMinimum NnumberValue must be > N
@exclusiveMaximum NnumberValue must be < N
@multipleOf NnumberValue must be an exact multiple of N
interface Rating {
/**
* @minimum 1
* @maximum 5
* @type int32
*/
stars: number;
}
const bad = await facet.parse({ stars: 6 }, 'Rating');
expect(bad.valid).toBe(false); // stars exceeds @maximum

const ok = await facet.parse({ stars: 5 }, 'Rating');
expect(ok).toEqual({ valid: true, data: { stars: 5 } });

String annotations

Apply to fields typed string.

AnnotationValueMeaning
@format Fone of the format IDs belowValue must match the named format
@pattern REGEXregex source (no flags)Value must match the regex
@length NintegerLength must equal N exactly (sets @minLength and @maxLength together)
@minLength NintegerString length must be ≥ N
@maxLength NintegerString length must be ≤ N
interface Contact {
/** @format email */
email: string;

/** @pattern ^[a-z0-9-]+$ */
slug: string;
}
const bad = await facet.parse(
{ email: 'not-an-email', slug: 'Has Spaces' },
'Contact',
);
expect(bad.valid).toBe(false); // both fields fail

const ok = await facet.parse(
{ email: 'alice@example.com', slug: 'hello-world' },
'Contact',
);
expect(ok).toEqual({
valid: true,
data: { email: 'alice@example.com', slug: 'hello-world' },
});

Accepted @format values

25 format IDs, drawn from typia's vocabulary:

  • Identifiers: uuid
  • Email: email, idn-email
  • Host / URL: hostname, idn-hostname, ipv4, ipv6, uri, uri-reference, uri-template, url, iri, iri-reference
  • Date / time: date-time (aliases: datetime, dateTime), date, time, duration
  • JSON Pointer: json-pointer, relative-json-pointer
  • Miscellaneous: byte, password, regex

Array annotations

Apply to fields typed as an array (T[], Array<T>) or a set (Set<T>).

AnnotationValueMeaning
@items NintegerLength must equal N exactly (sets @minItems and @maxItems together)
@minItems NintegerLength must be ≥ N
@maxItems NintegerLength must be ≤ N
@uniqueItems(no value)No duplicate elements
interface Bag {
/**
* @minItems 1
* @maxItems 10
* @uniqueItems
*/
tags: string[];
}
const empty = await facet.parse({ tags: [] }, 'Bag');
expect(empty.valid).toBe(false); // @minItems violated

const dup = await facet.parse({ tags: ['a', 'a'] }, 'Bag');
expect(dup.valid).toBe(false); // @uniqueItems violated

const ok = await facet.parse({ tags: ['a', 'b', 'c'] }, 'Bag');
expect(ok).toEqual({ valid: true, data: { tags: ['a', 'b', 'c'] } });

@default annotation

One more annotation, with its own semantics: @default. It fills missing optional fields before the validator runs. Covered in depth on its own page because it's about input-filling rather than constraint-checking.

Note on typia branded types

Typia also supports expressing these constraints as type-system branded types (number & Minimum<13>, string & Format<"email">). Both paths produce identical validators. This package's documentation uses the JSDoc form exclusively because:

  • Interface definitions are passed as strings at runtime, so a typia import isn't practical.
  • JSDoc is lower-friction for everyday use.
  • Mixing JSDoc @default with branded validator tags splits the annotation surface arbitrarily.

Branded types still work — typia's transformer processes them identically — but they're not documented or tested here. If you reach for them, consult the typia docs directly.