diff --git a/cspell.json b/cspell.json
index 74ecac7788d..2e814113f0a 100644
--- a/cspell.json
+++ b/cspell.json
@@ -1091,6 +1091,7 @@
"putVocabularies",
"pythonapi",
"qafh",
+ "qconnect",
"QLAPINONEDS",
"QLAPI",
"QLID",
@@ -1353,6 +1354,7 @@
"Unauth",
"uncategorized",
"uncommenting",
+ "unconfigured",
"unencrypted",
"unioned",
"unmarshall",
diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx
index aa3db961b70..e40e0bc155c 100644
--- a/src/components/Layout/Layout.tsx
+++ b/src/components/Layout/Layout.tsx
@@ -396,7 +396,10 @@ export const Layout = ({
/>
)}
- {(asPathWithNoHash.includes('/push-notifications/') ||
+ {((asPathWithNoHash.includes('/push-notifications/') &&
+ !asPathWithNoHash.includes(
+ '/push-notifications/customer-profiles/'
+ )) ||
asPathWithNoHash.includes('/analytics/') ||
asPathWithNoHash.includes('/in-app-messaging/')) &&
!asPathWithNoHash.includes('/kinesis') &&
diff --git a/src/components/Layout/__tests__/Layout.test.tsx b/src/components/Layout/__tests__/Layout.test.tsx
index ac116f9725a..c4d6ce802c4 100644
--- a/src/components/Layout/__tests__/Layout.test.tsx
+++ b/src/components/Layout/__tests__/Layout.test.tsx
@@ -3,13 +3,18 @@ import { render, screen } from '@testing-library/react';
import { Layout } from '../index';
import userEvent from '@testing-library/user-event';
+const routerState: { asPath: string; pathname: string } = {
+ asPath: '/[platform]/start/getting-started/introduction/',
+ pathname: '/[platform]/start/getting-started/introduction/'
+};
+
const routerMock = {
__esModule: true,
useRouter: () => {
return {
query: { platform: 'react' },
- pathname: '/[platform]/start/getting-started/introduction/',
- asPath: '/[platform]/start/getting-started/introduction/'
+ pathname: routerState.pathname,
+ asPath: routerState.asPath
};
}
};
@@ -32,6 +37,11 @@ describe('Layout', () => {
);
+ beforeEach(() => {
+ routerState.asPath = '/[platform]/start/getting-started/introduction/';
+ routerState.pathname = '/[platform]/start/getting-started/introduction/';
+ });
+
it('should render the Layout component', async () => {
render(layoutComponent);
const layout = await screen.getByRole('main', { name: 'Main content' });
@@ -108,4 +118,40 @@ describe('Layout', () => {
'layout-sidebar__mobile-toggle--open'
);
});
+
+ describe('PinpointEOLBanner', () => {
+ it('is not rendered for the Customer Profiles push-notifications client pages', async () => {
+ routerState.asPath =
+ '/react/frontend/push-notifications/customer-profiles/';
+ render(layoutComponent);
+ expect(document.body.textContent).not.toContain('Amazon Pinpoint');
+ });
+
+ it('is not rendered for the Customer Profiles push-notifications child pages', async () => {
+ routerState.asPath =
+ '/react/frontend/push-notifications/customer-profiles/register-device/';
+ render(layoutComponent);
+ expect(document.body.textContent).not.toContain('Amazon Pinpoint');
+ });
+
+ it('is rendered for a non-Customer-Profiles push-notifications sibling route', async () => {
+ routerState.asPath =
+ '/react/frontend/push-notifications/customer-profiles-legacy/';
+ render(layoutComponent);
+ expect(document.body.textContent).toContain('Amazon Pinpoint');
+ });
+
+ it('is rendered for the legacy (gen1) push-notifications pages', async () => {
+ routerState.asPath =
+ '/gen1/react/build-a-backend/push-notifications/set-up-push-notifications/';
+ render(layoutComponent);
+ expect(document.body.textContent).toContain('Amazon Pinpoint');
+ });
+
+ it('is rendered for analytics pages', async () => {
+ routerState.asPath = '/react/build-a-backend/add-aws-services/analytics/';
+ render(layoutComponent);
+ expect(document.body.textContent).toContain('Amazon Pinpoint');
+ });
+ });
});
diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs
index cfcc6c1e857..2b88f048c70 100644
--- a/src/directory/directory.mjs
+++ b/src/directory/directory.mjs
@@ -512,6 +512,24 @@ export const directory = {
}
]
},
+ {
+ path: 'src/pages/[platform]/build-a-backend/add-aws-services/notifications/index.mdx',
+ section: 'backend',
+ children: [
+ {
+ path: 'src/pages/[platform]/build-a-backend/add-aws-services/notifications/set-up-notifications/index.mdx',
+ section: 'backend'
+ },
+ {
+ path: 'src/pages/[platform]/build-a-backend/add-aws-services/notifications/author-message-templates/index.mdx',
+ section: 'backend'
+ },
+ {
+ path: 'src/pages/[platform]/build-a-backend/add-aws-services/notifications/existing-resources/index.mdx',
+ section: 'backend'
+ }
+ ]
+ },
{
path: 'src/pages/[platform]/build-a-backend/add-aws-services/pinpoint-migration/index.mdx',
section: 'backend'
@@ -873,6 +891,35 @@ export const directory = {
}
]
},
+ {
+ path: 'src/pages/[platform]/frontend/push-notifications/customer-profiles/index.mdx',
+ children: [
+ {
+ path: 'src/pages/[platform]/frontend/push-notifications/customer-profiles/identify-user/index.mdx'
+ },
+ {
+ path: 'src/pages/[platform]/frontend/push-notifications/customer-profiles/register-device/index.mdx'
+ },
+ {
+ path: 'src/pages/[platform]/frontend/push-notifications/customer-profiles/remove-device/index.mdx'
+ },
+ {
+ path: 'src/pages/[platform]/frontend/push-notifications/customer-profiles/guest-and-authenticated-users/index.mdx'
+ },
+ {
+ path: 'src/pages/[platform]/frontend/push-notifications/customer-profiles/request-permissions/index.mdx'
+ },
+ {
+ path: 'src/pages/[platform]/frontend/push-notifications/customer-profiles/manage-device-token/index.mdx'
+ },
+ {
+ path: 'src/pages/[platform]/frontend/push-notifications/customer-profiles/interact-with-notifications/index.mdx'
+ },
+ {
+ path: 'src/pages/[platform]/frontend/push-notifications/customer-profiles/app-badge-count/index.mdx'
+ }
+ ]
+ },
{
path: 'src/pages/[platform]/frontend/geo/index.mdx',
children: [
diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/notifications/author-message-templates/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/notifications/author-message-templates/index.mdx
new file mode 100644
index 00000000000..e950ef967be
--- /dev/null
+++ b/src/pages/[platform]/build-a-backend/add-aws-services/notifications/author-message-templates/index.mdx
@@ -0,0 +1,100 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Author message templates',
+ description:
+ 'Create a push message template with the Amazon Q in Connect API and connect it to an Amazon Connect journey custom action.',
+ platforms: [
+ 'javascript',
+ 'react-native',
+ 'swift',
+ 'android',
+ 'flutter',
+ 'angular',
+ 'nextjs',
+ 'react',
+ 'vue'
+ ]
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ return {
+ props: {
+ platform: context.params.platform,
+ meta
+ }
+ };
+}
+
+Amplify provisions the delivery path for your notifications, but not the message content. The text your users receive comes from a **push message template** stored in an Amazon Q in Connect knowledge base.
+
+
+
+**The Amazon Q in Connect console does not support authoring PUSH-channel message templates.** You create and publish push templates with the `qconnect` AWS CLI (or the underlying Amazon Q in Connect API), against the knowledge base that `defineNotifications` provisions. Amplify does not create the templates themselves, and a journey that has no matching template falls back to default placeholder copy.
+
+
+
+## Name the template to match your journey
+
+A push template's name is the only way Amplify links it to a journey: give the template the exact same name as the custom action block in your Amazon Connect journey.
+
+## Find your knowledge base ID
+
+In create mode, `defineNotifications` provisions an empty `MESSAGE_TEMPLATES` knowledge base and associates it with your Amazon Connect instance through the `Q_MESSAGE_TEMPLATES` integration. In attach mode, associate a `MESSAGE_TEMPLATES` knowledge base with your existing Connect instance yourself so this lookup resolves.
+
+Either way, look up the knowledge base ID with the AWS CLI:
+
+```bash title="Terminal" showLineNumbers={false}
+aws connect list-integration-associations \
+ --instance-id \
+ --integration-type Q_MESSAGE_TEMPLATES
+```
+
+The knowledge base ID is the last segment of the returned `IntegrationArn`, which ends in `knowledge-base/`.
+
+## Create the message template
+
+Create the template with `qconnect create-message-template`, using the same name you will give the journey's custom action block:
+
+```bash title="Terminal" showLineNumbers={false}
+aws qconnect create-message-template \
+ --knowledge-base-id \
+ --name 'Push Notification' \
+ --channel-subtype PUSH \
+ --content '{"push":{"apns":{"title":"Hello {{Attributes.firstName}}","body":{"content":"Your order shipped."}},"fcm":{"title":"Hello {{Attributes.firstName}}","body":{"content":"Your order shipped."}}}}'
+```
+
+Per-platform content maps to channels as `push.apns` (Apple Push Notification service, including the sandbox channel) and `push.fcm` (Firebase Cloud Messaging). A platform entry needs both a title and a body to be used; a platform you leave out falls back to default copy for that platform.
+
+Publish a version after every copy change:
+
+```bash title="Terminal" showLineNumbers={false}
+aws qconnect create-message-template-version \
+ --knowledge-base-id \
+ --message-template-id
+```
+
+## Personalize the message
+
+Templates support `{{Attributes.}}` variables, which resolve to the target Customer Profile's fields, including custom attributes set on the profile:
+
+```handlebars
+Hello {{Attributes.firstName}}, your order is on the way.
+```
+
+A variable with no matching profile value stays literal in the rendered copy, so use variables only for fields you populate consistently, and prefer copy that reads correctly when a value is absent.
+
+See the [Amazon Q in Connect message template API reference](https://docs.aws.amazon.com/amazon-q-connect/latest/APIReference/API_CreateMessageTemplate.html) for the full content schema.
+
+## Add the custom action to your journey
+
+Building the journey itself is done in the Amazon Connect console; only template authoring is CLI-only:
+
+1. In the Amazon Connect console, navigate to **Journeys** and open or create a journey.
+2. Add a **custom action** block and point it at the push-delivery Lambda function that Amplify deployed.
+3. Set the block name to the exact `--name` value you gave the template above, including capitalization.
+4. Publish the journey.
diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/notifications/existing-resources/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/notifications/existing-resources/index.mdx
new file mode 100644
index 00000000000..23be263ae17
--- /dev/null
+++ b/src/pages/[platform]/build-a-backend/add-aws-services/notifications/existing-resources/index.mdx
@@ -0,0 +1,68 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Use existing AWS resources',
+ description:
+ 'Attach your Amplify notifications resource to an Amazon Connect Customer Profiles domain that already exists.',
+ platforms: [
+ 'javascript',
+ 'react-native',
+ 'swift',
+ 'android',
+ 'flutter',
+ 'angular',
+ 'nextjs',
+ 'react',
+ 'vue'
+ ]
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ return {
+ props: {
+ platform: context.params.platform,
+ meta
+ }
+ };
+}
+
+Pass `domainName` to `defineNotifications` to attach to an Amazon Connect Customer Profiles domain that already exists:
+
+```ts title="amplify/backend.ts"
+import { defineBackend } from '@aws-amplify/backend';
+import { defineNotifications } from '@aws-amplify/backend-notifications';
+import { auth } from './auth/resource';
+
+defineBackend({
+ auth,
+ notifications: defineNotifications({
+ domainName: 'amazon-connect-my-instance'
+ })
+});
+```
+
+In this mode Amplify **attaches** to the domain rather than creating one. It registers the `AmplifyProfile` object type into the domain additively and does not create an Amazon Connect instance or a Customer Profiles domain. Object types that already exist on the domain are left unchanged, so you can attach to a domain that other workloads are using.
+
+## Requirements for the existing domain
+
+The domain must meet the following requirements:
+
+- **Same AWS account and Region as your Amplify project.** The construct references the domain by name, which resolves only within the account and Region your backend deploys to.
+- **The name must begin with `amazon-connect-`.** This is a functional requirement, not a naming preference. The AWS-managed `AmazonConnectServiceLinkedRolePolicy` grants your Amazon Connect instance access to Customer Profiles only on domains matching `arn:aws:profile:*:*:domains/amazon-connect-*`. A domain named otherwise is unreachable by the instance, and the Amazon Connect console reports that it does not have permission to access Customer Profiles. Because the service-linked role and its managed policy cannot be edited, the domain name is the only way to satisfy the policy.
+- **Customer Profiles enabled on your Amazon Connect instance.** The most common domain to attach to is the one Amazon Connect creates for you when you enable Customer Profiles, which is named `amazon-connect-`.
+
+
+
+To find the domain name, open the [Amazon Connect console](https://console.aws.amazon.com/connect/), select your instance, and choose **Customer Profiles**.
+
+
+
+## Properties that do not apply
+
+`instanceAlias` and `expirationDays` configure a domain that Amplify creates, so they cannot be combined with `domainName`. Set the data retention period on the existing domain in the Amazon Connect console instead.
+
+To have Amplify create the Amazon Connect instance and the domain for you, see [Set up notifications](/[platform]/build-a-backend/add-aws-services/notifications/set-up-notifications/).
diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/notifications/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/notifications/index.mdx
new file mode 100644
index 00000000000..8f3249e29d6
--- /dev/null
+++ b/src/pages/[platform]/build-a-backend/add-aws-services/notifications/index.mdx
@@ -0,0 +1,89 @@
+import { getChildPageNodes } from '@/utils/getChildPageNodes';
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Push Notifications',
+ description:
+ 'Send push notifications to your users with a backend powered by Amazon Connect Customer Profiles.',
+ route: '/[platform]/build-a-backend/add-aws-services/notifications',
+ platforms: [
+ 'javascript',
+ 'react-native',
+ 'swift',
+ 'android',
+ 'flutter',
+ 'angular',
+ 'nextjs',
+ 'react',
+ 'vue'
+ ]
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ const childPageNodes = getChildPageNodes(meta.route);
+ return {
+ props: {
+ platform: context.params.platform,
+ meta,
+ childPageNodes
+ }
+ };
+}
+
+Amplify push notifications are backed by [Amazon Connect Customer Profiles](https://docs.aws.amazon.com/connect/latest/adminguide/customer-profiles.html). A Customer Profile holds the profile information for one person, keyed on a server-derived `principalId`. That person's registered push devices are stored separately, in a DynamoDB device store, associated to the same `principalId`; they are not embedded in the Customer Profile object. You send a notification by targeting profiles from an [Amazon Connect journey](https://docs.aws.amazon.com/connect/latest/adminguide/journeys.html), which calls into your Amplify backend to deliver the message to that person's devices.
+
+If you are moving an existing application off the Pinpoint-backed push notifications feature, start with [Migrate from Pinpoint-backed features](/[platform]/build-a-backend/add-aws-services/pinpoint-migration/).
+
+Add the resource to your backend with the `defineNotifications` construct:
+
+```ts title="amplify/backend.ts"
+import { defineBackend } from '@aws-amplify/backend';
+import { defineNotifications } from '@aws-amplify/backend-notifications';
+import { auth } from './auth/resource';
+
+defineBackend({
+ auth,
+ notifications: defineNotifications()
+});
+```
+
+## What gets deployed
+
+Deploying `defineNotifications` provisions the following resources:
+
+- An **`AmplifyProfile` Customer Profiles object type**, registered on your Customer Profiles domain. It is keyed on `principalId`, a searchable and unique field that holds the Amazon Cognito identity pool `identityId` of the caller. Your backend derives `principalId` from the signed request, so a client never sends it and cannot write to another person's profile.
+- A **DynamoDB device store** that holds the registered push devices. Device records live in this table rather than in Customer Profiles.
+- Three **HTTP API routes** (`identify-user`, `register-device`, and `remove-device`), authorized with AWS Identity and Access Management (IAM) and called with [Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) signed requests. A single least-privilege Lambda function serves all three routes.
+- A **push-delivery Lambda function**, invoked as a custom action from an Amazon Connect journey. It looks up the target profile's devices and sends the message to each one.
+- An **AWS End User Messaging application**, with the APNs and FCM channels you configure.
+
+Depending on the mode you choose, an Amazon Connect instance and a Customer Profiles domain are created for you, or an existing domain is used. See [Set up notifications](/[platform]/build-a-backend/add-aws-services/notifications/set-up-notifications/) for both modes.
+
+## Review the generated configuration
+
+After a successful deployment, the invoke endpoint and region are written to the `notifications` section of `amplify_outputs.json`:
+
+```json title="amplify_outputs.json"
+{
+ "notifications": {
+ "amazon_connect": {
+ "endpoint": "https://abcdefghij.execute-api.us-east-1.amazonaws.com",
+ "aws_region": "us-east-1"
+ }
+ }
+}
+```
+
+Your frontend reads these values when you pass `amplify_outputs.json` to `Amplify.configure()`.
+
+
+
+Message content is not provisioned by Amplify. After you deploy, author a push message template in Amazon Q in Connect and connect it to a journey. See [Author message templates](/[platform]/build-a-backend/add-aws-services/notifications/author-message-templates/).
+
+
+
+
diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/notifications/set-up-notifications/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/notifications/set-up-notifications/index.mdx
new file mode 100644
index 00000000000..b07fdfcc6ed
--- /dev/null
+++ b/src/pages/[platform]/build-a-backend/add-aws-services/notifications/set-up-notifications/index.mdx
@@ -0,0 +1,193 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Set up notifications',
+ description:
+ 'Add push notifications to your Amplify backend with the defineNotifications construct, either by creating a new Amazon Connect instance or by attaching to an existing Customer Profiles domain.',
+ platforms: [
+ 'javascript',
+ 'react-native',
+ 'swift',
+ 'android',
+ 'flutter',
+ 'angular',
+ 'nextjs',
+ 'react',
+ 'vue'
+ ]
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ return {
+ props: {
+ platform: context.params.platform,
+ meta
+ }
+ };
+}
+
+The `defineNotifications` construct adds an Amazon Connect Customer Profiles-backed push notification resource to your Amplify backend.
+
+## Prerequisites
+
+Before you begin, complete the following steps:
+
+1. Add authentication to your backend. The notification APIs are called with [Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) signed requests using Amazon Cognito identity pool credentials, so an `auth` resource is required. Both signed-in and guest users can call the APIs; see [Guest access](/[platform]/build-a-backend/auth/concepts/guest-access/).
+2. Install the notifications package:
+
+```bash title="Terminal" showLineNumbers={false}
+npm add @aws-amplify/backend-notifications
+```
+
+3. Gather the credentials for each push channel you want to enable:
+
+ - **Apple Push Notification service (APNs)**: a token-based signing key (`.p8`), its key ID, your Apple team ID, and your application's bundle ID.
+ - **Firebase Cloud Messaging (FCM)**: the service account JSON for your Firebase project.
+
+4. Store the sensitive credentials as secrets. The signing key and the service account JSON are referenced with `secret()` rather than written into your backend code:
+
+```bash title="Terminal" showLineNumbers={false}
+npx ampx sandbox secret set APNS_SIGNING_KEY
+npx ampx sandbox secret set FCM_SERVICE_ACCOUNT_JSON
+```
+
+To learn more about secrets, see [Environment variables and secrets](/[platform]/build-a-backend/functions/environment-variables-and-secrets/).
+
+## Choose how the Customer Profiles domain is provisioned
+
+`defineNotifications` supports two modes. The mode is determined by whether you pass `domainName`.
+
+
+
+
+Omit `domainName` to create everything from scratch. Amplify provisions a new Amazon Connect instance and a new Customer Profiles domain, then registers the `AmplifyProfile` object type into that domain. No pre-existing Amazon Connect setup is required.
+
+```ts title="amplify/backend.ts"
+import { defineBackend } from '@aws-amplify/backend';
+import { defineNotifications } from '@aws-amplify/backend-notifications';
+import { auth } from './auth/resource';
+
+defineBackend({
+ auth,
+ notifications: defineNotifications()
+});
+```
+
+Two optional properties are available in this mode:
+
+- `instanceAlias`: the alias for the new Amazon Connect instance. Aliases are globally unique within an AWS Region. When you omit this property, Amplify generates a stable name for your Amplify project.
+- `expirationDays`: how long Customer Profiles retains profile data in the new domain.
+
+```ts title="amplify/backend.ts"
+notifications: defineNotifications({
+ instanceAlias: 'my-app-notifications',
+ expirationDays: 90
+});
+```
+
+
+
+The first deployment in this mode takes longer than a typical Amplify deployment because it provisions an Amazon Connect instance and a Customer Profiles domain.
+
+
+
+
+
+
+Pass `domainName` to attach to a Customer Profiles domain that already exists. Amplify registers the `AmplifyProfile` object type into that domain additively, and does not create an Amazon Connect instance or a domain.
+
+```ts title="amplify/backend.ts"
+import { defineBackend } from '@aws-amplify/backend';
+import { defineNotifications } from '@aws-amplify/backend-notifications';
+import { auth } from './auth/resource';
+
+defineBackend({
+ auth,
+ notifications: defineNotifications({
+ domainName: 'amazon-connect-my-instance'
+ })
+});
+```
+
+`instanceAlias` and `expirationDays` apply only when Amplify creates the domain, so they cannot be combined with `domainName`.
+
+For the requirements your existing domain must meet, see [Use existing resources](/[platform]/build-a-backend/add-aws-services/notifications/existing-resources/).
+
+
+
+
+## Configure push channels
+
+Add `apns`, `fcm`, or both to enable the corresponding channel on the AWS End User Messaging application that Amplify creates. Channels are optional and independent; omit one to leave it unconfigured.
+
+```ts title="amplify/backend.ts"
+import { defineBackend, secret } from '@aws-amplify/backend';
+import { defineNotifications } from '@aws-amplify/backend-notifications';
+import { auth } from './auth/resource';
+
+defineBackend({
+ auth,
+ notifications: defineNotifications({
+ apns: {
+ tokenKey: secret('APNS_SIGNING_KEY'),
+ tokenKeyId: 'A1B2C3D4E5',
+ teamId: 'T9S8R7Q6P5',
+ bundleId: 'com.example.myapp'
+ },
+ fcm: {
+ serviceJson: secret('FCM_SERVICE_ACCOUNT_JSON')
+ }
+ })
+});
+```
+
+The `apns` properties are:
+
+| Property | Required | Description |
+| --- | --- | --- |
+| `tokenKey` | Yes | The contents of your APNs token signing key (`.p8`), supplied with `secret()`. |
+| `tokenKeyId` | Yes | The ID of the signing key. |
+| `teamId` | Yes | Your Apple developer team ID. |
+| `bundleId` | Yes | The bundle ID of your application. |
+| `sandbox` | No | Set to `true` to send through the APNs sandbox environment, which is used for development builds. Defaults to `false`. |
+
+The `fcm` property is:
+
+| Property | Required | Description |
+| --- | --- | --- |
+| `serviceJson` | Yes | The contents of your Firebase service account JSON, supplied with `secret()`. |
+
+
+
+Pass `tokenKey` and `serviceJson` with `secret()`. Do not paste the key material into `amplify/backend.ts`; it would be committed to your repository.
+
+
+
+## Deploy and review the outputs
+
+Deploy the resource to your personal cloud sandbox:
+
+```bash title="Terminal" showLineNumbers={false}
+npx ampx sandbox
+```
+
+When the deployment finishes, the invoke endpoint and region are written to `amplify_outputs.json`:
+
+```json title="amplify_outputs.json"
+{
+ "notifications": {
+ "amazon_connect": {
+ "endpoint": "https://abcdefghij.execute-api.us-east-1.amazonaws.com",
+ "aws_region": "us-east-1"
+ }
+ }
+}
+```
+
+## Next steps
+
+- [Author message templates](/[platform]/build-a-backend/add-aws-services/notifications/author-message-templates/): create the push message content in Amazon Q in Connect.
diff --git a/src/pages/[platform]/frontend/push-notifications/customer-profiles/app-badge-count/index.mdx b/src/pages/[platform]/frontend/push-notifications/customer-profiles/app-badge-count/index.mdx
new file mode 100644
index 00000000000..193d975b337
--- /dev/null
+++ b/src/pages/[platform]/frontend/push-notifications/customer-profiles/app-badge-count/index.mdx
@@ -0,0 +1,51 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'App badge count',
+ description:
+ 'Get and set the number displayed on your application icon.',
+ platforms: ['react-native']
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ return {
+ props: {
+ platform: context.params.platform,
+ meta
+ }
+ };
+}
+
+Use `getBadgeCount` and `setBadgeCount` to read and update the number displayed on your application's icon. Call `initializePushNotifications()` first; see [Push notifications](/[platform]/frontend/push-notifications/customer-profiles/).
+
+## Get the current badge count
+
+```ts
+import { getBadgeCount } from 'aws-amplify/push-notifications/customer-profiles';
+
+const badgeCount = await getBadgeCount();
+```
+
+`getBadgeCount` resolves to the current count, or `null` on a platform that does not support badges.
+
+## Set the badge count
+
+```ts
+import { setBadgeCount } from 'aws-amplify/push-notifications/customer-profiles';
+
+setBadgeCount(3);
+```
+
+Set the count to `0` to clear the badge from your application's icon:
+
+```ts
+import { setBadgeCount } from 'aws-amplify/push-notifications/customer-profiles';
+
+setBadgeCount(0);
+```
+
+`setBadgeCount` is safe to call on a platform that does not support badges. The call is ignored rather than throwing an error.
diff --git a/src/pages/[platform]/frontend/push-notifications/customer-profiles/guest-and-authenticated-users/index.mdx b/src/pages/[platform]/frontend/push-notifications/customer-profiles/guest-and-authenticated-users/index.mdx
new file mode 100644
index 00000000000..804e682654b
--- /dev/null
+++ b/src/pages/[platform]/frontend/push-notifications/customer-profiles/guest-and-authenticated-users/index.mdx
@@ -0,0 +1,94 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Guest and authenticated users',
+ description:
+ 'How Amazon Connect Customer Profiles notifications work for guest and signed-in users, and how a device moves to the authenticated identity on sign-in.',
+ platforms: ['react-native']
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ return {
+ props: {
+ platform: context.params.platform,
+ meta
+ }
+ };
+}
+
+The Customer Profiles notification APIs work for guest users and signed-in users. Both are supported through the same mechanism, so your application does not need a separate code path for each.
+
+## How both identities are supported
+
+Every request to your notifications backend is signed with [Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) using credentials from your Amazon Cognito identity pool:
+
+- A **guest** user receives credentials from the identity pool's unauthenticated role.
+- A **signed-in** user receives credentials from the identity pool's authenticated role.
+
+Both are signed identically, and your backend derives the profile identity, `principalId`, from the signer identity of the request. Because that identifier comes from the credentials rather than from your application, no identity field is sent by the client and no code change is needed to support guests.
+
+Guest access must be enabled on your `auth` resource for unauthenticated callers to receive credentials. See [Guest access](/[platform]/build-a-backend/auth/concepts/guest-access/).
+
+
+
+A guest identity is scoped to the installation, not to a person. Treat a guest profile as belonging to the device until a user signs in.
+
+
+
+## A device moves to the authenticated identity on sign-in
+
+A guest user's push token and a signed-in user's push token are the same value, because the native platform issues the token to the installation rather than to a user. Without any handling, a device registered as a guest would stay attached to the guest identity after the user signed in, and journeys targeting the authenticated profile would not reach it.
+
+The library handles this for you. After `initializePushNotifications` has been called, it listens for the sign-in event and re-registers the device, which moves the existing registration from the guest identity to the authenticated one. Because registration is an idempotent operation keyed on a device identifier that is stable for the installation, this updates the existing record rather than creating a second one.
+
+No application code is required for this to happen:
+
+```ts title="index.js"
+import { Amplify } from 'aws-amplify';
+import { initializePushNotifications } from 'aws-amplify/push-notifications/customer-profiles';
+import outputs from './amplify_outputs.json';
+
+Amplify.configure(outputs);
+initializePushNotifications();
+```
+
+Re-registration on sign-in is a best-effort operation. A failure is logged and does not interrupt sign-in, so a device may briefly remain on the guest identity if the request fails. The device is re-registered on the next sign-in.
+
+If no push token has been issued yet when a user signs in, there is nothing to move. The first registration then happens against the authenticated identity once the native platform issues the token.
+
+## Sign a user out
+
+Signing a user out does not de-register their device. Call `removeDevice` while the user is still signed in, because de-registration is signed with their credentials:
+
+```ts
+import { signOut } from 'aws-amplify/auth';
+import { removeDevice } from 'aws-amplify/push-notifications/customer-profiles';
+
+await removeDevice();
+await signOut();
+```
+
+After `signOut` completes, subsequent calls are signed as a new guest identity. See [Remove a device](/[platform]/frontend/push-notifications/customer-profiles/remove-device/).
+
+## Profile information for guests
+
+`identifyUser` works for guest users, so you can store profile details before a user creates an account. Those details are written to the guest profile.
+
+A guest profile and an authenticated profile are separate profiles, because they have different `principalId` values. Profile information is not copied between them when a user signs in, so call `identifyUser` after sign-in to populate the authenticated profile with the details you hold:
+
+```ts
+import { identifyUser } from 'aws-amplify/push-notifications/customer-profiles';
+
+await identifyUser({
+ userProfile: {
+ email: 'jane@example.com',
+ name: 'Jane Doe'
+ }
+});
+```
+
+See [Identify a user](/[platform]/frontend/push-notifications/customer-profiles/identify-user/).
diff --git a/src/pages/[platform]/frontend/push-notifications/customer-profiles/identify-user/index.mdx b/src/pages/[platform]/frontend/push-notifications/customer-profiles/identify-user/index.mdx
new file mode 100644
index 00000000000..b0398030c18
--- /dev/null
+++ b/src/pages/[platform]/frontend/push-notifications/customer-profiles/identify-user/index.mdx
@@ -0,0 +1,104 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Identify a user',
+ description:
+ 'Send profile information for the current user to Amazon Connect Customer Profiles with the identifyUser API.',
+ platforms: ['react-native']
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ return {
+ props: {
+ platform: context.params.platform,
+ meta
+ }
+ };
+}
+
+Use `identifyUser` to send profile information for the current user to Amazon Connect Customer Profiles. The values you send populate the Customer Profile that your Amazon Connect journeys target and personalize messages with.
+
+```ts
+import { identifyUser } from 'aws-amplify/push-notifications/customer-profiles';
+
+await identifyUser({
+ userProfile: {
+ email: 'jane@example.com',
+ name: 'Jane Doe',
+ phone: '+15551234567',
+ location: {
+ city: 'Seattle',
+ country: 'US',
+ postalCode: '98101',
+ region: 'WA'
+ },
+ customAttributes: {
+ plan: 'premium',
+ favoriteCategory: 'outdoors'
+ }
+ }
+});
+```
+
+Every field is optional, so send only the values your application has. Each call replaces the fields you provide on the profile.
+
+## User profile fields
+
+| Field | Type | Description |
+| --- | --- | --- |
+| `email` | `string` | The user's email address. |
+| `name` | `string` | The user's name. |
+| `phone` | `string` | The user's phone number. |
+| `location` | `object` | The user's location. Accepts `city`, `country`, `postalCode`, and `region`, each a `string`. |
+| `customAttributes` | `Record` | Additional key-value pairs to store on the profile. |
+
+Values are validated before the request is sent. Every string, along with each `customAttributes` key and value, must be 255 characters or fewer, and `customAttributes` values must be strings. A profile that violates these bounds throws a validation error.
+
+
+
+`principalId` is a reserved `customAttributes` key and is rejected. Your backend uses it to store the profile identity.
+
+
+
+## How the profile identity is resolved
+
+Your application does not send a user identifier. Requests are signed with Signature Version 4 using Amazon Cognito identity pool credentials, and your backend derives the `principalId` for the profile from the signer identity of the request. A caller therefore cannot write to another user's profile, and there is no identifier for your application to manage.
+
+Because the identity comes from the credentials, `identifyUser` works for both signed-in and guest users. See [Guest and authenticated users](/[platform]/frontend/push-notifications/customer-profiles/guest-and-authenticated-users/).
+
+## When to call identifyUser
+
+Call `identifyUser` when the profile information you hold changes, for example:
+
+- After a user signs in, to associate their profile details with their authenticated identity.
+- After a user updates their contact details or preferences.
+
+`identifyUser` sends profile information only and performs no device work. To manage push devices, use [`registerDevice`](/[platform]/frontend/push-notifications/customer-profiles/register-device/) and [`removeDevice`](/[platform]/frontend/push-notifications/customer-profiles/remove-device/).
+
+
+
+`identifyUser` does not require `initializePushNotifications`. Its only prerequisites are a configured endpoint and identity pool credentials, so you can call it before push notifications are initialized.
+
+
+
+## Personalize messages with profile values
+
+Message templates reference profile values with the `Attributes` namespace, so a `customAttributes` entry named `favoriteCategory` is available to a template as `{{Attributes.favoriteCategory}}`. See [Author message templates](/[platform]/build-a-backend/add-aws-services/notifications/author-message-templates/).
+
+## Handle errors
+
+`identifyUser` returns a promise that rejects when validation fails or the endpoint returns an error, so handle failures where a rejected promise would otherwise go unobserved:
+
+```ts
+import { identifyUser } from 'aws-amplify/push-notifications/customer-profiles';
+
+try {
+ await identifyUser({ userProfile: { email: 'jane@example.com' } });
+} catch (error) {
+ console.error('Failed to identify user', error);
+}
+```
diff --git a/src/pages/[platform]/frontend/push-notifications/customer-profiles/index.mdx b/src/pages/[platform]/frontend/push-notifications/customer-profiles/index.mdx
new file mode 100644
index 00000000000..eb6eeca7eb9
--- /dev/null
+++ b/src/pages/[platform]/frontend/push-notifications/customer-profiles/index.mdx
@@ -0,0 +1,81 @@
+import { getChildPageNodes } from '@/utils/getChildPageNodes';
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Push Notifications',
+ description:
+ 'Send profile information and register push devices with Amazon Connect Customer Profiles from your application.',
+ route: '/[platform]/frontend/push-notifications/customer-profiles',
+ platforms: ['react-native']
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ const childPageNodes = getChildPageNodes(meta.route);
+ return {
+ props: {
+ platform: context.params.platform,
+ meta,
+ childPageNodes
+ }
+ };
+}
+
+The Amazon Connect Customer Profiles APIs let your application send profile information for the current user and manage that person's registered push devices. Devices are stored separately from the Customer Profile, in a DynamoDB device store, associated by `principalId`, rather than embedded inside the profile. Requests are signed with [Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) using Amazon Cognito identity pool credentials, and your backend derives the profile identity from the signed request.
+
+These APIs are exported from the `aws-amplify/push-notifications/customer-profiles` sub-path.
+
+
+
+This page assumes you have already deployed a notifications resource. See [Set up notifications](/[platform]/build-a-backend/add-aws-services/notifications/set-up-notifications/).
+
+
+
+## Install the library
+
+Push notifications require the native module in addition to `aws-amplify`:
+
+```bash title="Terminal" showLineNumbers={false}
+npm add aws-amplify @aws-amplify/react-native @aws-amplify/rtn-push-notification @react-native-async-storage/async-storage react-native-get-random-values
+```
+
+Because push notifications interact with the native platform, you also need to link the native module and grant your application the permission to receive remote notifications:
+
+- On iOS, run `npx pod-install`, add the push notification capability to your target in Xcode, and forward the remote notification callbacks from your `AppDelegate` to `AmplifyPushNotification`.
+- On Android, no additional integration steps are required beyond installing the packages above.
+
+## Configure Amplify
+
+Pass `amplify_outputs.json` to `Amplify.configure()`. The endpoint and Region that your backend deployed are read from the `notifications` section of that file.
+
+Configure Amplify and initialize push notifications at your application's root entry point. Initializing early allows your application to process notifications that arrive while it is in a terminated state.
+
+```ts title="index.js"
+import { AppRegistry } from 'react-native';
+import { Amplify } from 'aws-amplify';
+import { initializePushNotifications } from 'aws-amplify/push-notifications/customer-profiles';
+import outputs from './amplify_outputs.json';
+
+import App from './App';
+import { name as appName } from './app.json';
+
+Amplify.configure(outputs);
+initializePushNotifications();
+
+AppRegistry.registerComponent(appName, () => App);
+```
+
+`initializePushNotifications` sets up the native listeners the category depends on. Calling it more than once has no effect. Until it has been called, `registerDevice` and `removeDevice` throw a validation error.
+
+Once initialized, the library registers the device automatically the first time the native platform issues a push token, so most applications do not call `registerDevice` themselves. See [Register a device](/[platform]/frontend/push-notifications/customer-profiles/register-device/).
+
+
+
+Call `removeDevice` before signing a user out. De-registration is signed with the current user's credentials, so it cannot succeed after `signOut` has completed. See [Remove a device](/[platform]/frontend/push-notifications/customer-profiles/remove-device/).
+
+
+
+
diff --git a/src/pages/[platform]/frontend/push-notifications/customer-profiles/interact-with-notifications/index.mdx b/src/pages/[platform]/frontend/push-notifications/customer-profiles/interact-with-notifications/index.mdx
new file mode 100644
index 00000000000..6275f013f07
--- /dev/null
+++ b/src/pages/[platform]/frontend/push-notifications/customer-profiles/interact-with-notifications/index.mdx
@@ -0,0 +1,125 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Interact with notifications',
+ description:
+ 'Respond to push notifications being received and opened across the states your application can be in.',
+ platforms: ['react-native']
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ return {
+ props: {
+ platform: context.params.platform,
+ meta
+ }
+ };
+}
+
+Your application can be in one of three states when a notification arrives or is opened, and you handle each differently. Call `initializePushNotifications()` first; see [Push notifications](/[platform]/frontend/push-notifications/customer-profiles/).
+
+- **Foreground**: your application is running, active, and visible.
+- **Background**: your application is still running but is not active or visible. The user is typically on the home screen or in another application.
+- **Terminated**: your application is not running at all, even in the background.
+
+## Respond to a notification being received
+
+| App state | Handle with |
+| ------------------------ | ------------------------------------ |
+| Foreground | `onNotificationReceivedInForeground` |
+| Background or terminated | `onNotificationReceivedInBackground` |
+
+### Received in the foreground
+
+Notifications received while your application is in the foreground are not displayed by the system. Use their content to update your application instead, for example to refresh a list.
+
+```ts
+import { onNotificationReceivedInForeground } from 'aws-amplify/push-notifications/customer-profiles';
+
+const listener = onNotificationReceivedInForeground((notification) => {
+ // Update your application with the notification's content.
+});
+```
+
+### Received in the background or while terminated
+
+```ts
+import { onNotificationReceivedInBackground } from 'aws-amplify/push-notifications/customer-profiles';
+
+const listener = onNotificationReceivedInBackground(async (notification) => {
+ // Process the notification, for example by fetching fresh data.
+});
+```
+
+To handle notifications received while your application is terminated, register this listener at your application's root entry point, before you register your application component. React Native runs it through [Headless JS](https://reactnative.dev/docs/headless-js-android), so your application does not mount when the listener runs:
+
+```ts title="index.js"
+import { AppRegistry } from 'react-native';
+import { Amplify } from 'aws-amplify';
+import {
+ initializePushNotifications,
+ onNotificationReceivedInBackground
+} from 'aws-amplify/push-notifications/customer-profiles';
+import outputs from './amplify_outputs.json';
+
+import App from './App';
+import { name as appName } from './app.json';
+
+Amplify.configure(outputs);
+initializePushNotifications();
+
+onNotificationReceivedInBackground(async (notification) => {
+ // Process the notification, for example by fetching fresh data.
+});
+
+AppRegistry.registerComponent(appName, () => App);
+```
+
+Registering the listener at this entry point means you do not need to remove it.
+
+Every listener returned by these APIs can be removed by calling `remove` on it:
+
+```ts
+listener.remove();
+```
+
+## Respond to a notification being opened
+
+| App state | Handle with |
+| ------------------------ | ----------------------- |
+| Foreground or background | `onNotificationOpened` |
+| Terminated | `getLaunchNotification` |
+
+### Opened from the foreground or background
+
+Add an `onNotificationOpened` listener to respond when a user taps a notification while your application is running:
+
+```ts
+import { onNotificationOpened } from 'aws-amplify/push-notifications/customer-profiles';
+
+const listener = onNotificationOpened((notification) => {
+ // Take action based on the notification the user tapped, such as following a deep link.
+});
+```
+
+### Launched from a terminated state
+
+When a notification launches your application from a terminated state, call `getLaunchNotification` to retrieve it:
+
+```ts
+import { getLaunchNotification } from 'aws-amplify/push-notifications/customer-profiles';
+
+const launchNotification = await getLaunchNotification();
+
+// Take action based on the notification, such as following a deep link.
+```
+
+`getLaunchNotification` consumes the launch notification. It resolves to `null` if:
+
+- You call it more than once, so only the first call after launch resolves to the notification.
+- Another notification was opened while your application was already running.
+- Your application was brought to the foreground by some other means, such as the user tapping the app icon.
diff --git a/src/pages/[platform]/frontend/push-notifications/customer-profiles/manage-device-token/index.mdx b/src/pages/[platform]/frontend/push-notifications/customer-profiles/manage-device-token/index.mdx
new file mode 100644
index 00000000000..4450e316c82
--- /dev/null
+++ b/src/pages/[platform]/frontend/push-notifications/customer-profiles/manage-device-token/index.mdx
@@ -0,0 +1,52 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Manage the device token',
+ description:
+ 'Listen for the push notification token the native platform issues to the device.',
+ platforms: ['react-native']
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ return {
+ props: {
+ platform: context.params.platform,
+ meta
+ }
+ };
+}
+
+The native platform issues a token that uniquely identifies the device for push delivery. Use `onTokenReceived` to listen for that token. Call `initializePushNotifications()` first; see [Push notifications](/[platform]/frontend/push-notifications/customer-profiles/).
+
+## Listen for the token
+
+Add an `onTokenReceived` listener to be notified when a token is issued:
+
+```ts
+import { onTokenReceived } from 'aws-amplify/push-notifications/customer-profiles';
+
+const listener = onTokenReceived((token) => {
+ // Use the token for your own purposes, such as logging or diagnostics.
+});
+```
+
+A token is delivered to your listener:
+
+- On every app launch, including the first install, once the user has granted notification permission.
+- Whenever the token changes, which can happen if the native platform invalidates the previous token.
+
+Remove the listener when you no longer need it by calling `remove` on the value `onTokenReceived` returns:
+
+```ts
+listener.remove();
+```
+
+## Relationship to device registration
+
+You do not need to call `registerDevice` yourself in most applications. The library already listens for this token internally: once `initializePushNotifications` has been called, it registers the device automatically the first time a token arrives, and re-registers it when a user signs in, moving the registration from the guest identity to the authenticated one. See [Register a device](/[platform]/frontend/push-notifications/customer-profiles/register-device/) and [Guest and authenticated users](/[platform]/frontend/push-notifications/customer-profiles/guest-and-authenticated-users/).
+
+Add your own `onTokenReceived` listener only when you need the token value for something beyond registration, such as sending it to a system outside Amplify.
diff --git a/src/pages/[platform]/frontend/push-notifications/customer-profiles/register-device/index.mdx b/src/pages/[platform]/frontend/push-notifications/customer-profiles/register-device/index.mdx
new file mode 100644
index 00000000000..24a00328e23
--- /dev/null
+++ b/src/pages/[platform]/frontend/push-notifications/customer-profiles/register-device/index.mdx
@@ -0,0 +1,120 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Register a device',
+ description:
+ 'Register a push device with Amazon Connect Customer Profiles so that Amazon Connect journeys can deliver notifications to it.',
+ platforms: ['react-native']
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ return {
+ props: {
+ platform: context.params.platform,
+ meta
+ }
+ };
+}
+
+A device must be registered before an Amazon Connect journey can deliver a notification to it. Registering stores the device's push token against the current user's profile identity, so your backend knows where to send messages.
+
+## Registration happens automatically
+
+In most applications you do not call `registerDevice` yourself. After you call `initializePushNotifications`, the library listens for the push token that the native platform issues and registers the device the first time a token arrives.
+
+```ts title="index.js"
+import { Amplify } from 'aws-amplify';
+import { initializePushNotifications } from 'aws-amplify/push-notifications/customer-profiles';
+import outputs from './amplify_outputs.json';
+
+Amplify.configure(outputs);
+initializePushNotifications();
+```
+
+The library also re-registers the device when a user signs in, which moves the existing registration from the guest identity to the authenticated one. See [Guest and authenticated users](/[platform]/frontend/push-notifications/customer-profiles/guest-and-authenticated-users/).
+
+A token is issued only after the user grants notification permission, so request permission during your onboarding flow:
+
+```ts
+import {
+ getPermissionStatus,
+ requestPermissions
+} from 'aws-amplify/push-notifications/customer-profiles';
+
+const status = await getPermissionStatus();
+
+if (status === 'shouldRequest') {
+ await requestPermissions();
+} else if (status === 'shouldExplainThenRequest') {
+ // Explain why your application sends notifications, then request permission.
+ await requestPermissions();
+}
+```
+
+`getPermissionStatus` resolves to `'granted'`, `'denied'`, `'shouldRequest'`, or `'shouldExplainThenRequest'`. `requestPermissions` resolves to a boolean indicating whether permission was granted.
+
+## Register a device manually
+
+Call `registerDevice` with a push token when you need to control registration yourself, for example to register only after a user opts in to notifications in your own settings screen. Obtain the token with `onTokenReceived`:
+
+```ts
+import {
+ onTokenReceived,
+ registerDevice
+} from 'aws-amplify/push-notifications/customer-profiles';
+
+const listener = onTokenReceived(async (token) => {
+ await registerDevice({ token });
+});
+```
+
+The `token` is the only value you provide. The library resolves the remaining device fields for you:
+
+| Field | Source |
+| --- | --- |
+| `deviceId` | A stable identifier the library generates and persists per installation. |
+| `platform` | Derived from the operating system. |
+| `channelType` | Derived from the operating system, which selects the APNs or FCM channel. |
+
+Registration is an idempotent operation keyed on `deviceId`, so calling `registerDevice` again with a new token updates the existing record rather than creating a second one.
+
+## The device belongs to the current identity
+
+The registered device is stored against the `principalId` that your backend derives from the signed request, so a device always belongs to the identity that registered it. Your application never sends a user identifier, and one user cannot register a device against another user's profile.
+
+Because the identity comes from Amazon Cognito identity pool credentials, registration works for guest users as well as signed-in users.
+
+## When to call registerDevice
+
+Call `registerDevice` in these situations:
+
+- After a user opts in to notifications, when you are managing registration manually rather than relying on automatic registration.
+- After receiving a replacement token from `onTokenReceived`, if you are not relying on the automatic listener.
+
+You do not need to call `registerDevice` on every application start, or after a user signs in. The registration persists, and sign-in re-registration is handled for you.
+
+## Handle errors
+
+`registerDevice` rejects in the following cases:
+
+- **Push notifications are not initialized.** Call `initializePushNotifications` before registering a device.
+- **No token is available.** Pass a token, or wait until `onTokenReceived` has fired.
+- **The request failed.** The endpoint returned a non-success status, or the request could not complete.
+
+```ts
+import { registerDevice } from 'aws-amplify/push-notifications/customer-profiles';
+
+try {
+ await registerDevice({ token });
+} catch (error) {
+ console.error('Failed to register device', error);
+}
+```
+
+## Next steps
+
+- [Remove a device](/[platform]/frontend/push-notifications/customer-profiles/remove-device/): de-register a device before signing a user out.
diff --git a/src/pages/[platform]/frontend/push-notifications/customer-profiles/remove-device/index.mdx b/src/pages/[platform]/frontend/push-notifications/customer-profiles/remove-device/index.mdx
new file mode 100644
index 00000000000..e389dd7e2d9
--- /dev/null
+++ b/src/pages/[platform]/frontend/push-notifications/customer-profiles/remove-device/index.mdx
@@ -0,0 +1,87 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Remove a device',
+ description:
+ 'De-register a push device from Amazon Connect Customer Profiles, and why removal must happen before signing a user out.',
+ platforms: ['react-native']
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ return {
+ props: {
+ platform: context.params.platform,
+ meta
+ }
+ };
+}
+
+Use `removeDevice` to de-register the current device so that Amazon Connect journeys stop delivering notifications to it.
+
+```ts
+import { removeDevice } from 'aws-amplify/push-notifications/customer-profiles';
+
+await removeDevice();
+```
+
+`removeDevice` takes no arguments. The library resolves the device identifier for you, and your backend permits removal only of a device that the calling identity owns.
+
+
+
+**Call `removeDevice` before `signOut`.** De-registration is signed with the current user's credentials, and your backend removes a device only when the calling identity owns it. After `signOut` completes those credentials are gone and the caller signs as a new guest identity, so a removal attempted at that point cannot de-register the signed-in user's device. Always await `removeDevice` first:
+
+```ts
+import { signOut } from 'aws-amplify/auth';
+import { removeDevice } from 'aws-amplify/push-notifications/customer-profiles';
+
+await removeDevice();
+await signOut();
+```
+
+Reversing the order leaves the device registered against the signed-out user's profile, and a later journey can deliver their notifications to a device they no longer use.
+
+
+
+## Sign a user out safely
+
+Handle a failed removal deliberately rather than letting it block sign-out. The following pattern signs the user out even when de-registration fails, which avoids trapping a user in a signed-in state because of a network error:
+
+```ts
+import { signOut } from 'aws-amplify/auth';
+import { removeDevice } from 'aws-amplify/push-notifications/customer-profiles';
+
+const handleSignOut = async () => {
+ try {
+ await removeDevice();
+ } catch (error) {
+ console.error('Failed to remove device before sign out', error);
+ }
+
+ await signOut();
+};
+```
+
+## When to call removeDevice
+
+Call `removeDevice` in these situations:
+
+- Before signing a user out, as described above.
+- When a user turns off notifications in your application's settings.
+- Before deleting a user's account.
+
+You do not need to call `removeDevice` when the push token is replaced. Registration is keyed on a device identifier that is stable for the installation, so registering with a new token updates the existing record. See [Register a device](/[platform]/frontend/push-notifications/customer-profiles/register-device/).
+
+## Register the device again
+
+The device identifier persists after removal, so a user who opts back in can be registered again with `registerDevice`. Removing a device does not reset the identifier or require your application to store one.
+
+## Handle errors
+
+`removeDevice` rejects in the following cases:
+
+- **Push notifications are not initialized.** Call `initializePushNotifications` before removing a device.
+- **The request failed.** The endpoint returned a non-success status, or the request could not complete.
diff --git a/src/pages/[platform]/frontend/push-notifications/customer-profiles/request-permissions/index.mdx b/src/pages/[platform]/frontend/push-notifications/customer-profiles/request-permissions/index.mdx
new file mode 100644
index 00000000000..5ce3bc6ab72
--- /dev/null
+++ b/src/pages/[platform]/frontend/push-notifications/customer-profiles/request-permissions/index.mdx
@@ -0,0 +1,88 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+
+export const meta = {
+ title: 'Request permissions',
+ description:
+ 'Check and request permission to display push notifications on the device.',
+ platforms: ['react-native']
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ return {
+ props: {
+ platform: context.params.platform,
+ meta
+ }
+ };
+}
+
+Before a device can receive push notifications, the user must grant permission. Use `getPermissionStatus` and `requestPermissions` to check and request that permission. Call `initializePushNotifications()` first; see [Push notifications](/[platform]/frontend/push-notifications/customer-profiles/).
+
+## Check the current permission status
+
+Use `getPermissionStatus` to determine whether you need to request permission, and how:
+
+```ts
+import { getPermissionStatus } from 'aws-amplify/push-notifications/customer-profiles';
+
+const status = await getPermissionStatus();
+```
+
+`getPermissionStatus` resolves to one of the following values:
+
+| Status | Meaning |
+| --- | --- |
+| `granted` | Permission has already been granted. No further action is needed. |
+| `denied` | The user has denied permission. Requesting again will not show a system prompt; direct the user to their device settings instead. |
+| `shouldRequest` | Permission has not been requested yet, so you can request it directly. |
+| `shouldExplainThenRequest` | Explain why your application sends notifications before requesting permission. |
+
+## Request permission
+
+Use `requestPermissions` to show the system permission prompt:
+
+```ts
+import { requestPermissions } from 'aws-amplify/push-notifications/customer-profiles';
+
+const granted = await requestPermissions();
+```
+
+`requestPermissions` resolves to a boolean indicating whether permission was granted. By default, it requests all supported permissions. Pass an object to request a subset:
+
+```ts
+import { requestPermissions } from 'aws-amplify/push-notifications/customer-profiles';
+
+await requestPermissions({
+ alert: true,
+ badge: true,
+ sound: false
+});
+```
+
+`alert`, `badge`, and `sound` are each optional booleans. Android ignores these individual flags and requests a single combined permission.
+
+## Combine both APIs into a request flow
+
+Check the status before requesting, and use it to decide whether to show your own explanation first:
+
+```ts
+import {
+ getPermissionStatus,
+ requestPermissions
+} from 'aws-amplify/push-notifications/customer-profiles';
+
+const status = await getPermissionStatus();
+
+if (status === 'shouldRequest') {
+ await requestPermissions();
+} else if (status === 'shouldExplainThenRequest') {
+ // Explain why your application sends notifications, then request permission.
+ await requestPermissions();
+}
+```
+
+A token is issued to the device only after the user grants permission, so a device is not registered for push notifications until this flow completes. See [Manage the device token](/[platform]/frontend/push-notifications/customer-profiles/manage-device-token/).