serverless/serverless · error · ServerlessError
SNS_MISSING_TOPIC_NAME
SNS_MISSING_TOPIC_NAME
Error message
Missing "sns.topicName" setting (it needs to be provided if "sns.arn" is not provided as plain ARN string)
What it means
Thrown when 'sns.arn' is provided as a CloudFormation intrinsic function (an object like { 'Fn::GetAtt': [...] }, not a plain string) and 'sns.topicName' is not provided. The framework needs the bare topic name to author the subscription resource; it can extract the name from a string ARN but cannot parse it out of an intrinsic function. The check fires at sns.js:148-154 after the string-ARN branch has run.
Source
Thrown at packages/serverless/lib/plugins/aws/package/compile/events/sns.js:150
topicArn = event.sns.arn
if (typeof topicArn === 'string') {
// NOTE: we need to process the topic ARN that way due to lacking
// support of regex lookbehind in Node.js 6.
// Once Node.js 6 support is dropped we can change this to:
// `const splitArn = topicArn.split(/(?<!:):(?!:)/);`
const splitArn = topicArn
.replace(/::/g, '@@')
.split(':')
.map((s) => s.replace(/@@/g, '::'))
topicName = splitArn[splitArn.length - 1]
if (splitArn[3] !== this.options.region) {
region = splitArn[3]
}
}
topicName = event.sns.topicName || topicName
// Only true when providing CFN intrinsic function (not string) to arn property without topicName property
if (!topicName) {
throw new ServerlessError(
'Missing "sns.topicName" setting (it needs to be provided if "sns.arn" is not provided as plain ARN string)',
'SNS_MISSING_TOPIC_NAME',
)
}
} else {
topicName = event.sns.topicName
}
} else if (event.sns.indexOf('arn:') === 0) {
topicArn = event.sns
const splitArn = topicArn.split(':')
topicName = splitArn[splitArn.length - 1]
} else {
topicName = event.sns
}
if (event.sns.redrivePolicy) {
const {
deadLetterTargetArn,View on GitHub (pinned to b9d7ea51c8)
Solutions
- Add 'topicName: <name>' alongside the intrinsic 'arn'.
- If the topic is in this stack, use the topic's logical name via provider-style reference or supply a plain string ARN.
- Ensure the topicName matches the topic that the intrinsic resolves to at deploy time.
Example fix
# before
events:
- sns:
arn: { "Fn::GetAtt": [MyTopic, Arn] }
# after
events:
- sns:
arn: { "Fn::GetAtt": [MyTopic, Arn] }
topicName: my-topic Defensive patterns
Strategy: type-guard
Validate before calling
function validateSnsEvent(event) {
if (!event.sns || typeof event.sns !== 'object') return
const arn = event.sns.arn
if (arn != null && typeof arn === 'object' && !event.sns.topicName) {
throw new Error('sns.arn is a CloudFormation intrinsic; sns.topicName must be set explicitly')
}
} Type guard
function isIntrinsic(v) {
return v != null && typeof v === 'object' &&
(Object.keys(v).includes('Ref') || Object.keys(v).some(k => k.startsWith('Fn::')))
}
function needsTopicName(snsEvent) {
return snsEvent && typeof snsEvent === 'object' && snsEvent.arn != null &&
typeof snsEvent.arn === 'object' && snsEvent.topicName == null
} Prevention
- Whenever sns.arn is an object (intrinsic), always provide topicName in the same edit.
- Codify a helper that wraps dynamic SNS topic references and always injects topicName.
- Add a CI lint rule that flags object-form sns.arn without topicName.
When it happens
Trigger: Configuring 'sns: { arn: { "Fn::GetAtt": [MyTopic, Arn] } }' or 'arn: { Ref: TopicArnParam }' without a sibling 'topicName'. The framework cannot derive a topic name from an intrinsic and so requires it explicitly.
Common situations: Referencing a topic exported from another stack via Cross-Stack Reference or a CloudFormation parameter; migrating from a hard-coded ARN string to a dynamic reference and forgetting to add topicName.
Related errors
- S3_INVALID_BUCKET_PATTERN
- WEBSOCKETS_MISSING_AUTHORIZER_NAME
- CLOUDFORMATION_STACK_RESOURCE_NOT_FOUND
- ERROR_INVALID_REFERENCE_TO_EVENT_BUS_CUSTOM_RESOURCE
- S3_MULTIPLE_BUCKETS_PER_FUNCTION
AI-assisted analysis of serverless/serverless@b9d7ea51c8 (2026-08-13).
Data as JSON: /api/errors/ec0ddbaacaad7631.
Report an issue: GitHub.