Validators
Zod schemas for the input types modules keep needing.
validators is a set of Zod schemas for things every other module also validates. They are
ordinary Zod, so they compose with .optional(), .default(), and everything else you would
normally reach for.
import { validators } from '@odel/module-sdk';
inputSchema: {
to: validators.emailList(),
subject: validators.boundedString(1, 200),
replyTo: validators.email().optional()
}Text
| Validator | Accepts |
|---|---|
nonEmptyString() | At least one character — including whitespace. " " passes. |
trimmedString() | Trims first, then requires a character. " hi " becomes "hi"; " " fails. |
boundedString(min, max) | Length between min and max |
optionalString() | Optional, and turns "" into undefined |
nonEmptyString and trimmedString are easy to mix up. For anything a human typed into a form,
trimmedString is almost always the one you want — a subject line of three spaces is not a subject
line.
validators.email(); // one address
validators.emailList(); // an array, or a comma-separated stringemailList accepts ["a@b.com", "c@d.com"] or "a@b.com, c@d.com" and always gives you an array
of validated addresses. Surrounding whitespace is trimmed, so a list pasted out of a mail client
works.
URLs
validators.url(); // any URL Zod accepts
validators.httpsUrl(); // must start with https://url() is broader than it looks — it accepts ftp://, mailto:, and javascript: too. If your
module is going to fetch the value, or put it in an href, use httpsUrl().
Numbers
| Validator | Accepts |
|---|---|
positiveInt() | Integer greater than 0 |
nonNegativeInt() | Integer 0 or greater |
port() | Integer 1–65535 |
Keys and identifiers
validators.apiKey(); // at least 10 characters
validators.apiKey('sk-'); // …and must start with "sk-"
validators.uuid(); // any UUID version, not just v4The prefix form catches the most common support ticket a module gets: a key pasted from the wrong
service. Failing at the schema with API key must start with "sk-" is a better outcome than a
401 from upstream three calls later.
Dates and enums
validators.isoDate(); // ISO 8601 *datetime* — "2024-01-15T10:30:00Z"
validators.enumFrom(['draft', 'published', 'archived'] as const);isoDate requires a time component. A bare "2024-01-15" fails, so if you accept plain dates, use
your own z.string().regex(...).
enumFrom needs as const to infer the literal union — without it the array widens to string[]
and you lose the type.
JSON
validators.json<{ enabled: boolean }>();
// '{"enabled":true}' -> { enabled: true }This parses the string and casts to T. It does not check that the parsed value matches the
type — invalid JSON fails, but '{"enabled":"yes"}' passes and hands you a lie. When the shape
matters, parse and then validate:
validators.json().pipe(z.object({ enabled: z.boolean() }));Nothing here is required
These exist to save typing, not to gate you. A tool that needs something specific should use plain Zod, and mixing the two in one schema is normal:
import { z } from 'zod';
inputSchema: {
to: validators.emailList(),
body: z.string(),
priority: z.enum(['low', 'normal', 'high']).default('normal')
}