Zod Array of Numbers: Validate and Parse in Zod


Validating an array of numbers in Zod is a common requirement — whether you’re handling user-submitted IDs, enforcing numeric ranges, or processing structured data.

Fortunately, Zod provides a clean and expressive way to define number array schemas, apply constraints like length, uniqueness, or integer-only rules, and safely parse untrusted input.

This guide walks you through everything from defining number arrays to combining advanced validations using .int(), .coerce(), .refine(), and safeParse(). If you’re looking to build robust, reusable schemas that handle real-world numeric input gracefully, read on.

Defining an Array of Numbers in Zod

When working specifically with numeric lists — like scores, dimensions, or numeric form fields — Zod makes it easy to define an array that strictly accepts numbers.

To define an array of numbers in Zod, you use the z.array() method with z.number() as the inner schema:

const numberArray = z.array(z.number());

This tells Zod:
“I expect an array, and every element must be a number.”
It’s a concise yet powerful way to enforce number-only collections — no strings, booleans, or undefined values will pass validation.

Defining number arrays is foundational when:

  • Accepting numeric inputs from forms or query strings
  • Validating numeric payloads like [price, tax, total]
  • Working with structured values like coordinates, ID lists, or thresholds

Validating Fixed-Length Arrays: Coordinate Pairs and Ranges

In many real-world applications, you’re not just accepting any array of numbers — you’re expecting an array with exactly two numeric values. This pattern is especially common for:

  • Coordinate pairs like [latitude, longitude]
  • Numeric ranges like [min, max]
  • Slider values, rating boundaries, or dimension pairs

To validate fixed-length numeric arrays in Zod, you have two main approaches: z.tuple() for positional constraints and z.array(z.number()).length(n) for value-based uniformity.

const rangeSchema = z.array(z.number()).length(2);
rangeSchema.parse([0, 100]); // ✅
rangeSchema.parse([0]); // ❌ Error: Array must contain exactly 2 items

Restricting to Integer Values in a Number Array

In many real-world applications, it’s not sufficient to accept just any number — the data must be strictly integer-based.

Whether you’re dealing with ID arrays, index collections, quantity selectors, or offset-based pagination, allowing floats can introduce subtle bugs or type mismatches downstream.

Zod makes it straightforward to enforce integer-only validation within arrays using the .int() method chained to z.number():

const integerArraySchema = z.array(z.number().int());

This schema asserts that:

  • The input is a valid array
  • Every element is a number
  • Each number must be an integer — no floats, no NaN, no type coercion
integerArraySchema.parse([1, 2, 3]); // ✅ Passes
integerArraySchema.parse([1.5, 2, 3]); // ❌ Fails: invalid_type - Expected integer, received float

If any element fails the .int() check, Zod returns a precise, indexed error — making it easy to pinpoint the problematic value during debugging or input validation.

Parsing an Array of Numbers from String Inputs

In real-world applications, numeric data often arrives as strings — especially through query parameters, forms, or dynamic front-end frameworks.

Zod doesn’t coerce strings to numbers by default, so validating raw input like [“1”, “2”, “3”] using z.array(z.number()) will fail. To handle this gracefully, Zod offers two practical approaches: coercion and preprocessing.

If your input is already an array of numeric strings, use z.coerce.number() to automatically convert each string into a number before validation:

const schema = z.array(z.coerce.number());
schema.parse(["1", "2", "3"]); // ✅ Returns [1, 2, 3]

This is ideal when working with FormData, multi-select fields, or frontend frameworks that submit stringified numbers.

Creating a Reusable Schema for a Number Array

When multiple parts of your application expect the same structure — like a list of scores, prices, quantities, or numeric inputs — it’s best practice to define the array schema once and reuse it consistently.

This not only ensures type safety but also improves maintainability and reduces the risk of mismatched validation logic across components.

Zod makes schema reuse effortless. You can define a number array schema with specific constraints (like min/max values, positivity, or integer enforcement) and export it wherever needed:

import { z } from "zod";
export const scoreArraySchema = z.array(
  z.number().min(0).max(100)
);

This schema guarantees that all values are numbers between 0 and 100 — ideal for things like rating systems, percentage scores, or percentile data. Once defined, you can safely use it across your frontend forms, backend APIs, and shared validation layers:

