⚡ Node.jsNetworking / IoT

MAC Address Validation in Node.js

Validate MAC (Media Access Control) addresses in Node.js — accept colon, hyphen, or compact formats, detect address types (unicast, multicast, broadcast), identify locally-administered (randomised) MACs, and normalise to a canonical form for storage.

Also available in Python · For IMEI + MAC together see the IoT Device Validation guide

1. MAC address structure

A MAC address is a 48-bit (6-byte) identifier. The first 3 bytes form the OUI (Organizationally Unique Identifier) assigned to the manufacturer; the last 3 bytes are the device serial number.

  00 : 1B : 44 : 11 : 3A : B7
     
  OUI (3 bytes)   NIC-specific (3 bytes)
  Manufacturer    Device serial

Bit 0 of byte 0:  0 = unicast,  1 = multicast
Bit 1 of byte 0:  0 = globally unique (OUI-assigned)
                  1 = locally administered (may be randomised)

2. API response structure

Endpoint: GET /v0/mac-address?value=…

{
  "valid": true,
  "normalized": "00:1B:44:11:3A:B7",
  "format": "colon",
  "type": "unicast",
  "isMulticast": false,
  "isLocal": false,
  "isBroadcast": false
}
  1. Check valid
  2. Store normalized as canonical form (colon-separated uppercase)
  3. Reject isMulticast and isBroadcast for device registration
  4. Warn on isLocal — likely a privacy/randomised MAC

3. Accepted input formats

// All three formats are accepted — normalized is always colon-separated uppercase
await iv.macAddress('00:1B:44:11:3A:B7')   // colon
await iv.macAddress('00-1B-44-11-3A-B7')   // hyphen
await iv.macAddress('001B44113AB7')         // compact
await iv.macAddress('00:1b:44:11:3a:b7')   // lowercase — also accepted
// All return normalized: "00:1B:44:11:3A:B7"

4. Address types

TypeExampleSuitable for device ID?
unicast + globally unique00:1B:44:11:3A:B7✓ Yes — manufacturer-assigned
unicast + locally administered02:xx:xx:xx:xx:xx⚠️ Warn — may be randomised by OS
multicast01:xx:xx:xx:xx:xx✗ No — group address, not a device
broadcastFF:FF:FF:FF:FF:FF✗ No — reserved broadcast address

5. Locally-administered (randomised) addresses

⚠️iOS 14+, Android 10+, and Windows 10+ randomise Wi-Fi MAC addresses per network by default. If isLocal: true, the address may change when the user reconnects to a different network or resets their device. Do not use as a stable device identifier without an explicit user opt-out of MAC randomisation.
const result = await iv.macAddress(mac)
if (result.valid && result.isLocal) {
  // Log a warning — the user's device may be using MAC randomisation
  logger.warn({ mac: result.normalized }, 'Locally-administered MAC — may be randomised')

  // Option: require the user to disable MAC randomisation for this network
  // and resubmit, or fall back to a different device identifier (IMEI, UUID)
}

6. Validation with Promise.all

import { createClient } from '@isvalid-dev/sdk'

const iv = createClient({ apiKey: process.env.ISVALID_API_KEY! })

async function validateMac(mac: string) {
  const result = await iv.macAddress(mac)

  if (!result.valid) throw new Error(`Invalid MAC address: ${mac}`)

  if (result.isBroadcast) throw new Error('Broadcast address — not a valid device identifier')
  if (result.isMulticast) throw new Error('Multicast address — not suitable for device registration')
  if (result.isLocal) {
    console.warn(`MAC ${result.normalized} is locally-administered — may be randomised`)
  }

  return result
}

// Register device — validate MAC and normalise before DB insert
async function registerDevice(rawMac: string, deviceName: string) {
  const mac = await validateMac(rawMac)
  return { mac: mac.normalized, type: mac.type, deviceName }
}

// Batch validate from network scan
async function validateNetworkScan(macs: string[]) {
  const results = await Promise.allSettled(macs.map(m => iv.macAddress(m)))
  return macs.map((mac, i) => ({
    mac,
    result: results[i].status === 'fulfilled' ? results[i].value : null,
    error: results[i].status === 'rejected' ? results[i].reason?.message : null,
  }))
}

// Example
const device = await registerDevice('00-1B-44-11-3A-B7', 'Raspberry Pi sensor')
console.log(device.mac)  // "00:1B:44:11:3A:B7"

7. Summary checklist

Accept colon, hyphen, and compact formats
Store normalized (colon uppercase) as canonical form
Reject multicast and broadcast addresses
Warn on locally-administered (randomised) MACs
Do not use MAC as sole persistent ID (may change)
Consider IMEI or UUID as stable device ID fallback
Run batch scans with Promise.allSettled
Return 422 with reason on invalid MAC input

See also

Ready to integrate?

Free tier — 1,000 requests/month. No credit card required.

Get your API key →