Matching Currency Amounts with Regular Expressions
Currency validation is one of the most common form-field checks in every checkout, invoice, and finance app. But it's deceptively hard: the same value can be written as $1,234.56, 1.234,56 €, USD 1234.56, or 1.5M depending on locale, industry, and whether the user is trained to type accounting parens for negatives. Regex won't solve every case — but with the right pattern it will catch 95% of typos before your money library ever sees the string. Below are the patterns that actually work in production, why they work, and where they break.
The Core Patterns
US format — $1,234.56
/^\$?\d{1,3}(,\d{3})*(\.\d{2})?$/
// Matches: $1,234.56 $99.00 1000 $1,000,000.00
// Rejects: 1,23.45 $1,2345 $1.234 (three-decimal)This is the workhorse. The \\$? makes the dollar sign optional. \d{1,3}(,\d{3})* enforces the 1–3 leading digits then groups of exactly 3, so 1,23.45 is rejected while 1,234.56 passes. The (\.\d{2})? makes the cents optional but locks it to exactly 2 decimals — perfect for real-world money.
US format — decimal required
/^\$?\d{1,3}(,\d{3})*\.\d{2}$/
// Matches: $1,234.56 $99.00
// Rejects: $1,234 1000Use this when your database schema requires cents. It prevents users from submitting a whole-dollar amount that gets stored inconsistently.
European format — 1.234,56 €
/^€?\d{1,3}(\.\d{3})*(,\d{2})?$/
// Matches: €1.234,56 99,00 €1.000.000,00
// Rejects: €1,234.56 (US format)German, French, Spanish, Italian and Dutch conventions swap the comma and period. If your app serves EU users, either detect the locale first and pick the right regex, or accept both formats then normalise before storing.
Multi-currency (symbol optional)
/^[$€£¥]?\s?\d{1,3}(,\d{3})*(\.\d{2})?$/
// Matches: $100 € 99.00 ¥1,000 £250.50 1234
// The \s? allows an optional space between symbol and number.Abbreviated — $1.5M, 250K, £2B
/^[$€£¥]?\d+(\.\d+)?\s?[KkMmBbTt]$/
// Matches: $1.5M 250K £2B 0.5T 1M
// Rejects: 1,000K (thousands separators + abbreviation)
// Reject/accept both notations with an alternation:
/^[$€£¥]?\d{1,3}(,\d{3})*(\.\d+)?\s?[KkMmBbTt]?$/Marketing dashboards and news feeds love abbreviations. Note the character class [KkMmBbTt]so users don't have to remember capitalisation. If you need to convert to a Number afterwards, K = 1e3, M = 1e6, B = 1e9, T = 1e12.
Negative amounts
// Minus prefix
/^-\$?\d{1,3}(,\d{3})*(\.\d{2})?$/
// Accountant parentheses
/^\(\$?\d{1,3}(,\d{3})*(\.\d{2})?\)$/
// Both accepted, positive OR negative
/^(-?\$?|\(\$?)\d{1,3}(,\d{3})*(\.\d{2})?\)?$/Language-Specific Usage
JavaScript
const USD = /^\$?\d{1,3}(,\d{3})*(\.\d{2})?$/;
const MULTI = /^[$€£¥]?\s?\d{1,3}(,\d{3})*(\.\d{2})?$/;
function isCurrency(str) { return USD.test(str); }
function toCents(str) {
const clean = str.replace(/[$,€£¥\s]/g, '');
return Math.round(parseFloat(clean) * 100);
}
isCurrency('$1,234.56'); // true
isCurrency('$1,234'); // true
isCurrency('$1,23.45'); // false — bad grouping
toCents('$1,234.56'); // 123456
// Extract every currency mention from a paragraph
const text = 'Revenue rose to $1.5M this quarter, up from $850K last year.';
text.match(/\$?\d+(\.\d+)?\s?[KkMmBbTt]?/g);Python
import re
USD = re.compile(r'^\$?\d{1,3}(,\d{3})*(\.\d{2})?$')
def is_usd(s: str) -> bool:
return bool(USD.match(s))
is_usd('$1,234.56') # True
is_usd('1000') # True
is_usd('$1.5M') # False — use the abbreviated pattern for that
# For actual money math, use Decimal or a money library
from decimal import Decimal
amount = Decimal('1234.56') # Never use float for moneyPHP
function isCurrency(string $s): bool {
return (bool) preg_match(
'/^\$?\d{1,3}(,\d{3})*(\.\d{2})?$/',
$s
);
}
// For Laravel form validation
$request->validate([
'amount' => ['required', 'regex:/^\$?\d{1,3}(,\d{3})*(\.\d{2})?$/'],
]);Common Pitfalls
Regex is format-only — never trust it for value logic
A regex tells you the STRING looks like money. It doesn't tell you the AMOUNT is positive, within a valid range, or actually a real number after parsing. Always parse with Number(str.replace(/[$,]/g, '')) or a money library, and validate the numeric value separately.
Floating point breaks money
0.1 + 0.2 === 0.30000000000000004. Never store or math money as a floating-point number. Convert to integer cents (multiply by 100 and round) or use a library like dinero.js, money.js, big.js, or Python's decimal.Decimal.
Locale format flips break validation
A US-format regex rejects 1.234,56even though it's a perfectly valid European amount. Either detect locale server-side and pick the right pattern, or accept both and normalise. Never assume US-only for a global app.
Currency symbols before AND after
French writes 1 234,56 € — symbol AFTER the number. Swedish uses 1 234 kr. If your form is truly international, use either a locale-aware library (Intl.NumberFormat in JavaScript) or accept flexible positions.
Cryptocurrency needs more decimals
Fiat has 2 decimals (or 3 for JPY, 0 for some). Bitcoin has 8. Ethereum has 18. If your app handles crypto, either loosen the decimal count to {0,18} or use a crypto-specific validator.
Currency Regex Cheatsheet
| Goal | Pattern | Matches |
|---|---|---|
| USD flexible | /^\$?\d{1,3}(,\d{3})*(\.\d{2})?$/ | $1,234.56, $100 |
| USD strict cents | /^\$?\d{1,3}(,\d{3})*\.\d{2}$/ | $99.00 required |
| EU format | /^€?\d{1,3}(\.\d{3})*(,\d{2})?$/ | €1.234,56 |
| Multi-symbol | /^[$€£¥]?\d{1,3}(,\d{3})*(\.\d{2})?$/ | $, €, £, ¥ |
| Abbreviated | /^[$€£¥]?\d+(\.\d+)?[KkMmBbTt]$/ | $1.5M, 250K |
Testing Your Currency Regex
Use the live Regex Tester above with these test strings:
- Match:
$1,234.56,€99.00,£1,000,000.00 - Match (no symbol):
1234.56,1,000 - Match (abbreviated):
$1.5M,250K,£2B - Reject:
$1,23.45(bad grouping),$1.234(3-decimal),abc - Edge:
$0,$0.00,0
Common Mistakes When Writing Currency Regex
- Not anchoring the pattern. Without
^and$, your regex matchesabc$100xyzas a valid currency string. Always anchor for form validation. - Allowing any decimal count.
(\.\d+)?accepts$1.234567. For strict money use(\.\d{2})?— exactly two. - Forgetting the leading digit constraint.
\d+(,\d{3})*accepts1234,567.89(four leading digits before a comma).\d{1,3}(,\d{3})*enforces the correct grouping. - Assuming your locale. A US regex rejects half the world. Either detect locale or accept a broad superset then normalise.
- Storing as float. Regex or no regex, use integer cents or a money library. Financial code with floats will lose money to rounding within a few thousand transactions.
Performance Notes
Currency regexes are extremely fast — every quantifier is bounded and there's no alternation that could backtrack catastrophically. Even the multi-symbol pattern with an optional space and cents runs in constant time. Validating thousands of amounts per second is not a concern. If you need to extract every currency mention from long-form text (news articles, annual reports), the abbreviated pattern with a g flag is your friend.