# Voluptuous: schema validation with dicts, lists, and callables

> Voluptuous validates data coming into Python from JSON or YAML, and its schemas are ordinary Python data structures rather than a separate schema language. A validator is a callable, an error is a raised exception, and the library is now maintained by contributions only after its author stopped using it.

**alecthomas/voluptuous** — CONTRIBUTIONS ONLY: Voluptuous, despite the name, is a Python data validation library.

- Repository: https://github.com/alecthomas/voluptuous
- Website: https://pypi.org/project/voluptuous
- Stars: 1,853 · Forks: 247
- Language: Python
- License: BSD-3-Clause
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/alecthomas-voluptuous

## A schema is a dictionary

Voluptuous is a Python data validation library, and it is primarily intended for validating data coming into Python as JSON, YAML, and the like. The project states three goals: simplicity, support for complex data structures, and providing useful error messages.

The design decision that defines the library is that schemas are basic Python data structures. Should your data be a dictionary of integer keys to strings? Then {int: str} does what you expect. A list of integers, floats, or strings is [int, float, str]. A list of dictionaries is [{}], which reads oddly until you notice it is exactly the same construct as a list of a single empty dictionary schema.

The consistency rule is the thing to internalise. Types in the schema are checked as types, values are compared as values, and callables are called to validate. There is no separate schema language, no validation engine to configure, and no declarative layer to learn.

That is also what makes the library unusual in one respect and ordinary in another. The README says it is designed from the ground up for validating more than just forms, and nested data structures are treated in the same way as any other type, which is the opposite of how form validation libraries usually work, where a nested structure means a different class.

The project is BSD-3-Clause licensed, the home page is the PyPI project page, and the last push to the repository was on 2026-10-12. The recent releases are 0.16.0 from 2025-12-18, 0.15.2 and 0.15.1 both from July 2024.

## The example that explains the whole API

The README builds a schema for a Twitter user search endpoint, and the progression is the clearest documentation of how the library works.

The first attempt is the obvious one:

```pycon
>>> from voluptuous import Schema
>>> schema = Schema({
...   'q': str,
...   'per_page': int,
...   'page': int,
... })
```

That describes the shape of the data and nothing else. The API documentation says per_page should be restricted to at most 20 and defaults to 5, so the schema needs to be more thoroughly defined:

```pycon
>>> from voluptuous import Required, All, Length, Range
>>> schema = Schema({
...   Required('q'): All(str, Length(min=1)),
...   Required('per_page', default=5): All(int, Range(min=1, max=20)),
...   'page': All(int, Range(min=0)),
... })
```

Three ideas appear at once. Required marks a key as mandatory, and with a default it also supplies a value when the key is missing. All composes several validators so that a value must satisfy all of them. And the constraint helpers, here Length for strings and Range for numbers, express the API's actual semantics rather than just its types.

The result is that this schema fully enforces the interface defined in the documentation, and goes a little further for completeness. That last part is the difference between validating a shape and validating a contract.

## Error messages, which are the point

The three goals include providing useful error messages, and the README demonstrates them by showing exactly what a failure looks like.

A missing required key produces required key not provided @ data['q']. A wrong type produces expected str for dictionary value @ data['q']. An empty string produces length of value must be at least 1 for dictionary value @ data['q']. A per_page of 900 produces value must be at most 20 for dictionary value @ data['per_page'], and a per_page of minus 10 produces value must be at least 1 for dictionary value @ data['per_page'].

Each message names the path to the offending value, and the path syntax is the same in every case, which means you can write a handler that extracts the location and turn the error into a form field error. The trailing at data['q'] is a path into the input, not a position in the schema, so it points at what the user actually sent.

The exception type is MultipleInvalid, and the README's examples all catch it and then assert on the string. That is the intended usage: catch the exception, and use the message.

The two other design choices support that. Validators are simple callables, so no need to subclass anything, just use a function. And errors are simple exceptions, so a validator can just raise Invalid with a message and expect the user to get useful messages. Both of those are what make it possible to write your own constraint as a plain function and have it behave exactly like the built-in ones, including the error message format.

## Match semantics, node by node

The Defining schemas section explains the model: schemas are nested data structures consisting of dictionaries, lists, scalars, and validators, and each node in the input schema is pattern matched against corresponding nodes in the input data.

Literals are matched with normal equality checks, so Schema(1) accepts 1 and Schema('a string') accepts that exact string. Types are matched by checking whether the corresponding value is an instance of the type, so Schema(int) accepts 1 and rejects 'one'.

