Quick answer
A strong password regex is ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}$. Each lookahead checks one rule (a lowercase letter, an uppercase letter, a digit and a symbol) without consuming anything, then .{12,} requires at least 12 characters. Use it for a form hint, but follow current NIST advice: length and a breached-password check matter more than composition rules.
- Tested examples
- Token-by-token breakdown
- Code in 5 languages
- Live tester
How to use the strong password regex
- 1Copy the pattern with Copy pattern in the tester, or take a ready-made snippet for your language from the code section below.
- 2Check the sample lines in Test string: every line the strong password regex accepts is highlighted, rejected lines stay plain.
- 3Paste your own values one per line to see which pass, and read Explanation for what each part of the pattern does.
- 4Open the Code tab for JavaScript, Python, Go, C#, PHP or Dart with the pattern translated and escaped.
- 5Validate again on the server: client-side checks give users quick feedback, not security.
How does the strong password regex work?
Read it left to right. Each piece below matches one part of the input:
^Start of the string.(?=.*[a-z])Lookahead: somewhere ahead there is a lowercase letter.(?=.*[A-Z])Lookahead: there is an uppercase letter.(?=.*\d)Lookahead: there is a digit.(?=.*[^A-Za-z0-9])Lookahead: there is a character that is not a letter or digit (a symbol or space)..{12,}At least 12 characters of anything except line breaks. All four lookaheads ran from position 0, so this is the only part that consumes.$End of the string.
Which strings does the strong password regex match?
The whole input must match, from the first character to the last. Every row below is checked against the pattern by our automated test suite before each release.
| Input | Matches? | Why |
|---|---|---|
Tr0ub4dor&3xyz | Yes | All four character types and 14 characters. |
correct-Horse-7-battery | Yes | A long passphrase with a capital, a digit and hyphens. |
P@ssw0rd!2026 | Yes | Passes every rule, yet it is in every breach list. Rules are not enough. |
Short1!a | No | Only 8 characters. |
alllowercase1!x | No | No uppercase letter. |
ALLUPPERCASE1!X | No | No lowercase letter. |
NoDigitsHere!! | No | No digit. |
NoSymbols12345 | No | No symbol. |
Are complexity rules still recommended?
Not by the people who write the standards. NIST SP 800-63B-4 advises verifiers to require a minimum length, allow long passphrases (at least 64 characters), accept spaces and all printable characters, and not impose composition rules like "one uppercase, one symbol". Users respond to those rules predictably (Password1!), which attackers know.
What actually stops weak passwords:
- Length: NIST SP 800-63B-4 requires at least 15 characters when the password is the only factor and 8 with multi-factor authentication; 12 or more is a sensible floor for a sign-up form.
- A breached-password check: compare against a list of known leaked passwords, for example with the k-anonymity range API from Have I Been Pwned.
- Rate limiting and multi-factor authentication on the login endpoint.
If a policy or an auditor requires composition rules, this regex enforces them in one place. Show each rule as a live checklist in the UI rather than a single "invalid password" error.
How do I show which password rule failed?
Split the pattern into its parts and test each one, which is also how you would port it to Go (RE2 has no lookaheads):
const rules = [
{ test: /[a-z]/, label: "a lowercase letter" },
{ test: /[A-Z]/, label: "an uppercase letter" },
{ test: /\d/, label: "a digit" },
{ test: /[^A-Za-z0-9]/, label: "a symbol" },
{ test: /^.{12,}$/, label: "at least 12 characters" },
];
const missing = rules.filter((r) => !r.test.test(password)).map((r) => r.label);Never log or send the password while checking it, and hash it with a slow algorithm such as Argon2id or bcrypt before storing it.
How do I use the strong password regex in JavaScript, Python, Go, C# and PHP?
The pattern is translated for each engine rather than pasted as is: named groups, anchors, escapes, shorthand classes and flags are adjusted so each snippet behaves as closely to the JavaScript version as its engine allows. Differences that remain (Go's RE2 has no lookarounds or backreferences, for example) are called out under each one. The Code tab in the tester above also exports Dart, and updates as you edit the pattern.
JavaScript and TypeScript
const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}$/;
function isValid(value) {
return regex.test(value);
}
console.log(isValid("Tr0ub4dor&3xyz"));Python
import re
pattern = re.compile(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9]).{12,}\Z")
def is_valid(value: str) -> bool:
return pattern.search(value) is not None
print(is_valid("Tr0ub4dor&3xyz"))- JavaScript's . also refuses \r, \u2028 and \u2029; in Python it only refuses \n.
- \d and \w are written as [0-9] and [A-Za-z0-9_] because in Python they also match digits and letters from other scripts.
- JavaScript's $ only matches at the very end of the string. Python's $ also matches before a trailing newline, so the pattern uses \Z instead.
Go
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}$`)
fmt.Println(re.MatchString("Tr0ub4dor&3xyz"))
}Go's regexp package (RE2) does not support lookahead or lookbehind, so this pattern will not compile. Match more broadly and check the extra condition in Go code instead.
- JavaScript's . also refuses \r, \u2028 and \u2029; in Go it only refuses \n.
C#
using System;
using System.Text.RegularExpressions;
// The timeout stops a pathological input from hanging the thread (ReDoS).
var regex = new Regex(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9]).{12,}\z", RegexOptions.None, TimeSpan.FromSeconds(1));
Console.WriteLine(regex.IsMatch("Tr0ub4dor&3xyz"));- JavaScript's . also refuses \r, \u2028 and \u2029; in .NET it only refuses \n.
- \d and \w are written as [0-9] and [A-Za-z0-9_] because in .NET they also match digits and letters from other scripts.
- JavaScript's $ only matches at the very end of the string. .NET's $ also matches before a trailing newline, so the pattern uses \z instead.
PHP
<?php
$pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}$/D';
function isValid(string $value, string $pattern): bool
{
return preg_match($pattern, $value) === 1;
}
var_dump(isValid('Tr0ub4dor&3xyz', $pattern));- JavaScript's . also refuses \r, \u2028 and \u2029; in PHP (PCRE) it only refuses \n.
- The D modifier makes $ match only at the very end, like JavaScript (PCRE's $ also matches before a trailing newline).
Which related regex patterns are useful?
- Regex for Usernames: 3–16 characters, starts with a letter, single separators between letters and digits.
- Regex for Alphanumeric Characters: Letters A–Z and digits only, with a Unicode variant for every script.
- Regex for Email Validation: Practical email check: dotted local part, real domain labels and a letters-only top-level domain.
- Regex for JWT (JSON Web Token) Format: Three base64url segments separated by dots: header, payload and an optional signature.
Browse all 25 patterns in the regex pattern library, or build your own in the regex tester.
Frequently asked questions
Last updated by the Playxoft engineering team.