# Advanced Features > **Prerequisites:** Backend defined in `amplify/backend.ts` with `defineBackend({ auth, data })`. ## Geo (Location) — Backend + Frontend Add map display and location search using CDK constructs in `amplify/backend.ts`: ```typescript import { defineBackend } from '@aws-amplify/backend'; import * as geo from 'aws-cdk-lib/aws-location'; const backend = defineBackend({ auth }); const geoStack = backend.createStack('GeoStack'); const placeIndex = new geo.CfnPlaceIndex(geoStack, 'PlaceIndex', { dataSource: 'Esri', indexName: 'myPlaceIndex', }); const map = new geo.CfnMap(geoStack, 'Map', { mapName: 'myMap', configuration: { style: 'VectorEsriNavigation' }, }); backend.addOutput({ geo: { aws_region: geoStack.region, maps: { items: { [map.mapName]: { style: 'VectorEsriNavigation' } } }, search_indices: { items: [placeIndex.indexName] }, }, }); ``` Grant authenticated users access via IAM policy on the geo resources. ### Geo / Location (Frontend) Install: `npm install @aws-amplify/geo` ```typescript import { Geo } from '@aws-amplify/geo'; const results = await Geo.searchByText('Seattle'); ``` > **Note:** Gen2 uses `import { Geo } from '@aws-amplify/geo'` — NOT `Amplify.Geo.*` (that namespace doesn't exist). > > **Constraint:** Location Service resource names have a 100-character limit. Use short static names — avoid dynamic names like `${stack.stackName}-index`. For rendering maps, also install `maplibre-gl-js-amplify`. See [AWS Amplify Geo docs](https://docs.amplify.aws/react/build-a-backend/add-aws-services/geo/) for full client setup. ## PubSub — Backend + Frontend > **Install required:** `npm install @aws-amplify/pubsub` — not included in the base `aws-amplify` package. Real-time messaging via AWS IoT Core. Configure an IoT endpoint and attach an IAM policy for authenticated users in `amplify/backend.ts`: ```typescript import * as iam from 'aws-cdk-lib/aws-iam'; const pubsubStack = backend.createStack('PubSubStack'); backend.auth.resources.authenticatedUserIamRole.addToPrincipalPolicy( new iam.PolicyStatement({ actions: ['iot:Connect', 'iot:Publish', 'iot:Subscribe', 'iot:Receive'], resources: [ `arn:aws:iot:*:*:client/\${cognito-identity.amazonaws.com:sub}`, `arn:aws:iot:*:*:topic/amplify/*`, `arn:aws:iot:*:*:topicfilter/amplify/*`, ], }) ); backend.addOutput({ custom: { iotEndpoint: 'your-iot-endpoint.iot.region.amazonaws.com' }, }); ``` **Frontend** — subscribe and publish: ```typescript import { PubSub } from '@aws-amplify/pubsub'; const sub = PubSub.subscribe({ topics: ['myTopic'] }).subscribe({ next: (data) => console.log('Message:', data), error: (err) => console.error(err), }); await PubSub.publish({ topics: ['myTopic'], message: { msg: 'hello' } }); sub.unsubscribe(); // Always unsubscribe to prevent leaks ``` When using subscriptions in React, wrap in `useEffect` and return cleanup function to call `.unsubscribe()`. Retrieve the IoT endpoint programmatically: `aws iot describe-endpoint --endpoint-type iot:Data-ATS`. ## Custom CDK Stacks — Backend Only Create additional CloudFormation stacks for resources not natively supported by Amplify: ```typescript const backend = defineBackend({ auth, data }); const customStack = backend.createStack('AnalyticsStack'); // Use any CDK construct in the custom stack import * as sns from 'aws-cdk-lib/aws-sns'; const topic = new sns.Topic(customStack, 'NotificationTopic'); // Access Amplify resources from custom stack const userPool = backend.auth.resources.userPool; ``` Stack names must be unique within the backend — duplicate names cause deployment failures. Use descriptive names like `'EmailStack'`, `'AnalyticsStack'`. ## Backend Overrides — Backend Only Access and modify underlying CloudFormation resources when Amplify's high-level API does not expose a needed property: ```typescript const backend = defineBackend({ auth, data }); // Auth override: Access the underlying CFN user pool resource const cfnUserPool = backend.auth.resources.cfnResources.cfnUserPool; cfnUserPool.policies = { passwordPolicy: { minimumLength: 12, requireLowercase: true, requireUppercase: true, requireNumbers: true, requireSymbols: true, }, }; // DynamoDB override: Access underlying CFN resources const { cfnResources } = backend.data.resources; // Enable point-in-time recovery cfnResources.amplifyDynamoDbTables['Todo'].pointInTimeRecoveryEnabled = true; // Change billing mode cfnResources.amplifyDynamoDbTables['Todo'].billingMode = 'PAY_PER_REQUEST'; // Set TTL cfnResources.amplifyDynamoDbTables['Todo'].timeToLiveAttribute = { attributeName: 'ttl', enabled: true, }; ``` The entry point for DynamoDB table overrides is `backend.data.resources.cfnResources.amplifyDynamoDbTables['ModelName']`, which exposes L1 CFN properties directly. To add a **Global Secondary Index**, use `.secondaryIndexes()` in the schema definition (the Amplify-native approach) rather than CDK overrides: ```typescript const schema = a.schema({ Todo: a.model({ content: a.string(), status: a.string(), createdAt: a.datetime(), }) .secondaryIndexes(index => [ index('status').sortKeys(['createdAt']), ]) .authorization(allow => [allow.owner()]), }); ``` See [AWS Amplify Override docs](https://docs.amplify.aws/react/build-a-backend/add-aws-services/overriding-resources/) for the full override API. ## Custom Outputs — Backend Only Expose custom resource values to the frontend via `amplify_outputs.json`: ```typescript backend.addOutput({ custom: { analyticsTopicArn: topic.topicArn, apiEndpoint: 'https://api.example.com', }, }); ``` Values appear under the `custom` key in `amplify_outputs.json`. Frontend reads them from the Amplify configuration after `Amplify.configure()`. ## Face Liveness — Backend + Frontend Verify user identity with Amazon Rekognition Face Liveness. Add IAM permissions in `amplify/backend.ts`: ```typescript import * as iam from 'aws-cdk-lib/aws-iam'; backend.auth.resources.authenticatedUserIamRole.addToPrincipalPolicy( new iam.PolicyStatement({ actions: [ 'rekognition:CreateFaceLivenessSession', 'rekognition:StartFaceLivenessSession', 'rekognition:GetFaceLivenessSessionResults', ], resources: ['*'], // Rekognition session ARNs are generated at runtime — scope with conditions if needed }) ); ``` **Frontend (React):** ```bash npm install @aws-amplify/ui-react-liveness ``` ```tsx import { FaceLivenessDetector } from '@aws-amplify/ui-react-liveness'; { /* fetch results */ }} /> ``` Create the session server-side via Rekognition SDK or a Lambda function, then pass the `sessionId` to the component. See [AWS Amplify Liveness docs](https://ui.docs.amplify.aws/react/connected-components/liveness) for the full integration guide. **Frontend (Swift — iOS 14+):** Requires the `amplify-ui-swift-liveness` package and camera permission (`NSCameraUsageDescription` in `Info.plist`). Add the package via Xcode SPM: `https://github.com/aws-amplify/amplify-ui-swift-liveness`. The backend must include a Cognito Identity Pool with an IAM role that grants `rekognition:StartFaceLivenessSession` and `rekognition:GetFaceLivenessSessionResults`. See [Swift Liveness docs](https://ui.docs.amplify.aws/swift/connected-components/liveness) for the full SwiftUI integration guide. **Frontend (Android — API 24+):** Add the dependency to `app/build.gradle.kts`: ```kotlin dependencies { implementation("com.amplifyframework.ui:liveness:1.+") } ``` Requires Jetpack Compose. The backend must include a Cognito Identity Pool with an IAM role that grants `rekognition:StartFaceLivenessSession` and `rekognition:GetFaceLivenessSessionResults`. See [Android Liveness docs](https://ui.docs.amplify.aws/android/connected-components/liveness) for the full Compose integration guide. ## Avoiding Circular Dependencies Complex apps with storage triggers + auth groups + data models can create circular CloudFormation dependencies. Solutions: 1. **Incremental deployment:** Deploy auth + data first, then add storage triggers in a subsequent deployment 2. **Separate stacks:** Use `backend.createStack('storage-triggers')` to isolate trigger resources 3. **Avoid `.handler()` + manual grants:** When using `a.query().handler(fn)`, don't also call `grantReadData()` — `.handler()` auto-grants access See: [Troubleshoot circular dependencies](https://docs.amplify.aws/vue/build-a-backend/troubleshooting/circular-dependency/) ### Storage Triggers Writing to Data Tables — BLOCKED Storage triggers that need to write to DynamoDB tables in the data stack create a circular dependency that CANNOT be resolved with `resourceGroupName`. **Workarounds:** 1. Have the trigger write to a separate DynamoDB table (created via CDK, not `defineData`) 2. Use EventBridge to decouple: trigger → EventBridge → Lambda → DynamoDB 3. Handle metadata creation client-side after upload completes ## Pitfalls - **Duplicate stack names:** `backend.createStack()` names must be unique across the entire backend — reusing a name silently overwrites. - **Missing IAM permissions:** Geo, PubSub, and Face Liveness all require explicit IAM policies — Amplify does not auto-grant access to these services. - **Geo CDK setup:** Geo (maps, place search, geofencing) requires CDK constructs — there is no `defineGeo()` in Amplify Gen2. Use `aws-cdk-lib/aws-location` directly as shown above. - **PubSub endpoint:** Configure the correct IoT endpoint for your region — using the wrong endpoint type causes silent connection failures. ## Links - [Add AWS Services](https://docs.amplify.aws/react/build-a-backend/add-aws-services/) - [Custom Resources](https://docs.amplify.aws/react/build-a-backend/add-aws-services/custom-resources/) - [Overriding Resources](https://docs.amplify.aws/react/build-a-backend/add-aws-services/overriding-resources/)