Ow: runtime argument validation for JavaScript and TypeScript functions
Function argument validation for humans
At a glance
- What is it?
- Ow is a small npm package that checks function arguments, environment variables and CLI input at runtime while narrowing TypeScript types. It is narrow by design, and that is the point.
- Who is it for?
- Adopt Ow when your boundary is a function signature: constructors, CLI parsing, config loaders and utility functions that take unknown input. Skip it for API response schemas, database rows or large nested JSON, where the README points at zod instead.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap Ow fills between TypeScript's compiler and real input
TypeScript erases itself at compile time. A function annotated to accept a string can still be handed a number by a JSON parser, an environment variable, or a caller in untyped JavaScript, and the compiler has nothing to say about it. Ow exists for that boundary. The README frames the problem plainly: TypeScript validates types at compile time, and once compiled the program can still receive unexpected data from function arguments, CLI flags, environment variables and configuration files.
The intended user is a library or application author who controls a function's contract and wants the failure to happen at the call site rather than three stack frames deeper. The README's own example is a unicorn function that takes input, calls ow(input, ow.string.minLength(5)), and then proceeds. Passing 3 produces an ArgumentError naming the parameter and the expected type. Passing 'yo' produces a different message about the minimum length.
The README is equally clear about the boundary. For complex schema validation such as API responses, database queries or JSON parsing, it directs readers to zod. That is a deliberate scope decision, and it is the most useful sentence in the document.
How the chainable predicate API and label inference actually work
The core call is ow(value, predicate) or ow(value, label, predicate). The predicate is built from a chain: ow.number.positive.integer, ow.string.minLength(5), ow.string.oneOf(['development', 'production', 'test']). Each modifier returns a predicate, and the final object carries the accumulated checks. When a check fails, Ow throws an ArgumentError.
Labels are the part worth understanding before you adopt it. Ow infers the argument name automatically in Node.js, so the error can say Expected `input` to be of type `string`. The README states this inference does not work in the browser, and that you can override it by passing a label explicitly. The package.json confirms the mechanism: the browser field maps ./dist/utils/infer-label.js to ./dist/utils/infer-label.browser.js, so the browser build replaces the stack-inspection module with something that cannot inspect the call site. In bundled front-end code, expect to pass labels yourself.
There are three non-throwing paths. ow.isValid returns a boolean. ow.validate returns a discriminated union of {success: true; value: T} or {success: false; error: ArgumentError}, which lets TypeScript narrow on the success property. ow.create builds a reusable validator, optionally with a fixed label, so the same check can be applied in several places. ow.any(...predicates) accepts a value matching any of the supplied predicates. The README also documents ow.isPredicate for higher-order functions that need to tell a predicate apart from an ordinary value.
The type-guard behaviour is the strongest argument for using Ow over a hand-written if statement. After ow(input, ow.string), the README shows that input.slice(0, 3) compiles, where it did not before the call. That narrowing is visible to the compiler because the predicate carries type information, not just a runtime check.
Installing Ow and validating a config object on startup
Ow is a normal npm package. The README gives one install command, and package.json pins the runtime requirement at Node 20 or newer through the engines field, with the package published as an ES module ("type": "module").
npm install owA first real use is startup configuration. The README's configuration example reads three environment variables into an object and validates each one before the application proceeds. The commands below assume a Node 20+ project that can load ES modules.
import ow from 'ow';
const config = {
port: process.env.PORT,
apiKey: process.env.API_KEY,
nodeEnv: process.env.NODE_ENV
};
ow(config.port, ow.string.numeric);
ow(config.apiKey, ow.string.minLength(32));
ow(config.nodeEnv, ow.string.oneOf(['development', 'production', 'test']));
const port = parseInt(config.port, 10);With a valid environment this block runs and port is a parsed number. With API_KEY set to something shorter than 32 characters, it throws an ArgumentError instead of letting a malformed key reach the network layer. Note that process.env values are always strings, which is why the README validates port with ow.string.numeric and converts afterwards rather than validating a number directly.
If Ow is only a development aid, the README documents a second entry point. Import from 'ow/dev-only' instead of 'ow' and build with NODE_ENV set to production.
NODE_ENV="production" parcel build index.jsIn that configuration the dev-only entry exports a shim, which the README says should result in a significantly lower bundle size. The package.json exports map backs this up: the ./dev-only subpath resolves to ./dev-only.js at runtime while sharing the same type declarations. Verify the shim in your own build output, because the swap depends on your bundler honouring NODE_ENV, not on Ow.
Where Ow stops being the right tool
Ow validates a value against a predicate. It does not parse, coerce or transform, and it does not describe a nested document shape. The README says this outright, recommending zod for API responses, database queries and JSON parsing. If your input is a deeply nested payload from a third-party service, Ow's chainable predicates will not express it comfortably, and you will end up writing custom validations for each level.
A second limitation is browser label inference. Because the browser build replaces infer-label.js, error messages in front-end code will not name the variable unless you supply a label. That is a real difference in developer experience between the same library running on the server and in the browser, and it is easy to miss until a production error message reads less helpfully than the one you saw locally.
The dev-only path has its own failure mode. It is a build-time contract: if NODE_ENV is not set to production when the bundler runs, you get the full Ow implementation and none of the size benefit. Nothing in the package can detect a misconfigured build for you.
Finally, Ow throws by default. In code that must not throw, for example a request handler that should return a 400 rather than crash, you have to use ow.validate or ow.isValid and handle the result yourself. The throwing form is the documented default and the examples lean on it.
Ow against zod: assertion versus schema parsing
The README names zod as the alternative for complex schema validation, and the difference is architectural rather than a matter of feature count. Ow asserts: you hand it a value and a predicate, and it either returns nothing or throws. The predicate is a chain of checks attached to a single value. Zod parses: you define a schema, and parsing returns a new typed value, which means it can handle nested objects, transform input, and report multiple issues at once.
That distinction decides most adoption questions. Validating a constructor argument or a CLI flag is an assertion problem, and Ow's error messages, which name the parameter and describe the failed constraint, are tuned for exactly that. Validating a webhook body with optional nested fields is a parsing problem, and Ow has no schema composition to offer. The two can coexist in one codebase: Ow at function boundaries, a schema library at the network edge.
The practical cost difference is dependency weight. Ow pulls in @sindresorhus/is, callsites, dot-prop, environment, fast-equals and is-identifier. A schema library brings its own tree. Neither is free, and the dev-only entry point exists precisely because Ow's authors expect the size to matter in browser bundles.
Maintenance, release history and the MIT licence
The repository is not archived, and the last push was on 2026-09-18. Releases are versioned and recent: v3.0.0 on 2025-09-09, v3.1.0 on 2025-10-10, and v3.1.1 on 2025-10-16, which matches the version in package.json. The project is published as an ES module with a Node 20 floor, so upgrading across a major version is the event to plan for, as v3.0.0 implies a breaking change from v2. The README does not document a migration path or a rollback procedure, so pin the version and read the release notes before moving a major.
Ow is MIT licensed, which permits commercial and closed-source use with the licence and copyright notice retained. That is a permissive arrangement, but it is not legal advice; check how your organisation handles third-party notices. The package.json also lists a funding URL for the author, which is voluntary and has no bearing on the licence terms.
Upgrade cost is low in normal use. The public surface is small: ow, ow.isValid, ow.validate, ow.isPredicate, ow.create and ow.any, plus the predicate chains. The dev-only entry point is the one piece tied to your build configuration, so a bundler change is more likely to break that path than an Ow release is.
Editorial conclusion
Adopt Ow when your boundary is a function signature: constructors, CLI parsing, config loaders and utility functions that take unknown input. Skip it for API response schemas, database rows or large nested JSON, where the README points at zod instead. Before you commit, run the dev-only import path against your bundler with NODE_ENV set to production and confirm the shim is what ends up in the output, because that swap is the one behaviour that depends entirely on your build setup rather than on Ow itself.
Frequently asked questions
What is Ow used for in a TypeScript project?
Ow performs runtime validation of function arguments and other untrusted input, throwing an ArgumentError when a value fails its predicate. The README also notes that using it narrows the TypeScript type of a previously-unknown value, so code after the call can use the value safely.
How do I install Ow and import it?
The README gives npm install ow, and the package is published as an ES module requiring Node.js 20 or newer according to package.json. You then import the default export with import ow from 'ow'.
Should I use Ow or zod for validating API responses?
The README recommends zod for complex schema validation such as API responses, database queries and JSON parsing, and positions Ow for function arguments, CLI flags, environment variables and configuration files. Ow validates a single value against a predicate and does not compose nested schemas.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sindresorhus-ow)