ipaddr.js is the address library you can paste into a Node service
IP address manipulation library in JavaScript
At a glance
- What is it?
- ipaddr.js parses, validates, classifies and converts IPv4 and IPv6 addresses in JavaScript, with no runtime dependencies, a throwing and a non-throwing API, named range matching, and the mapped address handling dual stack sockets need. It is MIT licensed, a single JavaScript file with a TypeScript declaration beside it, and its test suite runs on the Node built in test runner.
- Who is it for?
- ipaddr.js suits a Node or browser codebase that has to make sense of an address from an untrusted source, since the validation entry points never throw, the parse entry points do, and the classification helpers give you a named range rather than a boolean.
- 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 26 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One file, no dependencies, MIT licensed
ipaddr.js is a library for manipulating IPv4 and IPv6 addresses in JavaScript, and it runs in Node.js and in browsers. The manifest shows how little it is. The package is version 3.0.0, marked as an ES module, and the published files are a single implementation file and its TypeScript declaration, with the main entry and the export map both pointing at the same file. There are no runtime dependencies at all; the only dependencies are development tools, an ESLint configuration and the linter itself, and the test script is the Node built in test runner rather than a test framework. The engines field asks for Node 12 or newer, and the README keeps a compatibility table, with 3.x for Node 12 and above, 2.x for Node 10 and above, and 1.x for older Node releases. That combination, a single file with no transitive dependencies, is the reason this kind of library is easy to adopt, and it is also the reason there is no build step to run before you use it.
Two ways to parse, one that throws and one that does not
The global API is organised around a deliberate split. The validation functions never throw, so isValid returns true or false for a string that may be anything at all, and isValidCIDR does the same for CIDR notation.
ipaddr.isValid('192.168.1.1'); // => true
ipaddr.isValid('2001:db8::1'); // => true
ipaddr.isValid('999.0.0.1'); // => false
ipaddr.isValid('hello'); // => falseThe parsing functions do throw, and that is the point, because parse returns an IPv4 or IPv6 object and parseCIDR returns a two element array of the address and the prefix length.
const [addr, prefix] = ipaddr.parseCIDR('192.168.1.0/24');
addr.toString(); // => '192.168.1.0'
prefix; // => 24The result of parseCIDR can be handed straight to the match method on an address, so a containment check is a single call rather than a comparison you write yourself. The quick start in the README shows the whole surface in a few lines, with isValid checks for both families, a parse, a kind call that tells you which family you got, and a CIDR parse.
const ipaddr = require('ipaddr.js');
ipaddr.isValid('192.168.1.1'); // => true
ipaddr.isValid('2001:db8::1'); // => true
ipaddr.isValid('not an address'); // => falseFor a service that handles addresses from headers, query strings or user input, the presence of a non-throwing path is what stops a malformed address from becoming an unhandled exception.
process() exists because dual stack sockets lie to you
The process function is the one that shows the library was written by someone who has debugged this. It behaves like parse, except that it automatically converts IPv4-mapped IPv6 addresses such as the IPv4 form embedded in IPv6 notation into their IPv4 equivalents, and returns all other addresses unchanged.
ipaddr.process('::ffff:192.168.1.1').toString(); // => '192.168.1.1'
ipaddr.process('::ffff:192.168.1.1').kind(); // => 'ipv4'
ipaddr.process('2001:db8::1').kind(); // => 'ipv6'
ipaddr.process('192.168.1.1').kind(); // => 'ipv4'The README states the case this exists for: when you accept connections on a dual stack IPv6 socket, the IPv4 client addresses arrive as IPv4-mapped IPv6 addresses. If you classify those directly, every IPv4 client looks like an IPv6 client, and any rule you wrote about private ranges or loopback will silently stop matching. Converting at the boundary is the fix, and the library gives you one call for it. There is a matching instance method in the IPv6 API, isIPv4MappedAddress, so the same check is available if you would rather decide for yourself than have the conversion done.
Named range matching turns a list of CIDRs into a label
The subnetMatch function is how the library classifies an address, and the shape of it is worth reading. You pass an address and a map of named CIDR ranges, and it returns the name of the first matching range, or the default name, which is unicast unless you pass one.
const rangeList = {
private: [
ipaddr.parseCIDR('10.0.0.0/8'),
ipaddr.parseCIDR('172.16.0.0/12'),
ipaddr.parseCIDR('192.168.0.0/16'),
],
loopback: ipaddr.parseCIDR('127.0.0.0/8'),
};
ipaddr.subnetMatch(ipaddr.parse('192.168.1.1'), rangeList); // => 'private'
ipaddr.subnetMatch(ipaddr.parse('127.0.0.1'), rangeList); // => 'loopback'
ipaddr.subnetMatch(ipaddr.parse('8.8.8.8'), rangeList, 'public'); // => 'public'
ipaddr.subnetMatch(ipaddr.parse('8.8.8.8'), rangeList); // => 'unicast'Two details make it practical. A value in the map can be a single pair or an array of pairs, so a name can cover several disjoint ranges, and a list may mix IPv4 and IPv6 entries, with entries of the wrong address family safely skipped rather than throwing. The ranges themselves are yours to define, which keeps the library free of an address database. The README also notes a TypeScript wrinkle, since using parseCIDR preserves the tuple type while a separately declared array literal is inferred as a general array and cannot be passed where a CIDR tuple is expected, so the example above works in both languages. There is a per instance range method as well, which reports the named category of an address against the built in list of loopback, private, reserved and similar ranges.
IPv6 needs three different string forms
The IPv6 API is where the size of the library shows, because a single IPv6 address has several legitimate text representations and different consumers want different ones. There is toString, which gives the compact form, toNormalizedString, and toRFC5952String, which follows the recommended format for textual representation, plus toFixedLengthString, which pads every group to four characters. There is also the zone identifier accessor for link local addresses, and parts, which exposes the eight groups. The static helpers mirror the IPv4 ones, so you can compute the broadcast address from a CIDR, the network address from a CIDR, and the subnet mask from a prefix length, and the IPv6 side additionally offers toIPv4Address, the inverse of the mapped address conversion, and toByteArray for working with raw bytes. The byte oriented API is shared with IPv4 and goes the other way too, with fromByteArray constructing an address from four or sixteen bytes in network byte order, and throwing if the length is anything else. For a service that has to render an address for a log line, for a firewall rule or for a comparison against a stored value, having all three string forms available avoids the kind of mismatch where two systems call the same address different things and never agree.
IPv4 keeps the legacy decimal forms out of the main validators
The IPv4 API carries a set of functions that exist because history left a mess. isValidFourPartDecimal and isValidCIDRFourPartDecimal validate the old decimal notation, and they are separate from isValid and isValidCIDR, so a string that looks like an address in the historical form is not silently accepted by the normal validators. Alongside them sit the network helpers, broadcastAddressFromCIDR, networkAddressFromCIDR and subnetMaskFromPrefixLength, and the reverse conversion, prefixLengthFromSubnetMask, so you can move between a prefix length and a mask representation in either direction. The instance methods cover the octets, the kind, matching, range classification, subnet matching, conversion to a byte array, conversion to an IPv4-mapped IPv6 address, the normalized string and the plain string. The IPv4-mapped conversion is the counterpart of what process does for you, and it is useful when you need to build the mapped form rather than consume it. The normalisation methods exist for the same reason as the IPv6 ones, so that two code paths that each produce a string from the same address produce the same text.
The test suite runs on the Node test runner
The development setup is minimal and modern. The test script is the Node built in test runner, and the coverage script adds the experimental coverage flag of that runner, so the project does not carry a test framework as a dependency. Linting is ESLint 10 over the library directory, with a separate fix script that also covers the tests, and the configuration is an ES module file at the top of the tree. The repository itself is small, with a lib directory holding the implementation and the declaration file, a test directory, a Changes.md changelog, an eslint configuration and the readme. There are no GitHub releases, so the npm version is the version to pin, and the last push was on 2026-09-27. The licence is MIT, which means the code is fine to vendor into a closed source product, and the author is listed in the manifest with an email address, which is the route for an issue that the tracker does not cover.
Editorial conclusion
ipaddr.js suits a Node or browser codebase that has to make sense of an address from an untrusted source, since the validation entry points never throw, the parse entry points do, and the classification helpers give you a named range rather than a boolean. It is the wrong choice for anything that needs a full IP database, because the range knowledge here is the small set of named categories rather than a geolocation or carrier table, and it is the wrong choice if you want a subnet planner, since it holds no prefix aggregation. Verify three things before you adopt it: that the address forms your input actually uses are accepted, since the four part decimal IPv4 forms are a separate set of validators, that the semantics of the range classification match what your logs will claim, and that your Node version is supported, since the manifest asks for Node 12 or newer while the README separates the 3.x, 2.x and 1.x lines by Node version. The licence is MIT and the last push was on 2026-09-27.
Frequently asked questions
How do I install ipaddr.js and check whether a string is an address?
Install it with npm install ipaddr.js, then call ipaddr.isValid for an address or ipaddr.isValidCIDR for CIDR notation. Both return true or false and never throw, while ipaddr.parse and ipaddr.parseCIDR throw on invalid input and return an address object or a tuple.
What does ipaddr.process do differently from ipaddr.parse?
It converts IPv4-mapped IPv6 addresses such as the IPv4 form inside IPv6 notation into their IPv4 equivalents, and returns everything else unchanged. The README points at dual stack IPv6 sockets, where IPv4 client addresses arrive in the mapped form and would otherwise be classified as IPv6.
How do I classify an address as private or loopback with ipaddr.js?
Use ipaddr.subnetMatch with a map of named CIDR ranges, where each value is a single pair or an array of pairs from parseCIDR, and it returns the name of the first matching range, or the default name you pass, which is unicast otherwise. Each address also has a range method against the built in categories.
Does ipaddr.js have any runtime dependencies?
No. The package manifest lists only development dependencies, an ESLint configuration and the linter, and the published files are a single implementation file with its TypeScript declaration. The test script uses the Node built in test runner, and the engines field asks for Node 12 or newer.
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/whitequark-ipaddr-js)Was this useful?