The practical consequence of that is that a schema is not a separate object that gets compiled. It is a value you build once and call, and the call either returns the validated data or raises. The README's examples show both halves of that, with the schema called on a dictionary and the result compared against the expected dictionary including the defaults that were filled in:

```pycon
>>> schema({'q': '#topic'}) == {'q': '#topic', 'per_page': 5}
True
>>> schema({'q': '#topic', 'page': 1}) == {'q': '#topic', 'page': 1, 'per_page': 5}
True
```

Note that the returned value includes per_page even when the input did not, because the default was applied. A validator in this library transforms as well as checks, and the result is a new structure rather than a boolean.

The examples directory also has a dataclasses example, which is the one place the library reaches outside plain dicts and lists. The project ships a py.typed marker in the package data, so type checkers use the library's annotations, and the mypy configuration in the project is strict about it.

## Contributions only, and what that means in practice

The first thing in the README is a section titled CONTRIBUTIONS ONLY, and the explanation is unusually direct.

What does this mean? The author does not have time to fix issues himself, and the only way fixes or new features will be added is by people submitting pull requests. The current status is that Voluptuous is largely feature stable, that there has not been a need to add new features in a while, but that there are some bugs that should be fixed.

The reason is given next. The author no longer uses Voluptuous personally, and in fact no longer regularly writes Python code. Rather than leave the project in a limbo of people filing issues and wondering why they are not being worked on, he believes this notice will more clearly set expectations.

That is a more useful signal than an open issue tracker, and it tells you how to read the release history. The releases have continued at a low rate, with 0.16.0 in December 2025 and two releases on the same day in July 2024, so the project is maintained by whoever sends a patch rather than by a maintainer with a roadmap.

There is still a maintained surface. There is a mailing list for the project, there is a Gitter chat, and the author can be contacted directly by email. For a bug, the instruction is to create a new issue with a short example of how to replicate it, and for a fix or a feature, a pull request is the only route.

The maintenance tooling in the repository is current rather than abandoned. The pyproject configures black with the target versions py39 through py312, isort with the black profile, mypy against Python 3.9 with unused ignores warned about, and pytest with the doctest glob for markdown files enabled and the verbose flag, so the examples in the README are executed as part of the test run. The test files are named tests.py and live in voluptuous/tests, and there is a tox.ini, a renovate configuration, and a coveralls configuration at the root.

## Conclusion

Adopt Voluptuous if you validate untrusted JSON or YAML and want schemas that read as ordinary Python, especially if you are migrating from a form validation library and want one model for both. Do not adopt it for a project that needs active maintenance of new features, because the project is contributions only. Verify first that the constraint helpers you need exist, since the library is described as largely feature stable and the constraint vocabulary is the part that grew over its life.

## FAQ

### What is Voluptuous and what is it for?

It is a Python data validation library, primarily intended for validating data coming into Python as JSON, YAML, and the like. Its three stated goals are simplicity, support for complex data structures, and providing useful error messages.

### How are schemas written in Voluptuous?

As ordinary Python data structures. A dictionary of integer keys to strings is {int: str}, a list of integers, floats, or strings is [int, float, str], and a list of dictionaries is [{}]. Types are checked as types, values are compared as values, and callables are called to validate.

### How do I express constraints like a maximum value in Voluptuous?

By composing validators with All and using the constraint helpers, so Required('per_page', default=5) mapped to All(int, Range(min=1, max=20)) both bounds the value and supplies a default when the key is absent.

### What errors does Voluptuous raise?

MultipleInvalid, which is caught and whose message names the path to the offending value, for example required key not provided @ data['q'] or expected str for dictionary value @ data['q']. Validators can also simply raise Invalid with a message of their own.

### Is Voluptuous still actively developed?

It is largely feature stable and the README is headed CONTRIBUTIONS ONLY, since the author no longer uses the library and does not have time to fix issues. Fixes and features now come only from pull requests, and the project is BSD-3-Clause licensed with the current release at 0.16.0 from 2025-12-18.

## Sources

- [alecthomas/voluptuous on GitHub](https://github.com/alecthomas/voluptuous)
- [License: BSD-3-Clause](https://github.com/alecthomas/voluptuous/blob/master/LICENSE)
- [Project website](https://pypi.org/project/voluptuous)
- [README](https://github.com/alecthomas/voluptuous/blob/master/README.md)
- [Releases](https://github.com/alecthomas/voluptuous/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/alecthomas-voluptuous
