ARN Anatomy
Every AWS ARN has six colon-separated parts. The AWS docs describe the general form as:
arn:PARTITION:SERVICE:REGION:ACCOUNT:RESOURCE
# 1 2 3 4 5 6
# │ │ │ │ │ └── resource (may contain /)
# │ │ │ │ └────────── 12-digit account (may be empty)
# │ │ │ └───────────────── region (may be empty)
# │ │ └───────────────────────── service (iam, s3, lambda, ...)
# │ └───────────────────────────────── partition (aws, aws-cn, aws-us-gov)
# └───────────────────────────────────── literal "arn"The Generic ARN Validator
A single pattern that accepts every well-formed ARN across all services and partitions. Positions that can be empty use * or {0,12} quantifiers.
const ARN = /^arn:(aws|aws-cn|aws-us-gov):([a-zA-Z0-9-]+):([a-z0-9-]*):(\d{0,12}):(.+)$/;
ARN.exec("arn:aws:iam::123456789012:user/alice");
// → match with groups:
// 1 = "aws" partition
// 2 = "iam" service
// 3 = "" region (IAM is global)
// 4 = "123456789012" account
// 5 = "user/alice" resource
ARN.exec("arn:aws:s3:::my-bucket/key.png");
// → match with groups:
// 1 = "aws"
// 2 = "s3"
// 3 = "" no region in S3 ARNs
// 4 = "" no account in S3 ARNs
// 5 = "my-bucket/key.png"
ARN.exec("arn:aws-cn:ec2:cn-north-1:111122223333:instance/i-0abc1234");
// → match with China partition and full location fieldsParsing with Named Groups
Numeric groups are opaque. Named groups make downstream code self-documenting:
const ARN_NAMED = /^arn:(?<partition>aws|aws-cn|aws-us-gov):(?<service>[a-zA-Z0-9-]+):(?<region>[a-z0-9-]*):(?<account>\d{0,12}):(?<resource>.+)$/;
function parseArn(arn) {
const m = ARN_NAMED.exec(arn);
if (!m) return null;
return m.groups;
}
parseArn("arn:aws:lambda:us-east-1:123456789012:function:myFn:1");
// {
// partition: "aws",
// service: "lambda",
// region: "us-east-1",
// account: "123456789012",
// resource: "function:myFn:1"
// }
// To further split the resource (type:name or type/name)
function splitResource(resource) {
const sep = resource.search(/[:/]/);
return sep === -1
? { type: null, name: resource }
: { type: resource.slice(0, sep), name: resource.slice(sep + 1) };
}Service-Specific Patterns
IAM (global, no region)
// IAM user
/^arn:aws:iam::(\d{12}):user\/(.+)$/
// e.g. arn:aws:iam::123456789012:user/alice
// IAM role
/^arn:aws:iam::(\d{12}):role\/(.+)$/
// e.g. arn:aws:iam::123456789012:role/MyLambdaRole
// IAM policy
/^arn:aws:iam::(\d{12}):policy\/(.+)$/
// AWS-managed policy (empty account)
/^arn:aws:iam::aws:policy\/(.+)$/S3 (no region, no account)
// Bucket (3-63 DNS-safe chars)
/^arn:aws:s3:::([a-z0-9][a-z0-9.-]{1,61}[a-z0-9])$/
// Object (bucket + "/" + key)
/^arn:aws:s3:::([a-z0-9][a-z0-9.-]{1,61}[a-z0-9])\/(.+)$/
// Access point
/^arn:aws:s3:([a-z]{2}-[a-z]+-\d+):(\d{12}):accesspoint\/(.+)$/Lambda
// Function (optionally with version or alias at the end)
/^arn:aws:lambda:([a-z]{2}-[a-z]+-\d+):(\d{12}):function:([a-zA-Z0-9-_]{1,64})(:(\$LATEST|[0-9]+|[a-zA-Z-_][a-zA-Z0-9-_]*))?$/
// Layer version
/^arn:aws:lambda:([a-z]{2}-[a-z]+-\d+):(\d{12}):layer:([a-zA-Z0-9-_]{1,64}):(\d+)$/DynamoDB
// Table
/^arn:aws:dynamodb:([a-z]{2}-[a-z]+-\d+):(\d{12}):table\/([a-zA-Z0-9_.-]+)$/
// Table + index
/^arn:aws:dynamodb:([a-z]{2}-[a-z]+-\d+):(\d{12}):table\/([a-zA-Z0-9_.-]+)\/index\/([a-zA-Z0-9_.-]+)$/
// Table + stream (stream label at the end)
/^arn:aws:dynamodb:([a-z]{2}-[a-z]+-\d+):(\d{12}):table\/([a-zA-Z0-9_.-]+)\/stream\/([0-9T:.-]+)$/SQS and SNS
// SQS queue
/^arn:aws:sqs:([a-z]{2}-[a-z]+-\d+):(\d{12}):([a-zA-Z0-9_-]{1,80})(\.fifo)?$/
// SNS topic
/^arn:aws:sns:([a-z]{2}-[a-z]+-\d+):(\d{12}):([a-zA-Z0-9_-]{1,256})(\.fifo)?$/Partition Awareness
Most tutorials assume commercial AWS (aws) and miss the two other partitions:
aws-cn— China (operated by AWS China partners). Regions:cn-north-1,cn-northwest-1.aws-us-gov— AWS GovCloud (US). Regions:us-gov-east-1,us-gov-west-1.
Resource ARNs within those partitions use the same shape but with the partition identifier and region prefix. If you're building a multi-region tool, always allow the three partition values in your regex. If you're a commercial-only shop, hardcoding aws is a valid stricter check.
Language-Specific Usage
JavaScript / Node
const ARN = /^arn:(?<partition>aws|aws-cn|aws-us-gov):(?<service>[a-zA-Z0-9-]+):(?<region>[a-z0-9-]*):(?<account>\d{0,12}):(?<resource>.+)$/;
function parseArn(s) {
const m = ARN.exec(s);
if (!m) throw new Error(`Invalid ARN: ${s}`);
return m.groups;
}
const info = parseArn("arn:aws:lambda:us-east-1:123456789012:function:myFn");
console.log(info.service); // "lambda"
console.log(info.region); // "us-east-1"
// Or use the aws-arn npm package for strict per-service validation:
// import { parse } from '@aws-sdk/util-arn-parser';
// parse(arn)Python
import re
ARN = re.compile(
r'^arn:(?P<partition>aws|aws-cn|aws-us-gov):'
r'(?P<service>[a-zA-Z0-9-]+):'
r'(?P<region>[a-z0-9-]*):'
r'(?P<account>\d{0,12}):'
r'(?P<resource>.+)$'
)
def parse_arn(s: str) -> dict:
m = ARN.match(s)
if not m:
raise ValueError(f"Invalid ARN: {s}")
return m.groupdict()
parse_arn("arn:aws:iam::123456789012:user/alice")
# {'partition': 'aws', 'service': 'iam', 'region': '',
# 'account': '123456789012', 'resource': 'user/alice'}
# For strict parsing, use arnparse: pip install arnparseGo
package main
import "regexp"
var arnRe = regexp.MustCompile(
"^arn:(aws|aws-cn|aws-us-gov):([a-zA-Z0-9-]+):([a-z0-9-]*):(\\d{0,12}):(.+)$",
)
type ARN struct {
Partition, Service, Region, Account, Resource string
}
func ParseARN(s string) (ARN, bool) {
m := arnRe.FindStringSubmatch(s)
if m == nil {
return ARN{}, false
}
return ARN{m[1], m[2], m[3], m[4], m[5]}, true
}
// Or use github.com/aws/aws-sdk-go-v2/aws/arn.Parse(s)Common Pitfalls
Resource field contains colons
Lambda version/alias and some other resource identifiers contain internal colons: function:myFn:$LATEST. The 6th group of a generic ARN regex uses .+ which greedily matches to end-of-string, so this is fine for the generic case. But if you split on colon in code after matching, be careful — the resource side can have arbitrary colons.
Resource field contains slashes
IAM users, roles and policies use / inside the resource: user/alice, role/MyRole/MyPath. Slashes are also used by S3 object keys and DynamoDB index names. Don't split on slash — treat the whole 6th group as an opaque identifier until you know which service you're parsing.
Account ID can be empty for cross-partition services
S3 bucket ARNs skip account. AWS-managed IAM policies use the literal string aws in the account position: arn:aws:iam::aws:policy/AdministratorAccess. The \d{0,12} group in the generic regex accepts empty but not the literal aws. For AWS-managed policies, add a separate branch or widen the account group to [a-zA-Z0-9]*.
Region format varies
Commercial AWS uses us-east-1, eu-west-3 (two-letter country, direction, digit). GovCloud uses us-gov-east-1 which has an extra segment. China uses cn-north-1. The pattern [a-z0-9-]* matches all three; stricter per-service patterns use [a-z]{2}-[a-z]+-\d+ which also handles the GovCloud format correctly.
ARN Regex Cheatsheet
| Service | Example | Shape |
|---|---|---|
| IAM user | arn:aws:iam::123:user/alice | no region |
| S3 bucket | arn:aws:s3:::my-bucket | no region, no account |
| S3 object | arn:aws:s3:::my-bucket/key.png | bucket + / + key |
| Lambda fn | arn:aws:lambda:us-east-1:123:function:fn | optional :version |
| DynamoDB | arn:aws:dynamodb:us-east-1:123:table/t | region + account |
| SQS queue | arn:aws:sqs:us-east-1:123:q.fifo | optional .fifo |
Testing Your ARN Regex
Open the live Regex Tester and paste this test block:
# VALID — should ALL match the generic ARN regex
arn:aws:iam::123456789012:user/alice
arn:aws:iam::123456789012:role/MyLambdaRole
arn:aws:iam::aws:policy/AdministratorAccess
arn:aws:s3:::my-bucket
arn:aws:s3:::my-bucket/path/to/key.png
arn:aws:lambda:us-east-1:123456789012:function:myFn
arn:aws:lambda:us-east-1:123456789012:function:myFn:$LATEST
arn:aws:lambda:us-east-1:123456789012:function:myFn:prod
arn:aws:dynamodb:us-east-1:123456789012:table/MyTable
arn:aws:sqs:us-east-1:123456789012:MyQueue
arn:aws:sqs:us-east-1:123456789012:MyQueue.fifo
arn:aws:sns:eu-west-1:123456789012:MyTopic
arn:aws-cn:ec2:cn-north-1:111122223333:instance/i-0abc1234
arn:aws-us-gov:s3:::govcloud-bucket
# INVALID — should ALL fail
arn:fake:iam::123:user/x # bad partition
arn:aws:iam::12345:user/x # 5-digit account
not-an-arn
arn::iam::123456789012:user/x # empty partition
arn:aws:iam::123456789012: # empty resourceWhen to Reach for the SDK
Regex is excellent for syntax validation and extracting parts for logging, routing or quick cross-account checks. It cannot tell you whether a specific resource exists, whether the account has permission to assume a role, or whether an S3 bucket name is already taken. For those, call the AWS SDK: @aws-sdk/util-arn-parser for structured parsing, boto3.client('iam').get_role(RoleName=...) for existence, aws sts get-caller-identity for the current account. Use regex at the ingress — form fields, config files, CLI args — and the SDK at the business-logic layer.