{"record":{"id":"ec0ddbaacaad7631","repo":"serverless/serverless","slug":"sns-missing-topic-name","errorCode":"SNS_MISSING_TOPIC_NAME","errorMessage":"Missing \"sns.topicName\" setting (it needs to be provided if \"sns.arn\" is not provided as plain ARN string)","messagePattern":"Missing \"sns\\.topicName\" setting \\(it needs to be provided if \"sns\\.arn\" is not provided as plain ARN string\\)","errorType":"exception","errorClass":"ServerlessError","httpStatus":null,"severity":"error","filePath":"packages/serverless/lib/plugins/aws/package/compile/events/sns.js","lineNumber":150,"sourceCode":"                topicArn = event.sns.arn\n                if (typeof topicArn === 'string') {\n                  // NOTE: we need to process the topic ARN that way due to lacking\n                  // support of regex lookbehind in Node.js 6.\n                  // Once Node.js 6 support is dropped we can change this to:\n                  // `const splitArn = topicArn.split(/(?<!:):(?!:)/);`\n                  const splitArn = topicArn\n                    .replace(/::/g, '@@')\n                    .split(':')\n                    .map((s) => s.replace(/@@/g, '::'))\n                  topicName = splitArn[splitArn.length - 1]\n                  if (splitArn[3] !== this.options.region) {\n                    region = splitArn[3]\n                  }\n                }\n                topicName = event.sns.topicName || topicName\n                // Only true when providing CFN intrinsic function (not string) to arn property without topicName property\n                if (!topicName) {\n                  throw new ServerlessError(\n                    'Missing \"sns.topicName\" setting (it needs to be provided if \"sns.arn\" is not provided as plain ARN string)',\n                    'SNS_MISSING_TOPIC_NAME',\n                  )\n                }\n              } else {\n                topicName = event.sns.topicName\n              }\n            } else if (event.sns.indexOf('arn:') === 0) {\n              topicArn = event.sns\n              const splitArn = topicArn.split(':')\n              topicName = splitArn[splitArn.length - 1]\n            } else {\n              topicName = event.sns\n            }\n\n            if (event.sns.redrivePolicy) {\n              const {\n                deadLetterTargetArn,","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/serverless/serverless/blob/b9d7ea51c8cce57cff1207964b9b71123673081f/packages/serverless/lib/plugins/aws/package/compile/events/sns.js#L132-L168","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nevents:\n  - sns:\n      arn: { \"Fn::GetAtt\": [MyTopic, Arn] }\n# after\nevents:\n  - sns:\n      arn: { \"Fn::GetAtt\": [MyTopic, Arn] }\n      topicName: my-topic","handlingStrategy":"type-guard","validationCode":"function validateSnsEvent(event) {\n  if (!event.sns || typeof event.sns !== 'object') return\n  const arn = event.sns.arn\n  if (arn != null && typeof arn === 'object' && !event.sns.topicName) {\n    throw new Error('sns.arn is a CloudFormation intrinsic; sns.topicName must be set explicitly')\n  }\n}","typeGuard":"function isIntrinsic(v) {\n  return v != null && typeof v === 'object' &&\n    (Object.keys(v).includes('Ref') || Object.keys(v).some(k => k.startsWith('Fn::')))\n}\nfunction needsTopicName(snsEvent) {\n  return snsEvent && typeof snsEvent === 'object' && snsEvent.arn != null &&\n    typeof snsEvent.arn === 'object' && snsEvent.topicName == null\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["aws","sns","cloudformation","config","validation"],"backgroundTag":null,"analyzedSha":"b9d7ea51c8cce57cff1207964b9b71123673081f","analyzedAt":"2026-08-13T04:14:40.386Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}