How to Validate an ABN in Code (Plus ACN, Postcodes, Phone Numbers and AEDT)
Quick answer
An ABN is 11 digits with a built-in checksum. To validate it: strip spaces, subtract 1 from the first digit, multiply the digits by the weights 10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, add the results, and check the total divides evenly by 89. That catches typos, but only an ABN Lookup call proves the business is registered.
Every Australian checkout, invoice form and onboarding flow ends up validating the same handful of formats: an ABN on the tax invoice, a postcode that starts with a zero, a mobile number typed six different ways, and a date that means one thing in Sydney and another in San Francisco. Each has a rule that catches teams out. This guide covers them with code you can paste, and every example here was run and checked against real, publicly listed identifiers.
The ABN checksum
An Australian Business Number is 11 digits, usually written in a 2-3-3-3 grouping
(51 824 753 556). The last digits aren't random: the whole number carries a checksum, so a
mistyped ABN is almost always detectable before you send anything to the tax office. The algorithm:
- Remove spaces. Reject anything that isn't exactly 11 digits.
- Subtract 1 from the first digit. This step is the one everyone forgets.
- Multiply each digit by its weight:
10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19. - Add the products. The ABN is valid if the sum is divisible by 89.
Worked through with the Australian Taxation Office's own ABN, 51 824 753 556:
digits 5 1 8 2 4 7 5 3 5 5 6 step 2 4 (5 − 1) weights 10 1 3 5 7 9 11 13 15 17 19 40 + 1 + 24 + 10 + 28 + 63 + 55 + 39 + 75 + 85 + 114 = 534 534 ÷ 89 = 6 exactly → valid ✓
function isValidABN(input) {
const digits = String(input).replace(/[\s-]/g, '');
if (!/^\d{11}$/.test(digits)) return false;
const weights = [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19];
const n = digits.split('').map(Number);
n[0] -= 1; // the step everyone forgets
const sum = n.reduce((total, d, i) => total + d * weights[i], 0);
return sum % 89 === 0;
}
isValidABN('51 824 753 556'); // true (Australian Taxation Office)
isValidABN('33 051 775 556'); // true (Telstra)
isValidABN('51 824 753 557'); // false (last digit changed)
The ACN, and how it hides inside an ABN
An Australian Company Number is 9 digits and uses a different scheme: a weighted sum of
the first 8 digits, where the 9th digit is the check digit. Multiply by 8, 7, 6, 5, 4, 3, 2, 1,
take the remainder after dividing by 10, and subtract it from 10 (a remainder of 0 means a check digit
of 0):
function isValidACN(input) {
const digits = String(input).replace(/[\s-]/g, '');
if (!/^\d{9}$/.test(digits)) return false;
const n = digits.split('').map(Number);
const sum = n.slice(0, 8).reduce((total, d, i) => total + d * (8 - i), 0);
const check = (10 - (sum % 10)) % 10;
return check === n[8];
}
isValidACN('000 014 675'); // true (Woolworths Group Limited)
// 0+0+0+0+ (1×4) + (4×3) + (6×2) + (7×1) = 35 → 10 − 5 = 5 → matches the last digit
Here's the useful part: when a company registers for an ABN, its ABN is normally its
ACN with two extra digits on the front. Telstra's ACN is 051 775 556 and
its ABN is 33 051 775 556. So you can cross-check the two fields on a form instead of
trusting whichever the user typed more carefully. Sole traders and partnerships have an ABN with no ACN
at all, so only apply this check when you actually have both.
A valid checksum is not a real business
Both algorithms only prove the number is internally consistent. They can't tell you the business exists, is still trading, or is registered for GST. Plenty of made-up numbers pass. For that you need the ABN Lookup web services published by the Australian Business Register, which return the entity name, status, GST registration and state. Use the checksum to reject typos instantly in the browser, then confirm against ABN Lookup on the server before you rely on it for tax or credit decisions.
Postcodes: the leading zero that Excel eats
Australian postcodes are four digits, and the Northern Territory's start with a zero: 0800 is Darwin. The moment that column goes through a spreadsheet as a number, the zero is gone and 0800 becomes 800. The same happens in JSON if you store postcodes as numbers:
{ "postcode": 0800 } ✗ invalid JSON — and 800 once parsed
{ "postcode": "0800" } ✓ string, zero preserved
Treat postcodes as text everywhere: the database column, the JSON field, the CSV export. When people open a CSV in Excel, that's still not enough, because Excel converts on open. Either tell recipients to import rather than double-click, or quote the values and accept that Excel may still re-type them. Our JSON to CSV guide covers the export side.
A regex that allows the territory ranges while rejecting other numbers below 1000:
/^(0[289]\d{2}|[1-9]\d{3})$/
0800 ✓ (NT) 0872 ✓ (NT remote) 0200 ✓ (ACT)
2000 ✓ (NSW) 3000 ✓ (VIC) 6000 ✓ (WA)
0123 ✗ 999 ✗ 12345 ✗
Broadly, the first digit maps to a state: 2 NSW, 3 VIC, 4 QLD, 5 SA, 6 WA, 7 TAS, 0 NT, with the ACT using 02xx and 26xx-29xx ranges. Don't hard-code that mapping for anything important, since ranges overlap at the borders and Australia Post changes them. Test the pattern against your own data in the Regex Tester.
Phone numbers: six ways to type the same mobile
An Australian mobile is 04xx xxx xxx — ten digits starting with 04. In international form
the leading zero is dropped, not kept: +61 4xx xxx xxx. Writing
+61 04... is the single most common mistake, and it will fail at most SMS gateways.
// Normalise first, validate second
function toE164AU(input) {
const d = String(input).replace(/[\s()\-.]/g, '');
if (/^04\d{8}$/.test(d)) return '+61' + d.slice(1); // 0412345678 -> +61412345678
if (/^(\+?61)4\d{8}$/.test(d)) return '+61' + d.replace(/^\+?61/, '');
return null;
}
toE164AU('0412 345 678'); // '+61412345678'
toE164AU('(04) 1234 5678'); // '+61412345678'
toE164AU('+61 412 345 678'); // '+61412345678'
Landlines follow the same shape with an area code of 02 (NSW/ACT), 03 (VIC/TAS), 07 (QLD) or 08 (SA/WA/NT), then eight digits. Service numbers don't fit either pattern: 1300 and 1800 numbers have ten digits, 13 numbers have only six, and none of them can receive SMS. If your form sends a verification code, validate for a mobile specifically rather than "any Australian number".
GST: 10%, and why you should count in cents
GST is 10%, which sounds like the easy case until floating point gets involved. In JavaScript,
19.99 * 0.1 is 1.9989999999999999. Round that per line across a thousand-line
invoice and your total drifts away from what the accounting system expects:
19.99 * 0.1 // 1.9989999999999999 Math.round(1999 * 0.1) // 200 cents — integer maths, no drift
Work in integer cents, or use a decimal library, and decide deliberately whether GST is rounded per line or on the invoice total, because the two disagree by a cent or two. This is the same class of bug as the money handling in our CFDI guide for Mexico: currency is not a job for binary floating point. A tax invoice also has to carry the supplier's ABN, which is exactly why the checksum at the top of this guide belongs in your invoice validation.
Dates: 04/10/2026 is not April
Australia writes dates day first. 04/10/2026 is 4 October 2026 in
Melbourne and 10 April 2026 in Chicago, and nothing in the string tells you which was meant. There is
one fix: ISO 8601 (2026-10-04) everywhere data is stored, transmitted or
logged, and locale formatting only at the moment of display:
new Intl.DateTimeFormat('en-AU', { dateStyle: 'long' }).format(date);
// "4 October 2026"
new Intl.DateTimeFormat('en-AU', { dateStyle: 'short' }).format(date);
// "4/10/26" ← day first
new Intl.NumberFormat('en-AU', { style: 'currency', currency: 'AUD' }).format(1234.56);
// "$1,234.56"
Note that AUD renders as a plain $ in an Australian locale. If your app shows several
dollar currencies, set currencyDisplay: 'code' or label it, or Australian and US amounts
look identical.
Time zones: daylight saving starts 4 October 2026
At 2:00 a.m. on Sunday, 4 October 2026, clocks jump forward to 3:00 a.m. in New South Wales, Victoria, the ACT, South Australia and Tasmania. That hour does not exist: a job scheduled for 2:30 a.m. has no 2:30 a.m. to run at. In April it runs twice instead. But the real complication is that the rest of the country doesn't move:
| Zone | Standard | Daylight saving |
|---|---|---|
Australia/Sydney, Melbourne, Hobart | UTC+10 | UTC+11 from 4 Oct 2026 |
Australia/Adelaide | UTC+9:30 | UTC+10:30 from 4 Oct 2026 |
Australia/Brisbane (QLD) | UTC+10 | none |
Australia/Perth (WA) | UTC+8 | none |
Australia/Darwin (NT) | UTC+9:30 | none |
Australia/Eucla | UTC+8:45 | none |
Australia/Lord_Howe | UTC+10:30 | UTC+11 (a 30-minute shift) |
So for half the year Sydney and Brisbane are an hour apart despite sharing a standard offset, and Australia has half-hour and quarter-hour offsets: any code that stores a time zone as an integer number of hours is broken here before it ships. Lord Howe Island shifts by 30 minutes rather than a full hour, which breaks code that assumes DST always means exactly one hour.
The rules are the same ones we set out for the US change in the
daylight saving guide: store instants in UTC, store
future local events as wall time plus an IANA zone name such as Australia/Perth, never
store a fixed offset, and run schedulers in UTC. Check any stored timestamp against the
Timestamp Converter when a report looks an hour out.
A pre-launch checklist
- ABN and ACN fields run the checksum in the browser, then ABN Lookup on the server.
- Postcodes are text in the database, the JSON and the CSV export.
- Phone numbers are normalised to E.164 (
+614…) on save, with the leading zero dropped. - Money is stored in cents; GST rounding happens at one agreed level.
- Dates are ISO 8601 in storage and day-first only in display.
- Time zones are IANA names, and the app is tested across the 4 October transition.
- Tax file numbers, if you handle them at all, are treated as sensitive: don't log them, and see the guide on hashing personal identifiers before assuming a hash makes them safe.
Useful ToolzYard tools
Conclusion
Australian data has a few sharp edges, and all of them are cheap to handle once you know they're there. Run the ABN checksum (remembering to subtract 1 from the first digit) and confirm against ABN Lookup. Keep postcodes and phone numbers as normalised text. Count money in cents. Write dates in ISO 8601 and only format them day-first for display. And treat Australian time zones as the hardest ones in the English-speaking world, because with half-hour offsets and three states that ignore daylight saving, they are.
Frequently Asked Questions
How do I validate an ABN?
Strip spaces and confirm 11 digits, subtract 1 from the first digit, multiply the digits by the weights 10, 1, 3, 5, 7, 9, 11, 13, 15, 17 and 19, add the results, and check the total is divisible by 89. That verifies the checksum, not that the business is registered.
Is a valid ABN checksum enough to trust a business?
No. The checksum only proves the number is internally consistent, and invented numbers can pass. Query ABN Lookup from the Australian Business Register to confirm the entity name, its status and whether it is registered for GST.
Is an ACN the same as an ABN?
No, but they are related. An ACN is the 9-digit company number, and a company's 11-digit ABN is normally its ACN with two extra digits in front. Sole traders and partnerships have an ABN but no ACN.
Why do Australian postcodes lose their leading zero?
Because they were stored or opened as numbers. Northern Territory postcodes such as 0800 become 800 once a spreadsheet or JSON parser treats them as numeric. Keep postcodes as text in the database, the JSON and the export.
When does daylight saving start in Australia in 2026?
At 2:00 a.m. on Sunday 4 October 2026, when clocks move forward to 3:00 a.m. in New South Wales, Victoria, the ACT, South Australia and Tasmania. Queensland, Western Australia and the Northern Territory do not observe daylight saving.