Right, let’s talk about the various ways PostgreSQL can remember where things live and how to talk to them. We’re not just storing ‘www.example.com’ here; we’re dealing with the raw, structured numbers that actually make the network work. This is for when you need to know if an IP is within a range, validate a MAC address, or just store this stuff without losing your mind trying to do it in a string.

PostgreSQL gives us a tidy little toolbox for this: inet, cidr, macaddr, and the newer macaddr8. They look similar to strings, but they’re actually intelligent types. They validate input, they have specialized operators, and they save you from a world of tedious parsing and error checking. Using a text field for an IP address is like using a shoebox to organize your wrenches—it works until you need to find the 10mm socket. These types are the proper wrench roll.

The inet and cidr types: hosts and networks

inet holds an IPv4 or IPv6 host address, optionally with its subnet mask. cidr holds an IPv4 or IPv6 network specification. This is the crucial difference: inet is for a specific machine (e.g., 192.168.1.1/24), while cidr is for the network itself (e.g., 192.168.1.0/24).

Why have both? Because their validation rules are different. The cidr type is strict; it will always silence host bits that aren’t part of the network. If you try to store 192.168.1.255/24 in a cidr column, PostgreSQL normalizes it to 192.168.1.0/24. The inet type, on the other hand, is more forgiving. It will happily store that address with the host bits set, because for a host, that’s a perfectly valid broadcast address.

-- inet is cool with host-specific addresses
SELECT '192.168.1.255/24'::inet;  -- returns '192.168.1.255/24'

-- cidr silently (and correctly) zeroes out the host bits for the network
SELECT '192.168.1.255/24'::cidr;  -- returns '192.168.1.0/24'

The real magic is in the operators. You can check if an inet address is contained within a cidr network using the <@ (contained within) operator.

-- Is this address part of our internal network?
SELECT '192.168.1.33'::inet <@ '192.168.1.0/24'::cidr; -- true

-- Is Google's DNS on the local network? (Spoiler: no)
SELECT '8.8.8.8'::inet <@ '192.168.1.0/24'::cidr; -- false

Common Pitfall: The most common mistake is using the wrong type. If you’re storing individual device addresses (a server, a user’s connection), use inet. If you’re storing network definitions (e.g., for a firewall rules table), use cidr. Putting a /32 on every inet entry is also a bit of a tell-tale sign of someone new to networking; it’s technically correct but often redundant unless you really care about the specific subnet mask of that individual host.

The macaddr and macaddr8 types: hardware addresses

These store MAC addresses, the unique identifiers burned into your network hardware. The classic macaddr type is fine for most purposes, but it has a quirk: it only handles the traditional 48-bit MAC addresses (6 bytes). The newer macaddr8 type handles both 48-bit and the extended 64-bit addresses (8 bytes) used in IPv6.

The great thing about both is their rigorous input validation and flexible formatting. They’ll accept just about any common separator (or none at all) and output in a canonical form. This alone is worth using them over text.

-- They all normalize to the same thing
SELECT
    '08:00:2b:01:02:03'::macaddr,
    '08-00-2b-01-02-03'::macaddr,
    '08002b:010203'::macaddr; -- All return '08:00:2b:01:02:03'

-- macaddr8 handles the longer format
SELECT '08:00:2b:01:02:03:04:05'::macaddr8; -- '08:00:2b:01:02:03:04:05'

Best Practice: Unless you’re absolutely certain you’ll never encounter EUI-64 addressing (common in IPv6 scenarios), default to using macaddr8 for new applications. It’s more future-proof and handles all the same formats as macaddr.

Why not just use text?

I know what you’re thinking. “This seems neat, but I could just slap a CHECK CONSTRAINT on a text column and call it a day.” You could. But you’d be writing and maintaining that validation regex yourself (have fun with IPv6, by the way). You’d be missing out on all the built-in operators (<@, <<, >>) that work correctly with the actual semantics of the data. You’d lose the built-in formatting and the guaranteed data integrity. The database is offering you a free, tested, and incredibly robust solution. Take it. It’s one of those choices that seems pedantic at the start but saves you from a nightmare of data quality issues later.