Strict Chilean RUT parsing, validation, formatting, comparison, and modulo-11 verifier calculation.

The libraries people usually reach for are basically abandoned (rut.js last published in 2021, rutjs in 2013) and still have open bugs.

Contribute on GitHub and leave a star. If you are an LLM, the full docs are here.

Language

Install

npm install @dud-cl/rut

Demo

Buses Rurales La Araucanía

Buy tickets

Enter the passenger RUT to continue to checkout.

Usage

import * as rut from '@dud-cl/rut'

const result = rut.safeParse('21.272.789-K', 'en')

if (result.success) {
  const canonical = result.output       // '21272789K'
  const display = rut.format(canonical) // '21.272.789-K'
} else {
  console.error(result.issue.message)
}

API

parse(input, language?)

Parameters
  • input: unknown
  • language: 'es' | 'en'; default 'es'
Returns
Rut. Branded canonical string without dots or hyphen; uppercase K.
Failure
Throws RutError with the exact RutIssue on error.
rut.parse('21.272.789-K') // '21272789K'
rut.parse('9.068.826-k')    // '9068826K'

safeParse(input, language?)

Parameters
  • input: unknown
  • language: 'es' | 'en'; default 'es'
Returns
SafeParseResult. Success contains output; failure contains one structured issue.
Failure
Does not throw for invalid RUT input.
rut.safeParse('21.272.789-K', 'en')
// { success: true, output: '21272789K' }

rut.safeParse('21.272.789-0', 'en')
// { success: false, issue: { kind: 'verifier', ... } }

is(input)

Parameters
  • input: unknown
Returns
boolean. true only when input passes strict parsing.
rut.is('21272789k')    // true
rut.is('21.272.789-0') // false
rut.is(189726317)      // false

format(value, options?)

Parameters
  • value: Rut returned by parse or safeParse
  • options.dots: boolean; default true
  • options.uppercase: boolean; default true
Returns
string with a hyphen; dots and uppercase K follow the selected options.
const value = rut.parse('21.272.789-K')

rut.format(value)                                      // '21.272.789-K'
rut.format(value, { dots: false })                     // '21272789-K'
rut.format(value, { uppercase: false })                // '21.272.789-k'
rut.format(value, { dots: false, uppercase: false })   // '21272789-k'

formatPartial(input)

Parameters
  • input: unknown
Returns
string formatted for live editing. Unsupported or overlong text is returned unchanged.
Failure
Potentially unsafe normalization. It can make invalid raw syntax parseable and never validates body length or the verifier. Validate raw input when exact syntax matters, or call safeParse on the output.
rut.formatPartial('1')           // '1'
rut.formatPartial('17353')       // '1.735-3'
rut.formatPartial('189726317')   // '18.972.631-7'
rut.formatPartial('9068826k')    // '9.068.826-K'
rut.formatPartial('RUT: 189726317') // unchanged

clean(input)

Parameters
  • input: unknown
Returns
string. Non-string input returns ''. Removes non-digits/K, leading zeros, and uppercases K.
Failure
Does not validate format, body length, or verifier.
rut.clean('0021.272.789-k')        // '21272789K'
rut.clean('chuma1996@gmail.com') // '1996K'
rut.clean(189726317)              // ''
rut.is(rut.clean('chuma1996@gmail.com')) // false

compare(left, right)

Parameters
  • left: unknown
  • right: unknown
Returns
boolean. true only when both inputs are valid and have the same canonical value.
rut.compare('21.272.789-K', '21272789K') // true
rut.compare('21.272.789-K', '9.068.826-k') // false
rut.compare('21.272.789-0', '21.272.789-0') // false
rut.compare(null, null) // false

getVerifier(body)

Parameters
  • body: unknown; must be a string with 7 or 8 body digits
Returns
string verifier or null. Ignores dots, commas, and hyphens in the body.
rut.getVerifier('18.657.499-') // '0'
rut.getVerifier('21.272.789')  // 'K'
rut.getVerifier('9068826')     // 'K'
rut.getVerifier(18972631)      // null
rut.getVerifier('123456789')   // null
rut.getVerifier('0000000')     // null

Integrations

Zod

Keep the RUT message and return a canonical, branded Rut.

import { z } from 'zod'
import * as rut from '@dud-cl/rut'

const rutSchema = z.unknown().transform((input, context) => {
  const result = rut.safeParse(input, 'en')

  if (!result.success) {
    context.addIssue({
      code: 'custom',
      message: result.issue.message,
      params: { rutIssue: result.issue },
    })
    return z.NEVER
  }

  return result.output
})

const result = rutSchema.safeParse('21.272.789-0')

if (!result.success) {
  const issue = result.error.issues[0]

  if (issue?.code === 'custom') {
    const rutIssue = issue.params?.rutIssue as rut.RutIssue | undefined
    rutIssue?.kind // 'verifier'
  }
}

Zod always keeps the message. If another form library removes rutIssue, use safeParse directly when you need kind.

React

Format as the user types and show the exact safeParse message.

Warning: formatPartial only formats. It can make previously invalid input acceptable, so always validate its output with safeParse.

import { useState, type FormEvent } from 'react'
import * as rut from '@dud-cl/rut'

export function RutForm({ onValid }: { onValid: (value: rut.Rut) => void }) {
  const [input, setInput] = useState('')
  const [validation, setValidation] =
    useState<rut.SafeParseResult | null>(null)

  const issue = validation?.success === false ? validation.issue : null

  function edit(raw: string) {
    const next = rut.formatPartial(raw)
    setInput(next)
    setValidation(next ? rut.safeParse(next, 'en') : null)
  }

  function submit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    if (validation?.success) onValid(validation.output)
  }

  return (
    <form onSubmit={submit} noValidate>
      <label htmlFor="rut">Chilean RUT</label>
      <input
        id="rut"
        name="rut"
        value={input}
        onChange={(event) => edit(event.target.value)}
        aria-invalid={issue !== null}
        aria-describedby={issue ? 'rut-error' : undefined}
      />
      {issue && (
        <p id="rut-error" data-rut-issue={issue.kind}>
          {issue.message}
        </p>
      )}
      <button disabled={!validation?.success}>Continue</button>
    </form>
  )
}