scoreArraySchema.parse([95, 88, 72]); // ✅
scoreArraySchema.parse([120, -5]); // ❌ Violates constraints: too_small - Number must be greater than or equal to 0

Ensuring Unique Values in a Number Array

When you’re validating arrays of numbers, sometimes it’s not just the type or range that matters — it’s uniqueness. Duplicate values in arrays can cause issues in many real-world scenarios, such as ID lists, selected options, ranking submissions, or datasets that require deduplication.

Zod doesn’t include a built-in .unique() method, but you can easily enforce uniqueness using .refine(). This lets you define a custom validation rule that checks if all numbers in the array are distinct.

const uniqueNumberArray = z.array(z.number()).refine(
  (arr) => new Set(arr).size === arr.length,
  { message: "Array must contain only unique numbers" }
);

Here, Set is used to filter out duplicates — if the length of the Set is equal to the original array, the values are unique.

uniqueNumberArray.parse([1, 2, 3]); // ✅ Valid
uniqueNumberArray.parse([1, 2, 2]); // ❌ Error: Array must contain only unique numbers

This pattern is particularly useful when working with things like user-selected IDs, numeric filters, or data uploads where redundancy is not allowed. You can also chain this with .min(), .int(), or .length() to create more specific rules while still ensuring uniqueness.

In short, while Zod doesn’t offer native deduplication, its .refine() method gives you the flexibility to enforce uniqueness exactly where and how you need it.

Combining Multiple Constraints for Custom Array Logic

In real-world applications, validating a number array often requires more than a single rule. You might want to ensure values are positive integers, the array has a fixed length, and that the numbers follow a specific pattern — like being in ascending order or within a defined range.

Zod allows you to compose such constraints fluently using method chaining and .refine().

For example, let’s validate an array of exactly three positive integers, where each number must be greater than the previous one:

const ascendingTripleSchema = z
.array(z.number().int().positive())
.length(3)
.refine(
  (arr) => arr[0] < arr[1] && arr[1] < arr[2],
  { message: "Numbers must be in strictly ascending order" }
);

This schema enforces:

  • Only positive integers
  • Exactly three elements
  • Ascending order across values
ascendingTripleSchema.parse([10, 20, 30]); // ✅
ascendingTripleSchema.parse([10, 20, 20]); // ❌ Not strictly increasing

By chaining validations like .int(), .positive(), .length(), and .refine(), you gain precise control over the logic — all within a declarative schema. This is especially useful for validating numeric filters, scoring models, or algorithm inputs that must follow structured patterns.

Using Safe Parsing and Handling Validation Errors

When validating number arrays in Zod, it’s often safer to avoid throwing exceptions and instead handle validation results explicitly. This is where safeParse() comes in — it gives you full control over success and failure states without disrupting your application flow.

Unlike parse(), which throws on failure, safeParse() returns an object with a success flag and either the parsed data or error details:

const numberArray = z.array(z.number());
const result = numberArray.safeParse([1, 2, "x"]);

if (!result.success) {
  console.log(result.error.issues); // Detailed error report
} else {
  console.log(result.data); // ✅ Validated array
}

This pattern is ideal for user-facing forms, APIs, or batch validations where graceful failure handling is important. It allows you to return meaningful messages, highlight specific issues, or provide recovery options without breaking execution.

Whether you’re working with structured inputs or untrusted data, safeParse() is the recommended way to validate number arrays in production-ready Zod workflows.

Conclusion

Validating an array of numbers in Zod goes far beyond just checking types — it enables precise control over structure, constraints, and custom logic.

Whether you’re working with fixed-length pairs, enforcing integer-only values, parsing string input, or validating uniqueness, Zod’s fluent API gives you the flexibility to compose robust schemas for every numeric use case.

By leveraging features like .int(), .coerce.number(), .length(), .refine(), and safeParse(), you can build defensive, scalable validation logic that fits seamlessly into both frontend and backend workflows.

As your validation needs grow, these patterns can serve as the foundation for more complex schemas — ensuring that your number arrays are always well-formed, predictable, and safe to process.

Sharukhan Avatar

Sharukhan Patan

Sharukhan Patan is the founder of Tecktol. He has worked as a software engineer specializing in full-stack web development.