string()
Create a string validator with URL, email, hostname, regex, min, max, and secret checks.
string()
Creates a validator that accepts string values.
Signature
function string(): StringValidator
Accepted Values
Only actual string values pass the base validator. Non-strings produce a type error with a
suggestion to wrap the value in quotes.
string().parse("hello") // ✅ ok: "hello"
string().parse("") // ✅ ok: ""
string().parse(42) // ❌ type error: "Expected a string, received number"
string().parse(null) // ❌ type error
Refinements
String refinements add additional constraints. Chain .min(), .max(), .url(), .email(), .port(), .hostname(), .regex() before .secret(), .optional(), .describe(). Reason: .secret() and .optional() return a generic wrapper that drops type-specific methods like .url().
.url()
Requires a valid URL with a protocol (https://, http://, etc.). Protocol-less strings and file: URLs are rejected. Uses new URL() internally.
const env = defineEnv({
API_URL: string().url(),
})
// ✅ "https://api.example.com/v1"
// ❌ "not-a-url"
.email()
Requires the value to match a basic email pattern:
const env = defineEnv({
SUPPORT_EMAIL: string().email(),
})
// ✅ "user@example.com"
// ❌ "not-an-email"
Uses the HTML5 email validation regex.
.port()
Requires the value to be a valid port number string (1-65535):
const env = defineEnv({
REDIS_PORT: string().port(),
})
// ✅ "6379"
// ✅ "65535"
// ❌ "0"
// ❌ "70000"
.hostname()
Requires the value to be a valid RFC 1123 hostname:
const env = defineEnv({
SERVICE_HOST: string().hostname(),
})
// ✅ "example.com"
// ✅ "my-service.internal"
// ✅ "localhost"
// ❌ "-invalid"
// ❌ "host name"
// ❌ "a-very-long-label-that-exceeds-sixty-three-characters.example.com"
.min(length)
Requires the string to be at least length characters:
const env = defineEnv({
PASSWORD: string().min(8),
})
// ✅ "abcdefgh" (8 characters)
// ❌ "abc" (too short)
.max(length)
Requires the string to be at most length characters:
const env = defineEnv({
USERNAME: string().min(3).max(20),
})
// ✅ "john_doe"
// ❌ "ab" (too short)
// ❌ "a_very_long_username_that_exceeds" (too long)
.regex(pattern, message?)
Requires the string to match a regular expression:
const env = defineEnv({
SLUG: string().regex(/^[a-z0-9-]+$/, "Must be a lowercase slug"),
})
// ✅ "my-blog-post"
// ❌ "Invalid slug!"
// Custom error message (optional second parameter)
string().regex(/^[A-Z]+$/, "Must be uppercase only")
return value: StringValidator
All refinement methods return a new StringValidator, enabling chaining:
string().min(1).max(255).url().optional()
The original validator is never mutated.
Chain order matters: Refinements (
.min(),.max(),.url(),.email(),.port(),.hostname(),.regex()) must come before.secret(),.optional(), or.describe(). Reason:.secret()and.optional()return a generic wrapper that drops type-specific methods.// ✅ Correct string().url().min(1).secret() // ❌ Wrong — .min() won't exist string().secret().url().min(1)
Examples
Basic string
const env = defineEnv({
APP_NAME: string(),
})
// env.APP_NAME: string
String with constraints
const env = defineEnv({
DATABASE_URL: string().url().describe("Primary database connection"),
JWT_SECRET: string().min(32).secret(),
NICKNAME: string().min(2).max(30).optional(),
EMAIL: string().email(),
PORT: string().port(),
})
Custom validation
const env = defineEnv({
API_KEY: string().validate((value) => {
if (!value.startsWith("sk_")) return "Must start with 'sk_'"
}),
})How is this guide?
Last updated on Jun 30, 2026