#1624·valibot

docs: add some number and date validations helpers

Author: userquinCreated Sep 8, 2026Updated Sep 9, 2026

Following with #1494 here some useful code you can add to the docs (I'm using vuetify 4 + valibot + vee-validate + vue-i18n with nuxt 4, check screenshots at the end).

requiredInteger
typescript
export function requiredInteger(
  t: (key: string, ...args: any[]) => string,
  min?: number,
  max?: number
) {
  return v.pipe(
    v.nonOptional(
      v.union([v.string(), v.number()]),
      () => t('messages.required')
    ),
    v.transform((input) => {
      if (typeof input === 'string' && input.trim() === '') {
        return Number.NaN
      }
      return Number(input)
    }),
    v.number(() => t('messages.required')),
    v.finite(() => t('messages.required')),
    v.integer(() => t('messages.invalid')),
    v.minValue(typeof min === 'number' ? min : Number.MIN_SAFE_INTEGER, ({ requirement }) => t('messages.min', { min: requirement })),
    v.maxValue(typeof max === 'number' ? max : Number.MAX_SAFE_INTEGER, ({ requirement }) => t('messages.max', { max: requirement })),
  )
}
requiredDouble
typescript
export function requiredDouble(
  t: (key: string, ...args: any[]) => string,
  min?: number,
  max?: number
) {
  return v.pipe(
    v.nonOptional(
      v.union([v.string(), v.number()]),
      () => t('messages.required')
    ),
    v.transform((input) => {
      if (typeof input === 'string' && input.trim() === '') {
        return Number.NaN
      }
      return Number(input)
    }),
    v.number(() => t('messages.required')),
    v.finite(() => t('messages.required')),
    v.minValue(typeof min === 'number' ? min : Number.MIN_SAFE_INTEGER, ({ requirement }) => t('messages.min', { min: requirement })),
    v.maxValue(typeof max === 'number' ? max : Number.MAX_SAFE_INTEGER, ({ requirement }) => t('messages.max', { max: requirement })),
  )
}
optionalInteger
typescript
export function optionalInteger(
  t: (key: string, ...args: any[]) => string,
  min?: number,
  max?: number
) {
  return v.optional(
    v.union([
      v.literal(''),
      v.pipe(
        v.nonOptional(
          v.union([v.string(), v.number()]),
          () => t('messages.required')
        ),
        v.transform((input) => {
          if (typeof input === 'string' && input.trim() === '') {
            return Number.NaN
          }
          return Number(input)
        }),
        // Valida que el dato entrante sea estrictamente un tipo number
        v.number(() => t('messages.required')),
        v.finite(() => t('messages.required')),
        v.integer(() => t('messages.invalid')),
        v.minValue(typeof min === 'number' ? min : Number.MIN_SAFE_INTEGER, ({ requirement }) => t('messages.min', { min: requirement })),
        v.maxValue(typeof max === 'number' ? max : Number.MAX_SAFE_INTEGER, ({ requirement }) => t('messages.max', { max: requirement })),
      ),
    ]),
    '',
  )
}
optionalDouble
typescript
export function optionalDouble(
  t: (key: string, ...args: any[]) => string,
  min?: number,
  max?: number
) {
  return v.optional(
    v.union([
      v.literal(''),
      v.pipe(
        v.nonOptional(
          v.union([v.string(), v.number()]),
          () => t('messages.required')
        ),
        v.transform((input) => {
          if (typeof input === 'string' && input.trim() === '') {
            return Number.NaN
          }
          return Number(input)
        }),
        v.number(() => t('messages.required')),
        v.finite(() => t('messages.invalid')),
        v.minValue(typeof min === 'number' ? min : Number.MIN_SAFE_INTEGER, ({ requirement }) => t('messages.min', { min: requirement })),
        v.maxValue(typeof max === 'number' ? max : Number.MAX_SAFE_INTEGER, ({ requirement }) => t('messages.max', { max: requirement })),
      ),
    ]),
    '',
  )
}
isValidIsoDate
typescript
export function isValidIsoDate(t: (key: string, ...args: any[]) => string) {
  return v.check(
    (input: string) => {
      if (input.length === 0) {
        return true
      }
      const [yearStr, monthStr, dayStr] = input.split('-')
      if (!yearStr || !monthStr || !dayStr) {
        return true
      }

      const year = +yearStr
      const month = +monthStr
      const day = +dayStr

      if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) {
        return true
      }

      const date = new Date(Date.UTC(year, month - 1, day))

      return (
        date.getUTCFullYear() === year &&
        date.getUTCMonth() === month - 1 &&
        date.getUTCDate() === day
      )
    },
    () => t('messages.invalid'),
  )
}
requiredDate
typescript
export function requiredDate(
  t: (key: string, ...args: any[]) => string,
) {
  return v.optional(
    v.pipe(
      v.string(),
      v.nonEmpty(() => t('messages.required')),
      v.isoDate(() => t('messages.invalid')),
      isValidIsoDate(t),
    ),
    '',
  )
}
optionalDate
typescript
export function optionalDate(
  t: (key: string, ...args: any[]) => string,
) {
  return v.optional(
    v.union([
      v.literal(''),
      v.pipe(
        v.string(),
        v.isoDate(() => t('messages.invalid')),
        isValidIsoDate(t),
      ),
    ]),
    '',
  )
}
Some screenshots using requiredInteger and optionalInteger
valibot schema using previous helpers valibot schema using requiredInteger and optionalInteger
valibot messages on vuetify v-textfile (type number) after clicking the submit button valibot messages on vuetify v-textfile (type number) after clicking the submit button