From 2faf352daa173d6d6f2a9807efbf547b0a461640 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:32:46 -0400 Subject: [PATCH 1/5] feat: add export or view page as MD --- .gitignore | 1 + next-env.d.ts | 3 +- package.json | 2 +- public/ai/llms-full.txt | 71043 ++++++++++++++++ public/ai/llms.txt | 596 + src/components/Layout/Layout.tsx | 15 +- src/components/MarkdownMenu/MarkdownMenu.tsx | 164 + .../__tests__/MarkdownMenu.test.tsx | 149 + src/components/MarkdownMenu/index.ts | 1 + src/styles/markdown-menu.scss | 55 + src/styles/styles.scss | 1 + tasks/__tests__/generate-llms-txt.test.ts | 273 + tasks/generate-llms-txt.mjs | 383 + tasks/postBuildTasks.mjs | 2 + 14 files changed, 72685 insertions(+), 3 deletions(-) create mode 100644 public/ai/llms-full.txt create mode 100644 public/ai/llms.txt create mode 100644 src/components/MarkdownMenu/MarkdownMenu.tsx create mode 100644 src/components/MarkdownMenu/__tests__/MarkdownMenu.test.tsx create mode 100644 src/components/MarkdownMenu/index.ts create mode 100644 src/styles/markdown-menu.scss create mode 100644 tasks/__tests__/generate-llms-txt.test.ts create mode 100644 tasks/generate-llms-txt.mjs diff --git a/.gitignore b/.gitignore index bc614fc0ec5..8c4c8088eab 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,7 @@ public/**/**/nextImageExportOptimizer/ public/next-image-export-optimizer-hashes.json src/directory/directory.json src/directory/flatDirectory.json +public/ai/pages/ src/references/raw-references.json .yarn/* diff --git a/next-env.d.ts b/next-env.d.ts index a4a7b3f5cfa..ddb76465e52 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +import "./client/www/next-build/dev/types/routes.d.ts"; // NOTE: This file should not be edited -// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/package.json b/package.json index 5fbdba2acd3..83a257a949f 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "refresh": "yarn clean && yarn", "test:unit": "jest --projects jest.unit.config.js --coverage", "test:e2e": "jest --projects e2e/jest.e2e.config.js", - "dev": "yarn prebuild && next dev --webpack", + "dev": "yarn prebuild && node tasks/generate-llms-txt.mjs && next dev --webpack", "spellcheck": "cspell \"src/**/*.mdx\" --no-progress", "spellcheck-diff": "git diff --name-only --cached | awk \"/src.*\\.mdx/{print}\" | npx cspell --no-must-find-files --file-list stdin", "build": "yarn prebuild && yarn next-build && yarn postbuild", diff --git a/public/ai/llms-full.txt b/public/ai/llms-full.txt new file mode 100644 index 00000000000..c99b3f2ba85 --- /dev/null +++ b/public/ai/llms-full.txt @@ -0,0 +1,71043 @@ +# AWS Amplify Gen 2 Documentation (Full) + +> Complete documentation export for AI coding assistants. +> Generated: 2026-03-10 + +## Table of Contents + +- [How Amplify works](#how-amplify-works) +- [Concepts](#how-amplify-works-concepts) +- [FAQ](#how-amplify-works-faq) +- [Get started](#start) +- [Quickstart](#start-quickstart) +- [Next.js Pages Router](#start-quickstart-nextjs-pages-router) +- [Next.js App Router](#start-quickstart-nextjs-app-router-client-components) +- [Configure AWS for local development](#start-account-setup) +- [Manual installation](#start-manual-installation) +- [Connect to AWS resources](#start-connect-to-aws-resources) +- [Kotlin Coroutines support](#start-kotlin-coroutines) +- [Gen 2 for Gen 1 customers](#start-migrate-to-gen2) +- [Platform setup](#start-platform-setup) +- [Build with AI assistants](#start-mcp-server) +- [Set up AWS MCP Server](#start-mcp-server-set-up-mcp) +- [Guided workflows](#start-mcp-server-amplify-workflows) +- [Build & connect backend](#build-a-backend) +- [Authentication](#build-a-backend-auth) +- [Set up Amplify Auth](#build-a-backend-auth-set-up-auth) +- [Concepts](#build-a-backend-auth-concepts) +- [Usernames](#build-a-backend-auth-concepts-usernames) +- [Email](#build-a-backend-auth-concepts-email) +- [Phone](#build-a-backend-auth-concepts-phone) +- [Passwordless](#build-a-backend-auth-concepts-passwordless) +- [User attributes](#build-a-backend-auth-concepts-user-attributes) +- [User groups](#build-a-backend-auth-concepts-user-groups) +- [Multi-factor authentication](#build-a-backend-auth-concepts-multi-factor-authentication) +- [External identity providers](#build-a-backend-auth-concepts-external-identity-providers) +- [Guest access](#build-a-backend-auth-concepts-guest-access) +- [Tokens and credentials](#build-a-backend-auth-concepts-tokens-and-credentials) +- [Connect your frontend](#build-a-backend-auth-connect-your-frontend) +- [Using the Authenticator](#build-a-backend-auth-connect-your-frontend-using-the-authenticator) +- [Sign-up](#build-a-backend-auth-connect-your-frontend-sign-up) +- [Sign-in](#build-a-backend-auth-connect-your-frontend-sign-in) +- [Switching authentication flows](#build-a-backend-auth-connect-your-frontend-switching-authentication-flows) +- [Sign-out](#build-a-backend-auth-connect-your-frontend-sign-out) +- [Manage user sessions](#build-a-backend-auth-connect-your-frontend-manage-user-sessions) +- [Manage user attributes](#build-a-backend-auth-connect-your-frontend-manage-user-attributes) +- [Listen to auth events](#build-a-backend-auth-connect-your-frontend-listen-to-auth-events) +- [Delete user account](#build-a-backend-auth-connect-your-frontend-delete-user-account) +- [Multi-step sign-in](#build-a-backend-auth-connect-your-frontend-multi-step-sign-in) +- [Manage users](#build-a-backend-auth-manage-users) +- [With admin actions](#build-a-backend-auth-manage-users-with-admin-actions) +- [Manage passwords](#build-a-backend-auth-manage-users-manage-passwords) +- [Manage WebAuthn credentials](#build-a-backend-auth-manage-users-manage-webauthn-credentials) +- [Manage devices](#build-a-backend-auth-manage-users-manage-devices) +- [Manage users with Amplify console](#build-a-backend-auth-manage-users-with-amplify-console) +- [Customize auth lifecycle](#build-a-backend-auth-customize-auth-lifecycle) +- [Custom auth flows](#build-a-backend-auth-customize-auth-lifecycle-custom-auth-flows) +- [Email customization](#build-a-backend-auth-customize-auth-lifecycle-email-customization) +- [Triggers](#build-a-backend-auth-customize-auth-lifecycle-triggers) +- [Enable sign-in with web UI](#build-a-backend-auth-sign-in-with-web-ui) +- [Uninstalling the app](#build-a-backend-auth-app-uninstall) +- [Data usage policy information](#build-a-backend-auth-data-usage-policy) +- [Examples](#build-a-backend-auth-examples) +- [Microsoft Entra ID (SAML)](#build-a-backend-auth-examples-microsoft-entra-id-saml) +- [Grant access to auth resources](#build-a-backend-auth-grant-access-to-auth-resources) +- [Modify Amplify-generated Cognito resources with CDK](#build-a-backend-auth-modify-resources-with-cdk) +- [Moving to production](#build-a-backend-auth-moving-to-production) +- [Advanced workflows](#build-a-backend-auth-advanced-workflows) +- [Use existing Cognito resources](#build-a-backend-auth-use-existing-cognito-resources) +- [Use AWS SDK](#build-a-backend-auth-use-aws-sdk) +- [API References](#build-a-backend-auth-reference) +- [Data](#build-a-backend-data) +- [Set up Amplify Data](#build-a-backend-data-set-up-data) +- [Connect your app code to API](#build-a-backend-data-connect-to-API) +- [Create, update, and delete application data](#build-a-backend-data-mutate-data) +- [Read application data](#build-a-backend-data-query-data) +- [Subscribe to real-time events](#build-a-backend-data-subscribe-data) +- [Customize your data model](#build-a-backend-data-data-modeling) +- [Add fields to data model](#build-a-backend-data-data-modeling-add-fields) +- [Modeling relationships](#build-a-backend-data-data-modeling-relationships) +- [Customize data model identifiers](#build-a-backend-data-data-modeling-identifiers) +- [Customize secondary indexes](#build-a-backend-data-data-modeling-secondary-index) +- [Disable Operations](#build-a-backend-data-data-modeling-disable-operations) +- [Customize your auth rules](#build-a-backend-data-customize-authz) +- [Public data access](#build-a-backend-data-customize-authz-public-data-access) +- [Per-user/per-owner data access](#build-a-backend-data-customize-authz-per-user-per-owner-data-access) +- [Multi-user data access](#build-a-backend-data-customize-authz-multi-user-data-access) +- [Signed-in user data access](#build-a-backend-data-customize-authz-signed-in-user-data-access) +- [User group-based data access](#build-a-backend-data-customize-authz-user-group-based-data-access) +- [Custom data access using Lambda functions](#build-a-backend-data-customize-authz-custom-data-access-patterns) +- [Use OpenID Connect as an authorization provider](#build-a-backend-data-customize-authz-using-oidc-authorization-provider) +- [Configure custom identity and group claims](#build-a-backend-data-customize-authz-configure-custom-identity-and-group-claim) +- [Grant Lambda function access to API and Data](#build-a-backend-data-customize-authz-grant-lambda-function-access-to-api) +- [Add custom queries and mutations](#build-a-backend-data-custom-business-logic) +- [Connect to Amazon OpenSearch for search and aggregate queries](#build-a-backend-data-custom-business-logic-search-and-aggregate-queries) +- [Connect to Amazon EventBridge to send and receive events](#build-a-backend-data-custom-business-logic-connect-eventbridge-datasource) +- [Connect to Amazon Polly for Text-To-Speech APIs](#build-a-backend-data-custom-business-logic-connect-amazon-polly) +- [Connect to Amazon Bedrock for generative AI use cases](#build-a-backend-data-custom-business-logic-connect-bedrock) +- [Connect to Amazon Rekognition for Image Analysis APIs](#build-a-backend-data-custom-business-logic-connect-amazon-rekognition) +- [Connect to Amazon Translate for language translation APIs](#build-a-backend-data-custom-business-logic-connect-amazon-translate) +- [Connect to an external HTTP endpoint](#build-a-backend-data-custom-business-logic-connect-http-datasource) +- [Batch DynamoDB Operations](#build-a-backend-data-custom-business-logic-batch-ddb-operations) +- [Working with files/attachments](#build-a-backend-data-working-with-files) +- [Add custom real-time subscriptions](#build-a-backend-data-custom-subscription) +- [Connect to existing data sources](#build-a-backend-data-connect-to-existing-data-sources) +- [Connect your app to existing MySQL and PostgreSQL database](#build-a-backend-data-connect-to-existing-data-sources-connect-postgres-mysql-database) +- [Connect to external Amazon DynamoDB data sources](#build-a-backend-data-connect-to-existing-data-sources-connect-external-ddb-table) +- [Connect to data from Server-side Runtimes](#build-a-backend-data-connect-from-server-runtime) +- [Next.js server runtime](#build-a-backend-data-connect-from-server-runtime-nextjs-server-runtime) +- [Nuxt.js server runtime](#build-a-backend-data-connect-from-server-runtime-nuxtjs-server-runtime) +- [Optimistic UI](#build-a-backend-data-optimistic-ui) +- [Connect to AWS AppSync Events](#build-a-backend-data-connect-event-api) +- [Modify Amplify-generated AWS resources](#build-a-backend-data-override-resources) +- [Manage Data with Amplify console](#build-a-backend-data-manage-with-amplify-console) +- [AWS AppSync Apollo Extensions](#build-a-backend-data-aws-appsync-apollo-extensions) +- [Enable logging](#build-a-backend-data-enable-logging) +- [Field-level validation](#build-a-backend-data-field-level-validation) +- [API References](#build-a-backend-data-reference) +- [Storage](#build-a-backend-storage) +- [Set up Storage](#build-a-backend-storage-set-up-storage) +- [Customize authorization rules](#build-a-backend-storage-authorization) +- [Upload files](#build-a-backend-storage-upload-files) +- [Download files](#build-a-backend-storage-download-files) +- [List file properties](#build-a-backend-storage-list-files) +- [Remove files](#build-a-backend-storage-remove-files) +- [Copy files](#build-a-backend-storage-copy-files) +- [Listen to storage events](#build-a-backend-storage-lambda-triggers) +- [Extend S3 resources](#build-a-backend-storage-extend-s3-resources) +- [Use AWS SDK for S3 APIs](#build-a-backend-storage-use-aws-sdk) +- [Use Amplify Storage with any S3 bucket](#build-a-backend-storage-use-with-custom-s3) +- [Data usage policy](#build-a-backend-storage-data-usage) +- [Manage files with Amplify console](#build-a-backend-storage-manage-with-amplify-console) +- [API References](#build-a-backend-storage-reference) +- [Functions](#build-a-backend-functions) +- [Set up a Function](#build-a-backend-functions-set-up-function) +- [Environment variables and secrets](#build-a-backend-functions-environment-variables-and-secrets) +- [Configure Functions](#build-a-backend-functions-configure-functions) +- [Configure client library](#build-a-backend-functions-configure-client-library) +- [Scheduling Functions](#build-a-backend-functions-scheduling-functions) +- [Streaming logs](#build-a-backend-functions-streaming-logs) +- [Lambda Layers](#build-a-backend-functions-add-lambda-layers) +- [Grant access to other resources](#build-a-backend-functions-grant-access-to-other-resources) +- [Examples](#build-a-backend-functions-examples) +- [Email domain filtering](#build-a-backend-functions-examples-email-domain-filtering) +- [Add user to group](#build-a-backend-functions-examples-add-user-to-group) +- [Create a user profile record](#build-a-backend-functions-examples-create-user-profile-record) +- [Override ID token claims](#build-a-backend-functions-examples-override-token) +- [User attribute validation](#build-a-backend-functions-examples-user-attribute-validation) +- [Custom message](#build-a-backend-functions-examples-custom-message) +- [Google reCAPTCHA challenge](#build-a-backend-functions-examples-google-recaptcha-challenge) +- [Amazon Kinesis Data Streams](#build-a-backend-functions-examples-kinesis-stream) +- [DynamoDB Streams](#build-a-backend-functions-examples-dynamo-db-stream) +- [S3 Upload confirmation](#build-a-backend-functions-examples-s3-upload-confirmation) +- [Custom Auth Challenge](#build-a-backend-functions-examples-custom-auth-flows) +- [Modify Amplify-generated Lambda resources with CDK](#build-a-backend-functions-modify-resources-with-cdk) +- [Custom functions](#build-a-backend-functions-custom-functions) +- [Server-Side Rendering](#build-a-backend-server-side-rendering) +- [Next.js App Router (Server Components)](#build-a-backend-server-side-rendering-nextjs-app-router-server-components) +- [Use Amplify categories APIs from Nuxt 3](#build-a-backend-server-side-rendering-nuxt) +- [Add any AWS service](#build-a-backend-add-aws-services) +- [Analytics](#build-a-backend-add-aws-services-analytics) +- [Set up Amplify Analytics](#build-a-backend-add-aws-services-analytics-set-up-analytics) +- [Record events](#build-a-backend-add-aws-services-analytics-record-events) +- [Identify user](#build-a-backend-add-aws-services-analytics-identify-user) +- [Automatically track sessions](#build-a-backend-add-aws-services-analytics-auto-track-sessions) +- [Enable and disable analytics](#build-a-backend-add-aws-services-analytics-enable-disable) +- [Streaming analytics data](#build-a-backend-add-aws-services-analytics-streaming-data) +- [Storing analytics data](#build-a-backend-add-aws-services-analytics-storing-data) +- [Personalized recommendations](#build-a-backend-add-aws-services-analytics-personalize-recommendations) +- [Use existing AWS resources](#build-a-backend-add-aws-services-analytics-existing-resources) +- [Use AWS SDK](#build-a-backend-add-aws-services-analytics-sdk) +- [Data usage policy information](#build-a-backend-add-aws-services-analytics-data-usage-policy) +- [Uninstalling the app](#build-a-backend-add-aws-services-analytics-app-uninstall) +- [API References](#build-a-backend-add-aws-services-analytics-reference) +- [Geo](#build-a-backend-add-aws-services-geo) +- [Set up Amplify Geo](#build-a-backend-add-aws-services-geo-set-up-geo) +- [Work with maps](#build-a-backend-add-aws-services-geo-maps) +- [Configure location search](#build-a-backend-add-aws-services-geo-configure-location-search) +- [Work with location search](#build-a-backend-add-aws-services-geo-location-search) +- [Configure a geofence collection](#build-a-backend-add-aws-services-geo-configure-geofencing) +- [Work with geofences](#build-a-backend-add-aws-services-geo-geofences) +- [Use existing Amazon Location resources](#build-a-backend-add-aws-services-geo-existing-resources) +- [Migrate from Google Maps](#build-a-backend-add-aws-services-geo-google-migration) +- [Use Amazon Location Service SDK](#build-a-backend-add-aws-services-geo-amazon-location-sdk) +- [In-App Messaging](#build-a-backend-add-aws-services-in-app-messaging) +- [Set up in-app messaging](#build-a-backend-add-aws-services-in-app-messaging-set-up-in-app-messaging) +- [Integrate your application](#build-a-backend-add-aws-services-in-app-messaging-integrate-application) +- [Sync messages](#build-a-backend-add-aws-services-in-app-messaging-sync-messages) +- [Display messages](#build-a-backend-add-aws-services-in-app-messaging-display-messages) +- [Clear messages](#build-a-backend-add-aws-services-in-app-messaging-clear-messages) +- [Identify a user](#build-a-backend-add-aws-services-in-app-messaging-identify-user) +- [Respond to interaction events](#build-a-backend-add-aws-services-in-app-messaging-respond-interaction-events) +- [Resolve conflicts](#build-a-backend-add-aws-services-in-app-messaging-resolve-conflicts) +- [Create an in-app messaging campaign on AWS Console](#build-a-backend-add-aws-services-in-app-messaging-create-campaign) +- [API References](#build-a-backend-add-aws-services-in-app-messaging-reference) +- [API (REST)](#build-a-backend-add-aws-services-rest-api) +- [Set up Amplify REST API](#build-a-backend-add-aws-services-rest-api-set-up-rest-api) +- [Set up Amplify HTTP API](#build-a-backend-add-aws-services-rest-api-set-up-http-api) +- [Define authorization rules](#build-a-backend-add-aws-services-rest-api-customize-authz) +- [Fetch data](#build-a-backend-add-aws-services-rest-api-fetch-data) +- [Post data](#build-a-backend-add-aws-services-rest-api-post-data) +- [Update data](#build-a-backend-add-aws-services-rest-api-update-data) +- [Delete data](#build-a-backend-add-aws-services-rest-api-delete-data) +- [Test the REST API](#build-a-backend-add-aws-services-rest-api-test-api) +- [Use existing AWS resources](#build-a-backend-add-aws-services-rest-api-existing-resources) +- [API References](#build-a-backend-add-aws-services-rest-api-reference) +- [AI/ML Predictions](#build-a-backend-add-aws-services-predictions) +- [Set up Predictions](#build-a-backend-add-aws-services-predictions-set-up-predictions) +- [Text to speech](#build-a-backend-add-aws-services-predictions-text-to-speech) +- [Transcribe audio to text](#build-a-backend-add-aws-services-predictions-transcribe-audio) +- [Translate language](#build-a-backend-add-aws-services-predictions-translate) +- [Identify text](#build-a-backend-add-aws-services-predictions-identify-text) +- [Identify entities from images](#build-a-backend-add-aws-services-predictions-identify-entity) +- [Label objects in an image](#build-a-backend-add-aws-services-predictions-label-image) +- [Interpret sentiment](#build-a-backend-add-aws-services-predictions-interpret-sentiment) +- [Logging](#build-a-backend-add-aws-services-logging) +- [Set up Logging](#build-a-backend-add-aws-services-logging-set-up-logging) +- [Send logs](#build-a-backend-add-aws-services-logging-send-logs) +- [Change log levels](#build-a-backend-add-aws-services-logging-change-log-levels) +- [Flush logs](#build-a-backend-add-aws-services-logging-flush-logs) +- [Enable and disable logging](#build-a-backend-add-aws-services-logging-enable-disable) +- [Configure user allow list](#build-a-backend-add-aws-services-logging-configure-user) +- [View logs](#build-a-backend-add-aws-services-logging-view-logs) +- [Remotely change log levels](#build-a-backend-add-aws-services-logging-remote-configuration) +- [Change local storage](#build-a-backend-add-aws-services-logging-change-local-storage) +- [Listen to log events](#build-a-backend-add-aws-services-logging-hub-events) +- [Use AWS SDK for logging](#build-a-backend-add-aws-services-logging-sdk) +- [Interactions](#build-a-backend-add-aws-services-interactions) +- [Set up Amplify Interactions](#build-a-backend-add-aws-services-interactions-set-up-interactions) +- [Interact with bots](#build-a-backend-add-aws-services-interactions-chatbot) +- [PubSub](#build-a-backend-add-aws-services-pubsub) +- [Publish](#build-a-backend-add-aws-services-pubsub-publish) +- [Set up Amplify PubSub](#build-a-backend-add-aws-services-pubsub-set-up-pubsub) +- [Subscribe and unsubscribe](#build-a-backend-add-aws-services-pubsub-subscribe) +- [Deletion protection and Backup resources](#build-a-backend-add-aws-services-deletion-backup-resources) +- [Custom resources](#build-a-backend-add-aws-services-custom-resources) +- [Tagging resources](#build-a-backend-add-aws-services-tagging-resources) +- [Overriding resources](#build-a-backend-add-aws-services-overriding-resources) +- [Use Amazon Q Developer with Amplify](#build-a-backend-q-developer) +- [Troubleshooting](#build-a-backend-troubleshooting) +- [Troubleshoot configuration errors](#build-a-backend-troubleshooting-library-not-configured) +- [Troubleshoot CDKToolkit stack issues](#build-a-backend-troubleshooting-cdktoolkit-stack) +- [Troubleshoot "Cannot find module $amplify/env/"](#build-a-backend-troubleshooting-cannot-find-module-amplify-env) +- [Troubleshoot circular dependency issues](#build-a-backend-troubleshooting-circular-dependency) +- [AI kit](#ai) +- [Set up AI](#ai-set-up-ai) +- [Concepts](#ai-concepts) +- [Architecture](#ai-concepts-architecture) +- [Models](#ai-concepts-models) +- [Prompting](#ai-concepts-prompting) +- [Inference Configuration](#ai-concepts-inference-configuration) +- [Streaming](#ai-concepts-streaming) +- [Tools](#ai-concepts-tools) +- [Conversation](#ai-conversation) +- [](#ai-conversation-ai-conversation) +- [Connect your frontend](#ai-conversation-connect-your-frontend) +- [Conversation History](#ai-conversation-history) +- [Tools](#ai-conversation-tools) +- [Context](#ai-conversation-context) +- [Response components](#ai-conversation-response-components) +- [Knowledge Base](#ai-conversation-knowledge-base) +- [Generation](#ai-generation) +- [Data Extraction](#ai-generation-data-extraction) +- [Build UI](#build-ui) +- [Connected forms](#build-ui-formbuilder) +- [Customize form inputs](#build-ui-formbuilder-customize) +- [Configure special inputs](#build-ui-formbuilder-special-inputs) +- [Validate form data](#build-ui-formbuilder-validations) +- [Manage form lifecycle](#build-ui-formbuilder-lifecycle) +- [Figma-to-React](#build-ui-figma-to-code) +- [Deployment](#deploy-and-host) +- [Frontend hosting](#deploy-and-host-hosting) +- [Cloud sandbox environments](#deploy-and-host-sandbox-environments) +- [Use cloud sandbox in dev environment](#deploy-and-host-sandbox-environments-setup) +- [Sandbox features](#deploy-and-host-sandbox-environments-features) +- [Sandbox Seed](#deploy-and-host-sandbox-environments-seed) +- [Fullstack workflows](#deploy-and-host-fullstack-branching) +- [Fullstack branch deployments](#deploy-and-host-fullstack-branching-branch-deployments) +- [Secrets and environment vars](#deploy-and-host-fullstack-branching-secrets-and-vars) +- [Share resources across branches](#deploy-and-host-fullstack-branching-share-resources) +- [Separate frontend and backend teams](#deploy-and-host-fullstack-branching-mono-and-multi-repos) +- [Monorepo setup](#deploy-and-host-fullstack-branching-monorepos) +- [Fullstack previews](#deploy-and-host-fullstack-branching-pr-previews) +- [Custom pipelines](#deploy-and-host-fullstack-branching-custom-pipelines) +- [Cross-account deployments](#deploy-and-host-fullstack-branching-cross-account-deployments) +- [Reference](#reference) +- [Project structure](#reference-project-structure) +- [About amplify_outputs.json](#reference-amplify_outputs) +- [CDK constructs](#reference-cdk-constructs) +- [CLI commands](#reference-cli-commands) +- [IAM policy](#reference-iam-policy) +- [Telemetry](#reference-telemetry) +- [API Documentation](#reference-flutter-api) + +--- + +--- +title: "How Amplify works" +section: "how-amplify-works" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-02T03:55:55.000Z" +url: "https://docs.amplify.aws/react/how-amplify-works/" +--- + + + +--- + +--- +title: "Concepts" +section: "how-amplify-works" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-03-21T01:04:47.000Z" +url: "https://docs.amplify.aws/react/how-amplify-works/concepts/" +--- + +AWS Amplify Gen 2 uses a TypeScript-based, code-first developer experience (DX) for defining backends. The Gen 2 DX offers a unified Amplify developer experience with hosting, backend, and UI-building capabilities and a code-first approach. Amplify empowers frontend developers to deploy cloud infrastructure by simply expressing their app’s data model, business logic, authentication, and authorization rules completely in TypeScript. Amplify automatically configures the correct cloud resources and removes the requirement to stitch together underlying AWS services. + +## Capabilities + +You can use Amplify for end-to-end fullstack development. + +### Build fullstack apps with TypeScript + +With the Gen 2 DX, you can provision backend infrastructure by authoring TypeScript. In the following diagram, the box at the bottom (outlined in pink), highlights the main difference in how you provision infrastructure compared to Gen 1. In Gen 1, you would use Studio's console or the CLI to provision infrastructure; in Gen 2, you author TypeScript code in files following a file-based convention (such as `amplify/auth/resource.ts` or `amplify/auth/data.ts`). With TypeScript types and classes for resources, you gain strict typing and IntelliSense in Visual Studio Code to prevent errors. A breaking change in the backend code immediately reflects as a type error in the co-located frontend code. The file-based convention follows the "convention over configuration" paradigmβ€”you know exactly where to look for resource definitions when you group them by type in separate files. + +![How Amplify capabilities can be used together or independently.](/images/gen2/how-amplify-works/amplify-flow.png) + +### Faster local development + +Per-developer cloud sandbox environments are optimized for faster iterations. Each developer on a team gets an isolated cloud development environment against which they can test their changes. These cloud sandbox environments are meant for local development only, but they deploy high-fidelity AWS backends while you build. Depending on the workflow, iterative updates are now deployed up to 8X faster than Gen 1 deployments. In the diagram below, four developers are able to work on fullstack features independently without disrupting each other's environments. + +![How cloud sandbox environments work.](/images/gen2/how-amplify-works/sandbox.png) + +### Fullstack Git-based environments + +All shared environments (such as `production`, `staging`, `gamma`) map 1:1 to Git branches in your repository. New features can be tested in ephemeral environments with pull request previews (or feature branches) before they are merged into production. Unlike the Gen 1 experience, which requires users to configure a number of steps in the CLI or Console to set up a fullstack environment, the Gen 2 experience is zero-config. Because of our code-first approach, the Git repository is always the source of truth for the state of the fullstack appβ€”all backend resources are defined as code for reproducibility and portability across branches. This, along with central management of environment variables and secrets, simplifies the promotion workflow from lower to upper environments. + +![How fullstack deployments work.](/images/gen2/how-amplify-works/fullstack.png) + +### Unified management console + +All branches can be managed in the new Amplify console. The Amplify Gen 2 console provides a single place for you to manage your builds, hosting settings (such as custom domains), deployed resources (such as data browser or user management), and environment variables and secrets. Even though you can access deployed resources directly in other AWS service consoles, the Amplify console will offer a first-party experience for the categories almost every app needsβ€”data, auth, storage, and functions. For example, with Data, Amplify offers an API playground and a data manager (coming soon) with relationship building, seed data generation, and file upload capabilities. + +## Build an app + +### Data + +The `@aws-amplify/backend` library offers a TypeScript-first `Data` library for setting up fully typed real-time APIs (powered by AWS AppSync GraphQL APIs) and NoSQL databases (powered by Amazon DynamoDB tables). After you generate an Amplify backend, you will have an `amplify/data/resource.ts` file, which will contain your app's data schema. The `defineData` function turns the schema into a fully functioning data backend with all the boilerplate handled automatically. + + + The schema-based approach is an evolution of the Amplify GraphQL API in Gen 1. + It offers several benefits, including dot completion, IntelliSense, and type + validation. + + +A data model for a chat app may look something like this, for example: + +```ts +const schema = a.schema({ + Chat: a.model({ + name: a.string(), + message: a.hasMany('Message', 'chatId'), + }), + Message: a.model({ + text: a.string(), + chat: a.belongsTo('Chat', 'chatId'), + chatId: a.id() + }), +}).authorization((allow) => allow.owner()); +``` + +On your app's frontend, you can use the `generateClient` function, which provides a typed client instance, making it easy to integrate CRUD (create, read, update, delete) operations for your models in your application code. + + + Gen 2 automatically generates your types without the explicit codegen step + that was part of Gen 1. + + +```ts +// generate your data client using the Schema from your backend +const client = generateClient(); + +// list all messages +const { data } = await client.models.Message.list(); + +// create a new message +const { errors, data: newMessage } = await client.models.Message.create({ + text: 'My message text' +}); +``` + +### Auth + +Auth works similarly to data. You can configure the authentication settings you want for your app in `amplify/auth/resource.ts`. If you want to change the verification email's subject line, you can change out the default generated code with the following: + +```ts title="amplify/auth/resource.ts" +export const auth = defineAuth({ + loginWith: { + email: { + verificationEmailSubject: 'Welcome πŸ‘‹ Verify your email!' + } + } +}); +``` + +You can customize your authentication flow with customized sign-in and registration flows, multi-factor authentication (MFA), and third-party social providers. Amplify deploys an Amazon Cognito instance in your AWS account when you add auth to your app. + +Then, you could use the Amplify `Authenticator` component or the client libraries to add user flows. + +```ts +import { withAuthenticator } from '@aws-amplify/ui-react'; + +function App({ signOut, user }) { + return ( + <> +

Hello {user.username}

+ + + ); +} + +export default withAuthenticator(App); +``` + +### UI building + +Amplify makes it easy to quickly build web app user interfaces using the UI component library, Figma-to-code generation, and CRUD form-generation capabilities. [Learn more.](https://ui.docs.amplify.aws/react/components) + +![Screenshot showing Figma to Code](/images/gen2/how-amplify-works/ui.jpg) + +## Connecting to AWS beyond Amplify + +### Add any AWS resource + +Gen 2 is layered on top of [AWS Cloud Development Kit (CDK)](https://docs.aws.amazon.com/cdk/api/v2/)β€”the Data and Auth capabilities in `@aws-amplify/backend` wrap L3 AWS CDK constructs. As a result, extending the resources generated by Amplify does not require any special configuration. The following example adds Amazon Location Services by adding a file: `amplify/custom/maps/resource.ts`. + +```ts +import { CfnOutput, Stack, StackProps } from 'aws-cdk-lib'; +import * as locations from 'aws-cdk-lib/aws-location'; +import { Construct } from 'constructs'; + +export class LocationMapStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + // Create the map resource + const map = new locations.CfnMap(this, 'LocationMap', { + configuration: { + style: 'VectorEsriStreets' // map style + }, + description: 'My Location Map', + mapName: 'MyMap' + }); + + new CfnOutput(this, 'mapArn', { + value: map.attrArn, + exportName: 'mapArn' + }); + } +} +``` + +This is then included in the `amplify/backend.ts` file so it gets deployed as part of your Amplify app. + +```ts +import { Backend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +import { data } from './data/resource'; +import { LocationMapStack } from './locationMapStack/resource'; + +const backend = new Backend({ + auth, + data +}); + +new LocationMapStack( + backend.getStack('LocationMapStack'), + 'myLocationResource', + {} +); +``` + +### Connect to existing resources + +Amplify is designed to work with your existing AWS resources and configurations. For example, you can use Amplify's pre-built authentication UI components with an existing Amazon Cognito user pool you created and configured separately. Or you can display images and files from an existing Amazon S3 bucket in your app's user interface by integrating with Amplify Storage. + +Amplify's libraries provide an interface to leverage your existing AWS services so that you can adopt Amplify's capabilities incrementally into your current workflows, without disrupting your existing backend infrastructure. + +## Next steps + +Now that you have a conceptual understanding of AWS Amplify's capabilities, complete the [quickstart tutorial](/[platform]/start/quickstart/) to put it into action in an app. + +--- + +--- +title: "FAQ" +section: "how-amplify-works" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-06-05T19:53:40.000Z" +url: "https://docs.amplify.aws/react/how-amplify-works/faq/" +--- + +**Is there a way to upgrade an existing Amplify project from Gen 1 to Gen 2?** + +We are still actively developing migration tooling to aid in transitioning your project from Gen 1 to Gen 2. Until then, we recommend you continue working with your Gen 1 Amplify project. We’ve put together a Gen 1 vs. Gen 2 feature support matrix [here](https://docs.amplify.aws/react/start/migrate-to-gen2/). We remain committed to supporting both Gen 1 and Gen 2 for the foreseeable future. For new projects, we recommend adopting Gen 2 to take advantage of its enhanced capabilities. Meanwhile, customers on Gen 1 will continue to receive support for high-priority bugs and essential security updates. + +**If I have a Gen 1 app, can I use Gen 2 in it?** + +Amplify Gen 1 and Gen 2 follow different architectural and tooling paradigms, which was necessary to address common customer feedback from Gen 1. You will need to use our upcoming migration tooling to move from a Gen 1 to Gen 2 app. You cannot use Amplify Gen 1 (Studio/CLI) in the same app as Gen 2. + +**Should I use Amplify Gen 1 or Gen 2 in new apps?** + +If you're building a new app, we recommend you use Amplify Gen 2. + +**Does Amplify Gen 2 support DataStore?** + +Amplify Gen 2 supports GraphQL APIs without DataStore. We will release migration support for moving DataStore Gen 1 apps to Gen 2. + +**What programming languages does Amplify Gen 2 support?** + +Amplify Gen 2 supports a wide range of programming languages for client-side development. This includes dedicated client-side libraries for JavaScript, TypeScript, Dart, Java, Kotlin, and Swift. For backend development, Amplify Gen 2 uses TypeScript. + +**In Gen 1, Amplify offered a set of use case categories for building applications (for example, Authentication, Analytics, API, DataStore, Geo, and Predictions). Are those same categories available in Gen 2?** + +Amplify Gen 2 offers built-in support for Auth, Data, Storage, and Functions. Other use cases can be implemented in Amplify Gen 2 as well using AWS Cloud Development Kit (AWS CDK) constructs which there is documentation for under the respective category name. + +**Can I use Gen 2 with a JavaScript frontend that doesn't use TypeScript?** + +Yes. Amplify Gen 2's TypeScript backend definition works with JavaScript frontends. In addition, you still get an end-to-end typed data fetching experience even with a pure JavaScript frontend. See [Generate a Data client](/react/build-a-backend/data/connect-to-API/#generate-the-amplify-data-client) for the recommended JavaScript client code. + +**What if we want to add a feature like AI/ML or Amazon Location Service to our application in Gen 2?** + +Because Amplify builds on the AWS Cloud Development Kit (AWS CDK), any AWS services supported by the CDK can be added to your app using [custom resources](/[platform]/build-a-backend/add-aws-services/custom-resources/) and L2/L1 AWS CDK constructs. + +**What happens once my application grows too big and I want to do more configuration with my application (add more features, other AWS services, etc.)?** + +You can stay with Amplify no matter how big your application grows. Amplify is layered on top of the AWS CDK and AWS CloudFormation. These provide a standardized way of interacting with AWS, so you can add any [AWS service supported by CDK to your Amplify app](/[platform]/build-a-backend/add-aws-services/custom-resources/). You can also override [Amplify-generated configuration of your resources](/[platform]/build-a-backend/add-aws-services/overriding-resources/) using the CDK. You can use any deployment pipeline you choose if you want more control over your CI. + +**How much does it cost to operate Amplify Gen2?** + +You can read all about Amplify's pricing on our [pricing page](https://aws.amazon.com/amplify/pricing/). + +**Which Amplify JavaScript version is compatible with Gen 2?** + +Amplify JavaScript version 6.2.0 and above is compatible with backends created by Amplify Gen 2. + +--- + +--- +title: "Get started" +section: "start" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-03-19T21:13:52.000Z" +url: "https://docs.amplify.aws/react/start/" +--- + +AWS Amplify is a collection of cloud services and libraries for fullstack application development. Amplify provides frontend libraries, UI components, backend building, and frontend hosting for building fullstack cloud apps. This tutorial will teach you how to use Amplify's new code-first developer experience to build a fullstack application with data, authentication, and frontend hosting which are all deployed to AWS. If you're completely new to AWS Amplify, you may want to read more about [how it works and the concepts behind the second generation of AWS Amplify](/[platform]/how-amplify-works/concepts/), which this tutorial will use. + +--- + +--- +title: "Quickstart" +section: "start" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-11-13T16:29:27.000Z" +url: "https://docs.amplify.aws/react/start/quickstart/" +--- + + +πŸ‘‹ Welcome to AWS Amplify! In this quickstart guide, you will: + +1. Deploy a Vanilla JavaScript app with Vite +2. Build and connect to a database with real-time data updates +3. Configure authentication and authorization rules + +## Create project + +Create a new Vanilla JavaScript app with vite using the following commands, create the directory (`amplify-js-app`) and files for the app. + +```bash +npm create vite@latest +βœ” Project name: amplify-js-app +βœ” Select a framework: β€Ί Vanilla +βœ” Select a variant: β€Ί TypeScript +``` + +Initialize npm and install dependencies and dev dependencies. +```bash +cd amplify-js-app +npm install +npm run dev +``` + +This runs a development server and allows you to see the output generated by the build. You can see the running app by navigating to [http://localhost:5173](http://localhost:5173). + +Add the following to the `index.html` file: + +```html title="index.html" + + + + + + Todo App + + +
+

My todos

+ +
    +
    + Try creating a new todo. +
    + + Review next step of this tutorial. + +
    +
    + + + +``` + +Add the following to `style.css` file: + +```css title="style.css" +body { + margin: 0; + background: linear-gradient(180deg, rgb(117, 81, 194), rgb(255, 255, 255)); + display: flex; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + height: 100vh; + width: 100vw; + justify-content: center; + align-items: center; +} + +main { + display: flex; + flex-direction: column; + align-items: stretch; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; + color: white; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +ul { + padding-inline-start: 0; + margin-block-start: 0; + margin-block-end: 0; + list-style-type: none; + display: flex; + flex-direction: column; + margin: 8px 0; + border: 1px solid black; + gap: 1px; + background-color: black; + border-radius: 8px; + overflow: auto; +} + +li { + background-color: white; + padding: 8px; +} + +li:hover { + background: #dadbf9; +} + +a { + font-weight: 800; + text-decoration: none; +} +``` + +In `main.js` remove the boilerplate code and leave it empty. Then refresh the browser to see the changes. + +## Create Backend + +The easiest way to get started with AWS Amplify is through npm with `create-amplify` command. You can run it from your base project directory. + +```bash title="Terminal" showLineNumbers={false} +npm create amplify@latest +? Where should we create your project? (.) # press enter +``` + +Running this command will scaffold Amplify backend files in your current project with the following files added: + +```text +β”œβ”€β”€ amplify/ +β”‚ β”œβ”€β”€ auth/ +β”‚ β”‚ └── resource.ts +β”‚ β”œβ”€β”€ data/ +β”‚ β”‚ └── resource.ts +β”‚ β”œβ”€β”€ backend.ts +β”‚ └── package.json +β”œβ”€β”€ node_modules/ +β”œβ”€β”€ index.html +β”œβ”€β”€ style.css +β”œβ”€β”€ .gitignore +β”œβ”€β”€ package-lock.json +β”œβ”€β”€ package.json +└── tsconfig.json +``` + +### Set up local AWS credentials + +To make backend updates, we are going to require AWS credentials to deploy backend updates from our local machine. + +**Skip ahead to step 8**, if you already have an AWS profile with credentials on your local machine, and your AWS profile has the `AmplifyBackendDeployFullAccess` permission policy. + +Otherwise, **[set up local AWS credentials](/[platform]/start/account-setup/)** that grant Amplify permissions to deploy backend updates from your local machine. + +### Deploy cloud sandbox + +To deploy your backend use Amplify's per-developer cloud sandbox. This feature provides a separate backend environment for every developer on a team, ideal for local development and testing. To run your application with a sandbox environment, you can run the following command: + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + +Once the sandbox environment is deployed, it will create a GraphQL API, database, and auth service. All the deployed resources will be available in the `amplify_outputs.json`. + +## Connect frontend to backend + +The initial scaffolding already has a pre-configured data backend defined in the `amplify/data/resource.ts` file. The default example will create a Todo model with `content` field. Update your main.js file to create new to-do items. + +```typescript title="src/main.ts" +import { generateClient } from "aws-amplify/data"; +import type { Schema } from "../amplify/data/resource"; +import './style.css'; +import { Amplify } from 'aws-amplify'; +import outputs from '../amplify_outputs.json'; + +Amplify.configure(outputs); + +const client = generateClient(); + +document.addEventListener("DOMContentLoaded", function () { + const todos: Array = []; + const todoList = document.getElementById("todoList") as HTMLUListElement; + const addTodoButton = document.getElementById("addTodo") as HTMLButtonElement; + + addTodoButton.addEventListener("click", createTodo); + + function updateUI() { + todoList.innerHTML = ''; + todos.forEach(todo => { + const li = document.createElement('li'); + li.textContent = todo.content ?? ''; + todoList.appendChild(li); + }); + } + + function createTodo() { + console.log('createTodo'); + const content = window.prompt("Todo content"); + if (content) { + client.models.Todo.create({ content }).then(response => { + if (response.data && !response.errors) { + todos.push(response.data); + updateUI(); + } else { + console.error('Error creating todo:', response.errors); + alert('Failed to create todo.'); + } + }).catch(error => { + console.error('Network or other error:', error); + alert('Failed to create todo due to a network or other error.'); + }); + } + } + + client.models.Todo.observeQuery().subscribe({ + next: (data) => { + todos.splice(0, todos.length, ...data.items); + updateUI(); + } + }); +}); +``` + + + +πŸ‘‹ Welcome to AWS Amplify! In this quickstart guide, you will: + +1. Deploy a React and Vite app +2. Build and connect to a database with real-time data updates +3. Configure authentication and authorization rules + +## Deploy a fullstack app to AWS + +We've created a starter "To-do" application to help get started faster. First, you will create a repository in your GitHub account using our starter React template. + +### 1. Create the repository + +Use our starter template to create a repository in your GitHub account. This template scaffolds `create-vite-app` with Amplify backend capabilities. + + + +Create repository from template + + +Use the form in GitHub to finalize your repo's creation. + +### 2. Deploy the starter app + +Now that the repository has been created, deploy it with Amplify. + + + +Deploy to AWS + + +Select **GitHub**. After you give Amplify access to your GitHub account via the popup window, pick the repository and `main` branch to deploy. Make no other changes and click through the flow to **Save and deploy**. + +### 3. View deployed app + + + +Let's take a tour of the project structure in this starter repository by opening it on GitHub. The starter application has pre-written code for a to-do list app. It gives you a real-time database with a feed of all to-do list items and the ability to add new items. + +```text +β”œβ”€β”€ amplify/ # Folder containing your Amplify backend configuration +β”‚ β”œβ”€β”€ auth/ # Definition for your auth backend +β”‚ β”‚ └── resource.tsx +β”‚ β”œβ”€β”€ data/ # Definition for your data backend +β”‚ β”‚ └── resource.ts +| β”œβ”€β”€ backend.ts +β”‚ └── tsconfig.json +β”œβ”€β”€ src/ # React UI code +β”‚ β”œβ”€β”€ App.tsx # UI code to sync todos in real-time +β”‚ β”œβ”€β”€ index.css # Styling for your app +β”‚ └── main.tsx # Entrypoint of the Amplify client library +β”œβ”€β”€ package.json +└── tsconfig.json +``` + + + When the build completes, visit the newly deployed branch by selecting "Visit deployed URL". Since the build deployed an API, database, and authentication backend, you will be able to create new to-do items. + +In the Amplify console, click into the deployment branch (in this case **main**) > select **Data** in the left-hand menu > **Data manager** to see the data entered in your database. + +## Make frontend updates + +Let's learn how to enhance the app functionality by creating a delete flow for to-do list items. + +### 4. Set up local environment + +Now let's set up our local development environment to add features to the frontend. Click on your deployed branch and you will land on the **Deployments** page which shows you your build history and a list of deployed backend resources. + +At the bottom of the page you will see a tab for **Deployed backend resources**. Click on the tab and then click the **Download amplify_outputs.json file** button. + +![](/images/gen2/getting-started/react/amplify-outputs-download.png) + +Clone the repository locally. + +```bash title="Terminal" showLineNumbers={false} +git clone https://github.com//amplify-vite-react-template.git +cd amplify-vite-react-template && npm install +``` + +Now move the `amplify_outputs.json` file you downloaded above to the root of your project. + +```text +β”œβ”€β”€ amplify +β”œβ”€β”€ src +β”œβ”€β”€ amplify_outputs.json <== backend outputs file +β”œβ”€β”€ package.json +└── tsconfig.json +``` + + +The **amplify_outputs.json** file contains backend endpoint information, publicly-viewable API keys, authentication flow information, and more. The Amplify client library uses this outputs file to connect to your Amplify Backend. You can review how the outputs file is imported within the `main.tsx` file and then passed into the `Amplify.configure(...)` function of the Amplify client library. + + +### 5. Implement delete functionality + +Go to the **src/App.tsx** file and add in a new `deleteTodo` functionality and pass function into the `
  • ` element's `onClick` handler. + +```tsx title="src/App.tsx" +function App() { + // ... + // highlight-start + function deleteTodo(id: string) { + client.models.Todo.delete({ id }) + } + // highlight-end + + return ( +
    +

    My todos

    + +
      + {todos.map(todo =>
    • deleteTodo(todo.id)} + key={todo.id}> + {todo.content} +
    • )} +
    +
    + πŸ₯³ App successfully hosted. Try creating a new todo. +
    + Review next step of this tutorial. +
    +
    + ) +} +``` + +Try out the deletion functionality now by starting the local dev server: + +```bash title="Terminal" showLineNumbers={false} +npm run dev +``` + +This should start a local dev server at http://localhost:5173. + +### 6. Implement login UI + +The starter application already has a pre-configured auth backend defined in the **amplify/auth/resource.ts** file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. + +The fastest way to get your login experience up and running is to use our Authenticator UI component. In your **src/main.tsx** file, import the Authenticator UI component and wrap your `` component. + +```tsx title="src/main.tsx" +import React from 'react'; +import ReactDOM from 'react-dom/client'; +// highlight-next-line +import { Authenticator } from '@aws-amplify/ui-react'; +import { Amplify } from 'aws-amplify'; +import App from './App.tsx'; +import outputs from '../amplify_outputs.json'; +import './index.css'; +// highlight-next-line +import '@aws-amplify/ui-react/styles.css'; + +Amplify.configure(outputs); + +ReactDOM.createRoot(document.getElementById('root')!).render( + // highlight-start + + + + + + // highlight-end +); +``` + +The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. + +In your `src/App.tsx` file, add a button to enable users to sign out of the application. Import the [`useAuthenticator`](https://ui.docs.amplify.aws/react/connected-components/authenticator/advanced#access-auth-state) hook from the Amplify UI library to hook into the state of the Authenticator. + +```tsx title="src/App.tsx" +import type { Schema } from '../amplify/data/resource'; +// highlight-next-line +import { useAuthenticator } from '@aws-amplify/ui-react'; +import { useEffect, useState } from 'react'; +import { generateClient } from 'aws-amplify/data'; + +const client = generateClient(); + +function App() { + // highlight-next-line + const { signOut } = useAuthenticator(); + + // ... + + return ( +
    + {/* ... */} + // highlight-next-line + +
    + ); +} + +export default App; +``` + +Try out your application in your localhost environment again. You should be presented with a login experience now. + +To get these changes to the cloud, commit them to git and push the changes upstream. + +```bash title="Terminal" showLineNumbers={false} +git commit -am "added authenticator" +git push +``` + +Amplify automatically deploys the latest version of your app based on your git commits. In just a few minutes, when the application rebuilds, the hosted app will be updated to support the deletion functionality. + +## Make backend updates + +Let's update our backend to implement per-user authorization rules, allowing each user to only access their own to-dos. + +### 7. Set up local AWS credentials + +To make backend updates, we are going to require AWS credentials to deploy backend updates from our local machine. + +**Skip ahead to step 8**, if you already have an AWS profile with credentials on your local machine, and your AWS profile has the `AmplifyBackendDeployFullAccess` permission policy. + +Otherwise, **[set up local AWS credentials](/[platform]/start/account-setup/)** that grant Amplify permissions to deploy backend updates from your local machine. + +### 8. Deploy cloud sandbox + +To update your backend without affecting the production branch, use Amplify's cloud sandbox. This feature provides a separate backend environment for each developer on a team, ideal for local development and testing. + +To start your cloud sandbox, run the following command in a **new Terminal window**: + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + +Once the cloud sandbox has been fully deployed (~5 min), you'll see the `amplify_outputs.json` file updated with connection information to a new isolated authentication and data backend. + +> **Info:** The `npx ampx sandbox` command should run concurrently to your `npm run dev`. You can think of the cloud sandbox as the "localhost-equivalent for your app backend". + +### 9. Implement per-user authorization + +The to-do items in the starter are currently shared across all users, but, in most cases, you want data to be isolated on a per-user basis. + +To isolate the data on a per-user basis, you can use an "owner-based authorization rule". Let's apply the owner-based authorization rule to your to-do items: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a.model({ + content: a.string(), + // highlight-next-line + }).authorization(allow => [allow.owner()]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + // This tells the data client in your app (generateClient()) + // to sign API requests with the user authentication token. + // highlight-next-line + defaultAuthorizationMode: 'userPool', + }, +}); +``` + +In the application client code, let's also render the username to distinguish different users once they're logged in. Go to your **src/App.tsx** file and render the `user` property from the `useAuthenticator` hook. + +```tsx title="src/App.tsx" +// ... imports + +function App() { + // highlight-next-line + const { user, signOut } = useAuthenticator(); + + // ... + + return ( +
    + // highlight-next-line +

    {user?.signInDetails?.loginId}'s todos

    + {/* ... */} +
    + ) +} +``` + +Now, let's go back to your local application and test out the user isolation of the to-do items. + +You will need to sign up new users again because now you're working with the cloud sandbox instead of your production backend. + +To get these changes to the cloud, commit them to git and push the changes upstream. + +```bash title="Terminal" showLineNumbers={false} +git commit -am "added per-user data isolation" +git push +``` + +Once your build completes in the Amplify Console, the `main` backend will update to support the changes made within the cloud sandbox. The data in the cloud sandbox is fully isolated and won't pollute your production database. + + + +πŸ‘‹ Welcome to AWS Amplify! In this quickstart guide, you will: + +1. Deploy a Next.js app +2. Build and connect to a database with real-time data updates +3. Configure authentication and authorization rules + +We have two Quickstart guides you can follow: + + + + +πŸ‘‹ Welcome to AWS Amplify! In this quickstart guide, you will: + +1. Deploy a Vue.js app +2. Build and connect to a database with real-time data updates +3. Configure authentication and authorization rules + +## Deploy a fullstack app to AWS + +We've created a starter "To-do" application to help get started faster. First, you will create a repository in your GitHub account using our starter Vue template. + +### 1. Create the repository + +Use our starter template to create a repository in your GitHub account. This template scaffolds `create-vite-app` with Amplify backend capabilities. + + + +Create repository from template + + +Use the form in GitHub to finalize your repo's creation. + +### 2. Deploy the starter app + +Now that the repository has been created, deploy it with Amplify. + + + +Deploy to AWS + + +Select **GitHub**. After you give Amplify access to your GitHub account via the popup window, pick the repository and `main` branch to deploy. Make no other changes and click through the flow to **Save and deploy**. + +### 3. View deployed app + + + +Let's take a tour of the project structure in this starter repository by opening it on GitHub. The starter application has pre-written code for a to-do list app. It gives you a real-time database with a feed of all to-do list items and the ability to add new items. + +```text +β”œβ”€β”€ amplify/ # Folder containing your Amplify backend configuration +β”‚ β”œβ”€β”€ auth/ # Definition for your auth backend +β”‚ β”‚ └── resource.tsx +β”‚ β”œβ”€β”€ data/ # Definition for your data backend +β”‚ β”‚ └── resource.ts +| β”œβ”€β”€ backend.ts +β”‚ └── tsconfig.json +β”œβ”€β”€ src/ # Vue code +β”‚ β”œβ”€β”€ assets/ # Styling for your app +β”‚ β”œβ”€β”€ components/ # UI code to sync todos in real-time +β”‚ β”œβ”€β”€ App.vue # UI layout +β”‚ └── main.tsx # Entrypoint of the Amplify client library +β”œβ”€β”€ package.json +└── tsconfig.json +``` + + + When the build completes, visit the newly deployed branch by selecting "Visit deployed URL". Since the build deployed an API, database, and authentication backend, you will be able to create new to-do items. + +In the Amplify console, click into the deployment branch (in this case **main**) > select **Data** in the left-hand menu > **Data manager** to see the data entered in your database. + +## Make frontend updates + +Let's learn how to enhance the app functionality by creating a delete flow for to-do list items. + +### 4. Set up local environment + +Now let's set up our local development environment to add features to the frontend. Click on your deployed branch and you will land on the **Deployments** page which shows you your build history and a list of deployed backend resources. + +At the bottom of the page you will see a tab for **Deployed backend resources**. Click on the tab and then click the **Download amplify_outputs.json file** button. + +![](/images/gen2/getting-started/react/amplify-outputs-download.png) + +Clone the repository locally. + +```bash title="Terminal" showLineNumbers={false} +git clone https://github.com//amplify-vue-template.git +cd amplify-vue-template && npm install +``` + +Now move the `amplify_outputs.json` file you downloaded above to the root of your project. + +```text +β”œβ”€β”€ amplify +β”œβ”€β”€ src +β”œβ”€β”€ amplify_outputs.json <== backend outputs file +β”œβ”€β”€ package.json +└── tsconfig.json +``` + + +The **amplify_outputs.json** file contains backend endpoint information, publicly-viewable API keys, authentication flow information, and more. The Amplify client library uses this outputs file to connect to your Amplify Backend. You can review how the outputs file is imported within the `main.tsx` file and then passed into the `Amplify.configure(...)` function of the Amplify client library. + + +### 5. Implement delete functionality + +Go to the **components/Todos.vue** file and add in a new `deleteTodo` functionality and pass function into the `
  • ` element's `onClick` handler. + +```tsx title="components/Todos.vue" +function App() { + // ... + // highlight-start + function deleteTodo(id: string) { + client.models.Todo.delete({ id }) + } + // highlight-end + + +} +``` + +Try out the deletion functionality now by starting the local dev server: + +```bash title="Terminal" showLineNumbers={false} +npm run dev +``` + +This should start a local dev server at http://localhost:5173. + +### 6. Implement login UI + +The starter application already has a pre-configured auth backend defined in the **amplify/auth/resource.ts** file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. + +The fastest way to get your login experience up and running is to use our Authenticator UI component. + +```terminal showLineNumbers={false} +npm add @aws-amplify/ui-vue +``` +In your **src/App.vue** file, import the Authenticator UI component and wrap your `
    ` template. + +```tsx title="src/App.vue" + + + +``` + +The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. + +Try out your application in your localhost environment again. You should be presented with a login experience now. + +To get these changes to the cloud, commit them to git and push the changes upstream. + +```bash title="Terminal" showLineNumbers={false} +git commit -am "added authenticator" +git push +``` + +Amplify automatically deploys the latest version of your app based on your git commits. In just a few minutes, when the application rebuilds, the hosted app will be updated to support the deletion functionality. + +## Make backend updates + +Let's update our backend to implement per-user authorization rules, allowing each user to only access their own to-dos. + +### 7. Set up local AWS credentials + +To make backend updates, we are going to require AWS credentials to deploy backend updates from our local machine. + +**Skip ahead to step 8**, if you already have an AWS profile with credentials on your local machine, and your AWS profile has the `AmplifyBackendDeployFullAccess` permission policy. + +Otherwise, **[set up local AWS credentials](/[platform]/start/account-setup/)** that grant Amplify permissions to deploy backend updates from your local machine. + +### 8. Deploy cloud sandbox + +To update your backend without affecting the production branch, use Amplify's cloud sandbox. This feature provides a separate backend environment for each developer on a team, ideal for local development and testing. + +To start your cloud sandbox, run the following command in a **new Terminal window**: + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + +Once the cloud sandbox has been fully deployed (~5 min), you'll see the `amplify_outputs.json` file updated with connection information to a new isolated authentication and data backend. + +> **Info:** The `npx ampx sandbox` command should run concurrently to your `npm run dev`. You can think of the cloud sandbox as the "localhost-equivalent for your app backend". + +### 9. Implement per-user authorization + +The to-do items in the starter are currently shared across all users, but, in most cases, you want data to be isolated on a per-user basis. + +To isolate the data on a per-user basis, you can use an "owner-based authorization rule". Let's apply the owner-based authorization rule to your to-do items: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a.model({ + content: a.string(), + // highlight-next-line + }).authorization(allow => [allow.owner()]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + // This tells the data client in your app (generateClient()) + // to sign API requests with the user authentication token. + // highlight-next-line + defaultAuthorizationMode: 'userPool', + }, +}); +``` + +In the application client code, let's also render the username to distinguish different users once they're logged in. + +```tsx title="src/App.vue" + + + +``` + +Now, let's go back to your local application and test out the user isolation of the to-do items. + +You will need to sign up new users again because now you're working with the cloud sandbox instead of your production backend. + +To get these changes to the cloud, commit them to git and push the changes upstream. + +```bash title="Terminal" showLineNumbers={false} +git commit -am "added per-user data isolation" +git push +``` + +Once your build completes in the Amplify Console, the `main` backend will update to support the changes made within the cloud sandbox. The data in the cloud sandbox is fully isolated and won't pollute your production database. + + + +πŸ‘‹ Welcome to AWS Amplify! In this quickstart guide, you will: + +1. Deploy an Angular app +2. Build and connect to a database with real-time data updates +3. Configure authentication and authorization rules + +## Deploy a fullstack app to AWS + +We've created a starter "To-do" application to help get started faster. First, you will create a repository in your GitHub account using our starter Angular template. + +### 1. Create the repository + +Use our starter template to create a repository in your GitHub account. This template scaffolds a starter Angular application with Amplify backend capabilities. + + + +Create repository from template + + +Use the form in GitHub to finalize your repo's creation. + +### 2. Deploy the starter app + +Now that the repository has been created, deploy it with Amplify. + + + +Deploy to AWS + + +Select **GitHub**. After you give Amplify access to your GitHub account via the popup window, pick the repository and `main` branch to deploy. Make no other changes and click through the flow to **Save and deploy**. + +### 3. View deployed app + + + +Let's take a tour of the project structure in this starter repository by opening it on GitHub. The starter application has pre-written code for a to-do list app. It gives you a real-time database with a feed of all to-do list items and the ability to add new items. + +```text +β”œβ”€β”€ amplify/ # Folder containing your Amplify backend configuration +β”‚ β”œβ”€β”€ auth/ # Definition for your auth backend +β”‚ β”‚ └── resource.tsx +β”‚ β”œβ”€β”€ data/ # Definition for your data backend +β”‚ β”‚ └── resource.ts +| β”œβ”€β”€ backend.ts +β”‚ └── tsconfig.json +β”œβ”€β”€ src/app/ # Angular UI code +β”‚ β”œβ”€β”€ todos/ # UI code to sync todos in real-time +β”‚ β”œβ”€β”€ app.component.css # Styling for your app +β”‚ └── app.component.ts # Entrypoint of the Amplify client library +β”œβ”€β”€ package.json +└── tsconfig.json +``` + + + When the build completes, visit the newly deployed branch by selecting "Visit deployed URL". Since the build deployed an API, database, and authentication backend, you will be able to create new to-do items. + +In the Amplify console, click into the deployment branch (in this case **main**) > select **Data** in the left-hand menu > **Data manager** to see the data entered in your database. + +## Make frontend updates + +Let's learn how to enhance the app functionality by creating a delete flow for to-do list items. + +### 4. Set up local environment + +Now let's set up our local development environment to add features to the frontend. Click on your deployed branch and you will land on the **Deployments** page which shows you your build history and a list of deployed backend resources. + +At the bottom of the page you will see a tab for **Deployed backend resources**. Click on the tab and then click the **Download amplify_outputs.json file** button. + +![](/images/gen2/getting-started/react/amplify-outputs-download.png) + +Clone the repository locally. + +```bash title="Terminal" showLineNumbers={false} +git clone https://github.com//amplify-angular-template.git +cd amplify-angular-template && npm install +``` + +Now move the `amplify_outputs.json` file you downloaded above to the root of your project. + +```text +β”œβ”€β”€ amplify +β”œβ”€β”€ src +β”œβ”€β”€ amplify_outputs.json <== backend outputs file +β”œβ”€β”€ package.json +└── tsconfig.json +``` + + +The **amplify_outputs.json** file contains backend endpoint information, publicly-viewable API keys, authentication flow information, and more. The Amplify client library uses this outputs file to connect to your Amplify Backend. You can review how the outputs file is imported within the `app.component.ts` file and then passed into the `Amplify.configure(...)` function of the Amplify client library. + + +### 5. Implement delete functionality + +Go to the **src/app/todos/todos.component.ts** file and add a new `deleteTodo` function. + +```tsx title="src/todos/todos.component.ts" +export class TodosComponent implements OnInit { + // ... + // highlight-start + deleteTodo(id: string) { + client.models.Todo.delete({ id }) + } + // highlight-end +} +``` + +Call the `deleteTodo` function from the UI. + +```html title="src/app/todos/todos.component.html" +... +
      +
    • + {{ todo.content }} +
    • +
    +... +``` + +Try out the deletion functionality now by starting the local dev server: + +```bash title="Terminal" showLineNumbers={false} +npm run start +``` + +This should start a local dev server at http://localhost:4200. + +### 6. Implement login UI + +The starter application already has a pre-configured auth backend defined in the **amplify/auth/resource.ts** file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. + +The fastest way to get your login experience up and running is to use our Authenticator UI component. + +```terminal showLineNumbers={false} +npm add @aws-amplify/ui-angular +``` + +In your **src/app/app.component.ts** file, import the `AmplifyAuthenticatorModule`. + +```ts title="src/app/app.component.ts" +import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { TodosComponent } from './todos/todos.component'; +import { Amplify } from 'aws-amplify'; +import outputs from '../../amplify_outputs.json'; +// highlight-next-line +import { AmplifyAuthenticatorModule, AuthenticatorService } from '@aws-amplify/ui-angular'; + +Amplify.configure(outputs); + +@Component({ + selector: 'app-root', + standalone: true, + templateUrl: './app.component.html', + styleUrl: './app.component.css', + // highlight-next-line + imports: [RouterOutlet, TodosComponent, AmplifyAuthenticatorModule], +}) +export class AppComponent { + title = 'amplify-angular-template'; + // highlight-start + constructor(public authenticator: AuthenticatorService) { + Amplify.configure(outputs); + } + // highlight-end +} +``` +Update the application UI and include styles. + +```html title="src/app/app.component.html" + + + + + + +``` + +```json title="angular.json" +... + "styles": [ + "node_modules/@aws-amplify/ui-angular/theme.css", + "src/styles.css" + ], +... +``` +The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. + +Try out your application in your localhost environment again. You should be presented with a login experience now. + +To get these changes to the cloud, commit them to git and push the changes upstream. + +```bash title="Terminal" showLineNumbers={false} +git commit -am "added authenticator" +git push +``` + +Amplify automatically deploys the latest version of your app based on your git commits. In just a few minutes, when the application rebuilds, the hosted app will be updated to support the deletion functionality. + +## Make backend updates + +Let's update our backend to implement per-user authorization rules, allowing each user to only access their own to-dos. + +### 7. Set up local AWS credentials + +To make backend updates, we are going to require AWS credentials to deploy backend updates from our local machine. + +**Skip ahead to step 8**, if you already have an AWS profile with credentials on your local machine, and your AWS profile has the `AmplifyBackendDeployFullAccess` permission policy. + +Otherwise, **[set up local AWS credentials](/[platform]/start/account-setup/)** that grant Amplify permissions to deploy backend updates from your local machine. + +### 8. Deploy cloud sandbox + +To update your backend without affecting the production branch, use Amplify's cloud sandbox. This feature provides a separate backend environment for each developer on a team, ideal for local development and testing. + +To start your cloud sandbox, run the following command in a **new Terminal window**: + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + +Once the cloud sandbox has been fully deployed (~5 min), you'll see the `amplify_outputs.json` file updated with connection information to a new isolated authentication and data backend. + +> **Info:** The `npx ampx sandbox` command should run concurrently to your `npm run dev`. You can think of the cloud sandbox as the "localhost-equivalent for your app backend". + +### 9. Implement per-user authorization + +The to-do items in the starter are currently shared across all users, but, in most cases, you want data to be isolated on a per-user basis. + +To isolate the data on a per-user basis, you can use an "owner-based authorization rule". Let's apply the owner-based authorization rule to your to-do items: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a.model({ + content: a.string(), + }) + // highlight-next-line + .authorization(allow => [allow.owner()]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + // This tells the data client in your app (generateClient()) + // to sign API requests with the user authentication token. + // highlight-next-line + defaultAuthorizationMode: 'userPool', + }, +}); +``` + +In the application client code, let's also render the username to distinguish different users once they're logged in. + +```html title="src/app/app.component.html" + + +

    Hello {{user?.signInDetails?.loginId}}'s todos

    + + +
    +
    +``` + +Now, let's go back to your local application and test out the user isolation of the to-do items. + +You will need to sign up new users again because now you're working with the cloud sandbox instead of your production backend. + +To get these changes to the cloud, commit them to git and push the changes upstream. + +```bash title="Terminal" showLineNumbers={false} +git commit -am "added per-user data isolation" +git push +``` + +Once your build completes in the Amplify Console, the `main` backend will update to support the changes made within the cloud sandbox. The data in the cloud sandbox is fully isolated and won't pollute your production database. + + + +## Prerequisites + +Before you get started, make sure you have the following installed: + +- [Node.js](https://nodejs.org/) v18.17 or later +- [npm](https://www.npmjs.com/) v9 or later +- [git](https://git-scm.com/) v2.14.1 or later +- You will also need to [create an AWS Account](https://portal.aws.amazon.com/billing/signup). Note that AWS Amplify is part of the [AWS Free Tier](https://aws.amazon.com/amplify/pricing/). +- Configure your AWS account to use with Amplify [instructions](/[platform]/start/account-setup/). +- A stable version of [Flutter](https://docs.flutter.dev/get-started/install). + +> **Info:** You can follow the [official documentation](https://flutter.dev/docs/get-started/install) to install Flutter on your machine and check the [editor documentation](https://docs.flutter.dev/get-started/editor) for setting up your editor. + +Once you have installed Flutter, you can create a new Flutter project using the following command: + +> **Info:** In this Quickstart guide, you will build the application for web. However, if you want to run the application on other platforms, be sure to follow the required setup [guide here](/[platform]/start/platform-setup/). + +```bash title="Terminal" showLineNumbers={false} +flutter create my_amplify_app +``` + +## Create Backend + +The easiest way to get started with AWS Amplify is through npm with `create-amplify` command. You can run it from your base project directory. First, go to the base project directory with the following command: + +```bash title="Terminal" showLineNumbers={false} +cd my_amplify_app +``` + +After that, run the following to create an Amplify project: + +```bash title="Terminal" showLineNumbers={false} +npm create amplify@latest -y +``` + +Running this command will scaffold Amplify backend files in your current project with the following files added: + +```text +β”œβ”€β”€ amplify/ +β”‚ β”œβ”€β”€ auth/ +β”‚ β”‚ └── resource.ts +β”‚ β”œβ”€β”€ data/ +β”‚ β”‚ └── resource.ts +β”‚ β”œβ”€β”€ backend.ts +β”‚ └── package.json +β”œβ”€β”€ node_modules/ +β”œβ”€β”€ .gitignore +β”œβ”€β”€ package-lock.json +β”œβ”€β”€ package.json +└── tsconfig.json +``` + +To deploy your backend use Amplify's per-developer cloud sandbox. This feature provides a separate backend environment for every developer on a team, ideal for local development and testing. To run your application with a sandbox environment, you can run the following command: + + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox + +``` + + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox --outputs-format dart --outputs-out-dir lib +``` + + +## Adding Authentication + +The initial scaffolding already has a pre-configured auth backend defined in the `amplify/auth/resource.ts` file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. + +The fastest way to get your login experience up and running is to use our Authenticator UI component available in the Amplify UI library. + +To use the Authenticator, you need to add the following dependencies to your project: + +```yaml title="pubspec.yaml" +dependencies: + amplify_flutter: ^2.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_authenticator: ^2.0.0 +``` + +You will add: + +- `amplify_flutter` to connect your application with the Amplify resources. +- `amplify_auth_cognito` to connect your application with the Amplify Cognito resources. +- `amplify_authenticator` to use the Amplify UI components. + +After adding the dependencies, you need to run the following command to install the dependencies: + +```bash title="Terminal" showLineNumbers={false} +flutter pub get +``` + +Lastly update your main.dart file to use the Amplify UI components: + +```dart title="main.dart" +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_authenticator/amplify_authenticator.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; + +import 'amplify_outputs.dart'; + +Future main() async { + try { + WidgetsFlutterBinding.ensureInitialized(); + await _configureAmplify(); + runApp(const MyApp()); + } on AmplifyException catch (e) { + runApp(Text("Error configuring Amplify: ${e.message}")); + } +} + +Future _configureAmplify() async { + try { + await Amplify.addPlugin(AmplifyAuthCognito()); + await Amplify.configure(amplifyConfig); + safePrint('Successfully configured'); + } on Exception catch (e) { + safePrint('Error configuring Amplify: $e'); + } +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override + Widget build(BuildContext context) { + return Authenticator( + child: MaterialApp( + builder: Authenticator.builder(), + home: const Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SignOutButton(), + Text('TODO Application'), + ], + ), + ), + ), + ), + ); + } +} +``` + +The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. + +Run your application in your local environment again. You should be presented with a login experience now. + +## Adding Data + +The initial scaffolding already has a pre-configured data backend defined in the `amplify/data/resource.ts` file. The default example will create a Todo model with `content` field. + +Let's modify this to add the following: +- A boolean `isDone` field. +- An authorization rules specifying owners, authenticated via your Auth resource can "create", "read", "update", and "delete" their own records. +- Update the `defaultAuthorizationMode` to sign API requests with the user authentication token. + +```typescript +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + isDone: a.boolean(), + }) + .authorization(allow => [allow.owner()]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "userPool", + }, +}); +``` +Next, let's implement UI to create, list, and delete the to-do items. + +Amplify can automatically generate code for interacting with the backend API. Run the command in the terminal to generate dart model classes from the Data schema under `lib/models`: + +```bash title="Terminal" showLineNumbers={false} +npx ampx generate graphql-client-code --format modelgen --model-target dart --out lib/models +``` + +Once you are done, add the API dependencies to your project. You will add `amplify_api` to connect your application with the Amplify API. + +```yaml title="pubspec.yaml" +dependencies: + amplify_api: ^2.0.0 +``` + +After adding the dependencies, update the `_configureAmplify` method in your `main.dart` file to use the Amplify API: + +```dart title="main.dart" +Future _configureAmplify() async { + try { + await Amplify.addPlugins( + [ + AmplifyAuthCognito(), + AmplifyAPI( + options: APIPluginOptions( + modelProvider: ModelProvider.instance, + ), + ), + ], + ); + await Amplify.configure(amplifyConfig); + safePrint('Successfully configured'); + } on Exception catch (e) { + safePrint('Error configuring Amplify: $e'); + } +} +``` + +Next create a new widget called `TodoScreen` and add the following code to the end of the **main.dart** file: + +```dart title="main.dart" + +class TodoScreen extends StatefulWidget { + const TodoScreen({super.key}); + + @override + State createState() => _TodoScreenState(); +} + +class _TodoScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton.extended( + label: const Text('Add Random Todo'), + onPressed: () async { + final newTodo = Todo( + id: uuid(), + content: "Random Todo ${DateTime.now().toIso8601String()}", + isDone: false, + ); + final request = ModelMutations.create(newTodo); + final response = await Amplify.API.mutate(request: request).response; + if (response.hasErrors) { + safePrint('Creating Todo failed.'); + } else { + safePrint('Creating Todo successful.'); + } + }, + ), + body: const Placeholder(), + ); + } +} +``` + +This will create a random Todo every time a user clicks on the floating action button. You can see the `ModelMutations.create` method is used to create a new Todo. + +And update the `MyApp` widget in your **main.dart** file like the following: + +```dart title="main.dart" +class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override + Widget build(BuildContext context) { + return Authenticator( + child: MaterialApp( + builder: Authenticator.builder(), + home: const SafeArea( + child: Scaffold( + body: Column( + children: [ + SignOutButton(), + Expanded(child: TodoScreen()), + ], + ), + ), + ), + ), + ); + } +} +``` + +Next add a `_todos` list in `_TodoScreenState` to add the results from the API and call the refresh function: + +```dart title="main.dart" +List _todos = []; + +@override +void initState() { + super.initState(); + _refreshTodos(); +} +``` + +and create a new function called `_refreshTodos`: + +```dart title="main.dart" +Future _refreshTodos() async { + try { + final request = ModelQueries.list(Todo.classType); + final response = await Amplify.API.query(request: request).response; + + final todos = response.data?.items; + if (response.hasErrors) { + safePrint('errors: ${response.errors}'); + return; + } + setState(() { + _todos = todos!.whereType().toList(); + }); + } on ApiException catch (e) { + safePrint('Query failed: $e'); + } +} +``` + +and update the `build` function like the following: + +```dart title="main.dart" +@override +Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton.extended( + label: const Text('Add Random Todo'), + onPressed: () async { + final newTodo = Todo( + id: uuid(), + content: "Random Todo ${DateTime.now().toIso8601String()}", + isDone: false, + ); + final request = ModelMutations.create(newTodo); + final response = await Amplify.API.mutate(request: request).response; + if (response.hasErrors) { + safePrint('Creating Todo failed.'); + } else { + safePrint('Creating Todo successful.'); + } + _refreshTodos(); + }, + ), + body: _todos.isEmpty == true + ? const Center( + child: Text( + "The list is empty.\nAdd some items by clicking the floating action button.", + textAlign: TextAlign.center, + ), + ) + : ListView.builder( + itemCount: _todos.length, + itemBuilder: (context, index) { + final todo = _todos[index]; + return Dismissible( + key: UniqueKey(), + confirmDismiss: (direction) async { + return false; + }, + child: CheckboxListTile.adaptive( + value: todo.isDone, + title: Text(todo.content!), + onChanged: (isChecked) async {}, + ), + ); + }, + ), + ); +} +``` + +Now let's add the update and delete functionality. + +For update, add the following code to the `onChanged` method of the `CheckboxListTile.adaptive` widget: + +```dart title="main.dart" +final request = ModelMutations.update( + todo.copyWith(isDone: isChecked!), +); +final response = + await Amplify.API.mutate(request: request).response; +if (response.hasErrors) { + safePrint('Updating Todo failed. ${response.errors}'); +} else { + safePrint('Updating Todo successful.'); + await _refreshTodos(); +} +``` + +This will call the `ModelMutations.update` method to update the Todo with a copied/updated version of the todo item. So now the checkbox will get an update as well. + +For delete functionality, add the following code to the `confirmDismiss` method of the `Dismissible` widget: + +```dart title="main.dart" +if (direction == DismissDirection.endToStart) { + final request = ModelMutations.delete(todo); + final response = + await Amplify.API.mutate(request: request).response; + if (response.hasErrors) { + safePrint('Updating Todo failed. ${response.errors}'); + } else { + safePrint('Updating Todo successful.'); + await _refreshTodos(); + return true; + } +} +return false; +``` + +This will delete the Todo item when the user swipes the item from right to left. Now if you run the application you should see the following flow. + +You can terminate the sandbox environment now to clean up the project. + +### Publishing changes to cloud + +Publishing changes to the cloud requires a remote git repository. Amplify offers fullstack branch deployments that allow you to automatically deploy infrastructure and application code changes from feature branches. To learn more, visit the [fullstack branch deployments guide](/[platform]/deploy-and-host/fullstack-branching/branch-deployments). + + + +## Prerequisites + +Before you get started, make sure you have the following installed: + +- [Node.js](https://nodejs.org/) v18.17 or later +- [npm](https://www.npmjs.com/) v9 or later +- [git](https://git-scm.com/) v2.14.1 or later +- You will also need to [create an AWS Account](https://portal.aws.amazon.com/billing/signup). Note that AWS Amplify is part of the [AWS Free Tier](https://aws.amazon.com/amplify/pricing/). +- Configure your AWS account to use with Amplify [instructions](/[platform]/start/account-setup/). +- You need to have [Xcode and Developer Tooling](https://developer.apple.com/xcode/) installed on your machine. + + +Open Xcode and select **Create New Project...** + +![Shows the Xcode starter video to start project](/images/lib/getting-started/ios/set-up-swift-1.png) + +In the next step select the **App** template under **iOS**. Click on next. + +![Shows the template of apps for iOS](/images/lib/getting-started/ios/set-up-swift-2.png) + +Next steps are: + +- Adding a _Product Name_ (e.g. MyAmplifyApp) +- Select a _Team_ (e.g. None) +- Select a _Organization Identifier_ (e.g. com.example) +- Select **SwiftUI** an _Interface_. +- Press **Next** + +![Shows the project details dialog](/images/lib/getting-started/ios/set-up-swift-3.png) + +Now you should have your project created. + +![Shows the base project for SwiftUI](/images/lib/getting-started/ios/set-up-swift-4.png) + + +## Create Backend + +The easiest way to get started with AWS Amplify is through npm with `create-amplify` command. You can run it from your base project directory. + +```bash title="Terminal" showLineNumbers={false} +cd my_amplify_app +npm create amplify@latest +? Where should we create your project? (.) # press enter +``` + +Running this command will scaffold Amplify backend files in your current project with the following files added: + +```text +β”œβ”€β”€ amplify/ +β”‚ β”œβ”€β”€ auth/ +β”‚ β”‚ └── resource.ts +β”‚ β”œβ”€β”€ data/ +β”‚ β”‚ └── resource.ts +β”‚ β”œβ”€β”€ backend.ts +β”‚ └── package.json +β”œβ”€β”€ node_modules/ +β”œβ”€β”€ .gitignore +β”œβ”€β”€ package-lock.json +β”œβ”€β”€ package.json +└── tsconfig.json +``` + +To deploy your backend use Amplify's per-developer cloud sandbox. This feature provides a separate backend environment for every developer on a team, ideal for local development and testing. To run your application with a sandbox environment, you can run the following command: + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + +Once the sandbox environment is deployed, it will create an `amplify_outputs.json`. However, Xcode won't be able to recognize them. For recognizing the files, you need to drag and drop the generated files to your project. + +## Adding Authentication + +The initial scaffolding already has a pre-configured auth backend defined in the `amplify/auth/resource`.ts file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. + +The fastest way to get your login experience up and running is to use our Authenticator UI component available in the Amplify UI library. + +To use the Authenticator, open your project in Xcode and select **File > Add Packages...** and add the following dependencies: + +![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-5.png) + +- Amplify Library for Swift: Enter its GitHub URL (https://github.com/aws-amplify/amplify-swift), select **Up to Next Major Version** and click **Add Package Dependencies...** and select the following libraries: + + - Amplify + - AWSCognitoAuthPlugin + +![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-6.png) + +- Amplify UI Swift - Authenticator: Enter its GitHub URL (https://github.com/aws-amplify/amplify-ui-swift-authenticator), select **Up to Next Major Version** and click **Add Package Dependencies...** and select the following libraries: + - Authenticator + +![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-7.png) + +Now update the `MyAmplifyAppApp` class with the following code: + +```swift +import Amplify +import Authenticator +import AWSCognitoAuthPlugin +import SwiftUI + +@main +struct MyApp: App { + init() { + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.configure(with: .amplifyOutputs) + } catch { + print("Unable to configure Amplify \(error)") + } + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} +``` + +Update `ContentView` with the following code: +```swift +import Amplify +import Authenticator + +struct ContentView: View { + var body: some View { + Authenticator { state in + VStack { + Button("Sign out") { + Task { + await state.signOut() + } + } + } + } + } +} +``` + +The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. + +Run your application in your local environment again. You should be presented with a login experience now. + +
    + ) +} +``` +
    See the complete amplify/data/resources.ts + +Open the `amplify/data/resource.ts` file in your text editor, and you will see a default data model generated for you. + +```ts showLineNumbers title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a + .model({ + content: a.string() + }) + .authorization(allow => [allow.owner(), allow.publicApiKey().to(['read'])]) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + // API Key is used for allow.publicApiKey() rules + apiKeyAuthorizationMode: { + expiresInDays: 30 + } + } +}); +``` + + The schema generated by Amplify is for a to-do app. A schema is a blueprint + for how our app's data will be organized. Within the schema, we will define + models that will correspond to a database tableβ€”`Todo` in the above code. + Finally, we will define fields, which are attributes that each data instance + will haveβ€”in the generated code, the field is `content`. Each + field will have a type attached to itβ€”in the above examples, we are stating + that the `content` field is a string. +
    + +Try out the deletion functionality now by starting the local dev server: + +```bash title="Terminal" showLineNumbers={false} +npm run dev +``` + +This should start a local dev server at http://localhost:3000. + +### 6. Implement login UI + +The starter application already has a pre-configured auth backend defined in the **amplify/auth/resource.ts** file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. + +The fastest way to get your login experience up and running is to use our Authenticator UI component. In your **pages/_app.tsx** file, import the Authenticator UI component and wrap your `` component. + +```tsx title="pages/_app.tsx" +import type { AppProps } from "next/app"; +// highlight-start +import { Authenticator } from '@aws-amplify/ui-react' +import '@aws-amplify/ui-react/styles.css' +// highlight-end +import "@/styles/app.css"; +import { Amplify } from "aws-amplify"; +import outputs from "@/amplify_outputs.json"; + +Amplify.configure(outputs); + +export default function App({ Component, pageProps }: AppProps) { + return( + // highlight-start + + ; + + // highlight-end + ) +} +``` +
    See the complete amplify/auth/resources.ts + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from '@aws-amplify/backend'; + +/** + * Define and configure your auth resource + * When used alongside data, it is automatically configured as an auth provider for data + * @see https://docs.amplify.aws/gen2/build-a-backend/auth + */ +export const auth = defineAuth({ + loginWith: { + email: true, + // add social providers + externalProviders: { + /** + * first, create your secrets using `ampx sandbox secret` + * then, import `secret` from `@aws-amplify/backend` + * @see https://docs.amplify.aws/gen2/deploy-and-host/sandbox-environments/features/#setting-secrets + */ + // loginWithAmazon: { + // clientId: secret('LOGINWITHAMAZON_CLIENT_ID'), + // clientSecret: secret('LOGINWITHAMAZON_CLIENT_SECRET'), + // } + } + }, + /** + * enable multifactor authentication + * @see https://docs.amplify.aws/gen2/build-a-backend/auth/manage-mfa + */ + // multifactor: { + // mode: 'OPTIONAL', + // sms: { + // smsMessage: (code) => `Your verification code is ${code}`, + // }, + // }, + userAttributes: { + /** request additional attributes for your app's users */ + // profilePicture: { + // mutable: true, + // required: false, + // }, + } +}); +``` +
    + +The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. + +In your **pages/index.tsx** file, add a button to enable users to sign out of the application. Import the [`useAuthenticator`](https://ui.docs.amplify.aws/react/connected-components/authenticator/advanced#access-auth-state) hook from the Amplify UI library to hook into the state of the Authenticator. + +```tsx title="pages/index.tsx" +import type { Schema } from "@/amplify/data/resource"; +// highlight-next-line +import { useAuthenticator } from "@aws-amplify/ui-react"; +import { useState, useEffect } from "react"; +import { generateClient } from "aws-amplify/data"; + +const client = generateClient(); + +export default function HomePage() { + + // highlight-start + const { signOut } = useAuthenticator(); + // highlight-end + + // ... + + return ( +
    + {/* ... */} + // highlight-next-line + +
    + ); +} +``` + +Try out your application in your localhost environment again. You should be presented with a login experience now. + +To get these changes to the cloud, commit them to git and push the changes upstream. + +```bash title="Terminal" showLineNumbers={false} +git commit -am "added authenticator" +git push +``` + +Amplify automatically deploys the latest version of your app based on your git commits. In just a few minutes, when the application rebuilds, the hosted app will be updated to support the deletion functionality. + +## Make backend updates + +Let's update our backend to implement per-user authorization rules, allowing each user to only access their own to-dos. + +### 7. Set up local AWS credentials + +To make backend updates, we are going to require AWS credentials to deploy backend updates from our local machine. + +**Skip ahead to step 8**, if you already have an AWS profile with credentials on your local machine, and your AWS profile has the `AmplifyBackendDeployFullAccess` permission policy. + +Otherwise, **[set up local AWS credentials](/[platform]/start/account-setup/)** that grant Amplify permissions to deploy backend updates from your local machine. + +### 8. Deploy cloud sandbox + +To update your backend without affecting the production branch, use Amplify's cloud sandbox. This feature provides a separate backend environment for each developer on a team, ideal for local development and testing. + +To start your cloud sandbox, run the following command in a **new Terminal window**: + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + +Once the cloud sandbox has been fully deployed (~5 min), you'll see the `amplify_outputs.json` file updated with connection information to a new isolated authentication and data backend. + +> **Info:** The `npx ampx sandbox` command should run concurrently to your `npm run dev`. You can think of the cloud sandbox as the "localhost-equivalent for your app backend". + +### 9. Implement per-user authorization + +The to-do items in the starter are currently shared across all users, but, in most cases, you want data to be isolated on a per-user basis. + +To isolate the data on a per-user basis, you can use an "owner-based authorization rule". Let's apply the owner-based authorization rule to your to-do items: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a.model({ + content: a.string(), + // highlight-next-line + }).authorization(allow => [allow.owner()]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + // This tells the data client in your app (generateClient()) + // to sign API requests with the user authentication token. + // highlight-next-line + defaultAuthorizationMode: 'userPool', + }, +}); +``` + +In the application client code, let's also render the username to distinguish different users once they're logged in. Go to your **pages/index.tsx** file and render the `user` property from the `useAuthenticator` hook. + +```tsx title="pages/index.tsx" +// ... imports + +function HomePage() { + // highlight-next-line + const { user, signOut } = useAuthenticator(); + + // ... + + return ( +
    + // highlight-next-line +

    {user?.signInDetails?.loginId}'s todos

    + {/* ... */} +
    + ) +} +``` + +Now, let's go back to your local application and test out the user isolation of the to-do items. + +You will need to sign up new users again because now you're working with the cloud sandbox instead of your production backend. + +To get these changes to the cloud, commit them to git and push the changes upstream. + +```bash title="Terminal" showLineNumbers={false} +git commit -am "added per-user data isolation" +git push +``` + +Once your build completes in the Amplify Console, the `main` backend will update to support the changes made within the cloud sandbox. The data in the cloud sandbox is fully isolated and won't pollute your production database. + +## πŸ₯³ Success + +That's it! You have successfully built a fullstack app on AWS Amplify. If you want to learn more about how to work with Amplify, here's the conceptual guide for [how Amplify works](/[platform]/how-amplify-works/concepts/). + +--- + +--- +title: "Next.js App Router" +section: "start/quickstart" +platforms: ["nextjs"] +gen: 2 +last-updated: "2025-07-24T13:26:03.000Z" +url: "https://docs.amplify.aws/react/start/quickstart/nextjs-app-router-client-components/" +--- + +## Pre-requisites + +This Quickstart guide will walk you through how to build a task list application with TypeScript, Next.js **App Router with Client Components**, and React. Before you begin, make sure you have the following installed: + +- [Node.js](https://nodejs.org/) v14.x or later +- [npm](https://www.npmjs.com/) v6.14.4 or later +- [git](https://git-scm.com/) v2.14.1 or later +- If you are new to these technologies, we recommend you go through the official [React](https://react.dev/learn/tutorial-tic-tac-toe), [Next.js](https://nextjs.org/docs/app/getting-started), and [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html) tutorials first. + +## Deploy a fullstack app to AWS + +We've created a starter "To-do" application to help get started faster. First, you will create a repository in your GitHub account using our starter Next template. + +### 1. Create the repository + +Use our starter template to create a repository in your GitHub account. This template scaffolds `create-next-app` with Amplify backend capabilities. + + + +Create repository from template + + +Use the form in GitHub to finalize your repo's creation. + +### 2. Deploy the starter app + +Now that the repository has been created, deploy it with Amplify. + + + +Deploy to AWS + + +Select **Start with an existing app** > **GitHub**. After you give Amplify access to your GitHub account via the popup window, pick the repository and `main` branch to deploy. Make no other changes and click through the flow to **Save and deploy**. + +### 3. View deployed app + + + +Let's take a tour of the project structure in this starter repository by opening it on GitHub. The starter application has pre-written code for a to-do list app. It gives you a real-time database with a feed of all to-do list items and the ability to add new items. + +```text +β”œβ”€β”€ amplify/ # Folder containing your Amplify backend configuration +β”‚ β”œβ”€β”€ auth/ # Definition for your auth backend +β”‚ β”‚ └── resource.tsx +β”‚ β”œβ”€β”€ data/ # Definition for your data backend +β”‚ β”‚ └── resource.ts +| β”œβ”€β”€ backend.ts +β”‚ └── tsconfig.json +β”œβ”€β”€ src/ # React UI code +β”‚ β”œβ”€β”€ App.tsx # UI code to sync todos in real-time +β”‚ β”œβ”€β”€ index.css # Styling for your app +β”‚ └── main.tsx # Entrypoint of the Amplify client library +β”œβ”€β”€ package.json +└── tsconfig.json +``` + + + When the build completes, visit the newly deployed branch by selecting "View deployed URL". Since the build deployed an API, database, and authentication backend, you will be able to create new to-do items. + +In the Amplify console, click into the deployment branch (in this case **main**) > select **Data** in the left-hand menu > **Data manager** to see the data entered in your database. + +## Make frontend updates + +Let's learn how to enhance the app functionality by creating a delete flow for to-do list items. + +### 4. Set up local environment + +Now let's set up our local development environment to add features to the frontend. Click on your deployed branch and you will land on the **Deployments** page which shows you your build history and a list of deployed backend resources. + +At the bottom of the page you will see a tab for **Deployed backend resources**. Click on the tab and then click the **Download amplify_outputs.json file** button. + +![](/images/gen2/getting-started/react/amplify-outputs-download.png) + +Clone the repository locally. + +```bash title="Terminal" showLineNumbers={false} +git clone https://github.com//amplify-next-template.git +cd amplify-next-template && npm install +``` + +Now move the `amplify_outputs.json` file you downloaded above to the root of your project. + +```text +β”œβ”€β”€ amplify +β”œβ”€β”€ src +β”œβ”€β”€ amplify_outputs.json <== backend outputs file +β”œβ”€β”€ package.json +└── tsconfig.json +``` + + +The **amplify_outputs.json** file contains backend endpoint information, publicly-viewable API keys, authentication flow information, and more. The Amplify client library uses this outputs file to connect to your Amplify Backend. You can review how the outputs file is imported within the `main.tsx` file and then passed into the `Amplify.configure(...)` function of the Amplify client library. + + +### 5. Implement delete functionality + +Go to the **app/page.tsx** file and add in a new `deleteTodo` functionality and pass function into the `
  • ` element's `onClick` handler. + +```tsx title="app/page.tsx" +function App() { + // ... + // highlight-start + function deleteTodo(id: string) { + client.models.Todo.delete({ id }) + } + // highlight-end + + return ( +
    +

    My todos

    + +
      + {todos.map(todo =>
    • deleteTodo(todo.id)} + key={todo.id}> + {todo.content} +
    • )} +
    +
    + πŸ₯³ App successfully hosted. Try creating a new todo. +
    + Review next step of this tutorial. +
    +
    + ) +} +``` +
    See the complete amplify/data/resources.ts + +Open the `amplify/data/resource.ts` file in your text editor, and you will see a default data model generated for you. + +```ts showLineNumbers title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a + .model({ + content: a.string() + }) + .authorization(allow => [allow.owner(), allow.publicApiKey().to(['read'])]) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + // API Key is used for allow.publicApiKey() rules + apiKeyAuthorizationMode: { + expiresInDays: 30 + } + } +}); +``` + + The schema generated by Amplify is for a to-do app. A schema is a blueprint + for how our app's data will be organized. Within the schema, we will define + models that will correspond to a database tableβ€”`Todo` in the above code. + Finally, we will define fields, which are attributes that each data instance + will haveβ€”in the generated code, the field is `content`. Each + field will have a type attached to itβ€”in the above examples, we are stating + that the `content` field is a string. +
    + +Try out the deletion functionality now by starting the local dev server: + +```bash title="Terminal" showLineNumbers={false} +npm run dev +``` + +This should start a local dev server at http://localhost:3000. + +### 6. Implement login UI + +The starter application already has a pre-configured auth backend defined in the **amplify/auth/resource.ts** file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. + +The fastest way to get your login experience up and running is to use our Authenticator UI component. To properly integrate it with Next.js App Router, we'll create a client component wrapper and use it in the layout. + +First, create an AuthenticatorWrapper.tsx file in your app directory: + +```tsx title="app/AuthenticatorWrapper.tsx" +"use client" + +import { Authenticator } from "@aws-amplify/ui-react"; + +export default function AuthenticatorWrapper({ + children, +}: { + children: React.ReactNode; +}) { + return {children}; +} +``` + +Next, update your app/layout.tsx file to import and use the AuthenticatorWrapper component: + +```tsx title="app/layout.tsx" + +import React from "react"; +import { Amplify } from "aws-amplify"; +import "./app.css"; +// highlight-start +import AuthenticatorWrapper from "./AuthenticatorWrapper"; +import "@aws-amplify/ui-react/styles.css"; +// highlight-end +import outputs from "@/amplify_outputs.json"; + +Amplify.configure(outputs); + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + // highlight-start + + + + {children} + + + + // highlight-end + ); +} +``` + +The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. + +In your **app/page.tsx** file, add a button to enable users to sign out of the application. Import the [`useAuthenticator`](https://ui.docs.amplify.aws/react/connected-components/authenticator/advanced#access-auth-state) hook from the Amplify UI library to hook into the state of the Authenticator. + +```tsx title="app/page.tsx" +import type { Schema } from "@/amplify/data/resource"; +// highlight-next-line +import { useAuthenticator } from "@aws-amplify/ui-react"; +import { useState, useEffect } from "react"; +import { generateClient } from "aws-amplify/data"; + +const client = generateClient(); + +export default function HomePage() { + + // highlight-start + const { signOut } = useAuthenticator(); + // highlight-end + + // ... + + return ( +
    + {/* ... */} + // highlight-next-line + +
    + ); +} +``` + +Try out your application in your localhost environment again. You should be presented with a login experience now. + +To get these changes to the cloud, commit them to git and push the changes upstream. + +```bash title="Terminal" showLineNumbers={false} +git commit -am "added authenticator" +git push +``` + +Amplify automatically deploys the latest version of your app based on your git commits. In just a few minutes, when the application rebuilds, the hosted app will be updated to support the deletion functionality. + +## Make backend updates + +Let's update our backend to implement per-user authorization rules, allowing each user to only access their own to-dos. + +### 7. Set up local AWS credentials + +To make backend updates, we are going to require AWS credentials to deploy backend updates from our local machine. + +**Skip ahead to step 8**, if you already have an AWS profile with credentials on your local machine, and your AWS profile has the `AmplifyBackendDeployFullAccess` permission policy. + +Otherwise, **[set up local AWS credentials](/[platform]/start/account-setup/)** that grant Amplify permissions to deploy backend updates from your local machine. + +### 8. Deploy cloud sandbox + +To update your backend without affecting the production branch, use Amplify's cloud sandbox. This feature provides a separate backend environment for each developer on a team, ideal for local development and testing. + +To start your cloud sandbox, run the following command in a **new Terminal window**: + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + +Once the cloud sandbox has been fully deployed (~5 min), you'll see the `amplify_outputs.json` file updated with connection information to a new isolated authentication and data backend. + +> **Info:** The `npx ampx sandbox` command should run concurrently to your `npm run dev`. You can think of the cloud sandbox as the "localhost-equivalent for your app backend". + +### 9. Implement per-user authorization + +The to-do items in the starter are currently shared across all users, but, in most cases, you want data to be isolated on a per-user basis. + +To isolate the data on a per-user basis, you can use an "owner-based authorization rule". Let's apply the owner-based authorization rule to your to-do items: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a.model({ + content: a.string(), + // highlight-next-line + }).authorization(allow => [allow.owner()]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + // This tells the data client in your app (generateClient()) + // to sign API requests with the user authentication token. + // highlight-next-line + defaultAuthorizationMode: 'userPool', + }, +}); +``` + +In the application client code, let's also render the username to distinguish different users once they're logged in. Go to your **app/page.tsx** file and render the `user` property from the `useAuthenticator` hook. + +```tsx title="app/page.tsx" +// ... imports + +function HomePage() { + // highlight-next-line + const { user, signOut } = useAuthenticator(); + + // ... + + return ( +
    + // highlight-next-line +

    {user?.signInDetails?.loginId}'s todos

    + {/* ... */} +
    + ) +} +``` + +Now, let's go back to your local application and test out the user isolation of the to-do items. + +You will need to sign up new users again because now you're working with the cloud sandbox instead of your production backend. + +To get these changes to the cloud, commit them to git and push the changes upstream. + +```bash title="Terminal" showLineNumbers={false} +git commit -am "added per-user data isolation" +git push +``` + +Once your build completes in the Amplify Console, the `main` backend will update to support the changes made within the cloud sandbox. The data in the cloud sandbox is fully isolated and won't pollute your production database. + +## πŸ₯³ Success + +That's it! You have successfully built a fullstack app on AWS Amplify. If you want to learn more about how to work with Amplify, here's the conceptual guide for [how Amplify works](/[platform]/how-amplify-works/concepts/). + +--- + +--- +title: "Configure AWS for local development" +section: "start" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-24T20:22:12.000Z" +url: "https://docs.amplify.aws/react/start/account-setup/" +--- + +> **Info:** **Note**: If you already have an AWS account and profile configured locally, you do not need to follow this guide. Please add the`AmplifyBackendDeployFullAccess` IAM role to your configured AWS profile. + +This guide will help you set up Temporary credentials with [IAM Identity Center](https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html) and [AWS Organizations](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_introduction.html), which will enable you to define Single-sign on (SSO), users, groups, permission sets, and more for your team. AWS Organizations can grow to house multiple AWS accounts. Users within the organization can traverse the AWS account(s) as their permission set allows. + +Amplify leverages the standard local credentials chain provider to simplify access to AWS services. While this guide highlights IAM Identity Center, you can explore additional methods for [authenticating with AWS locally](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html#getting-started-prereqs-keys). + +
    IAM Identity Center terminology + +IAM Identity Center enables users to sign in using a single user identity to access all their assigned AWS accounts, business applications, and custom applications in the AWS Cloud. This single sign-on capability reduces the complexity of managing multiple credentials and improves security by centralizing user authentication. + +### Users + +Users refers to the location where user identities and group information are stored and managed. IAM Identity Center can integrate with external identity sources like Microsoft Active Directory or use a built-in identity store provided by AWS. + +### Permission Set + +A collection of permissions that can be assigned to users or groups. Permission sets define what actions users are allowed to perform in your AWS accounts. They are similar to IAM policies but are used within the context of IAM Identity Center to manage access across multiple accounts. + +### AWS Organization + +AWS Organizations and IAM Identity Center work together to streamline management across multiple AWS accounts. AWS Organizations manages account structures and policies, while IAM Identity Center integrates with it to enable single sign-on and align permissions with organizational roles. This synergy ensures secure and consistent access control, simplifying user and permission management. + +### Local Profiles + +Credentials are typically resolved through the use of [AWS profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-using-profiles). Profiles can contain permanent credentials or SSO metadata, and can be set for use with Amplify by using the same techniques as the AWS CLI: + +- with the `--profile` flag +- with the `AWS_PROFILE` environment variable + +### Temporary credentials + +An alternative to permanent credentials, enable you to define permissions for a _session_. Sessions are created when you [_assume_ an IAM role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html) or sign in using AWS IAM Identity Center. These sessions come with an additional "session token" that is used to validate the temporary credentials and must be included on requests to AWS. As you are working locally, this will be presented as an additional environment variable. + +You can use temporary security credentials to make programmatic requests for AWS resources using the AWS CLI or AWS API (through the AWS SDKs). The temporary credentials provide the same permissions as long-term security credentials, such as IAM user credentials. However, there are a few differences, which are covered in the [AWS Identity and Access Management documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html). + +
    + +## Set up Identity Center + +Follow the steps below if **you have never set up AWS profiles before**. + +If you already have a profile, attach the [`AmplifyBackendDeployFullAccess`](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmplifyBackendDeployFullAccess.html) managed policy to your [IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_change-permissions.html#users_change_permissions-add-console). + +### 1. Create user with Amplify permissions + +Sign in to the AWS Console to access [IAM Identity Center page](https://console.aws.amazon.com/singlesignon/home) and choose **Enable**. + +![](/images/gen2/account-setup/sso-enable.png) + +A dialog will open, prompting you to "Choose how to configure IAM Identity Center in your AWS environment." Select **Enable with AWS Organizations** and choose **Continue**. + +![](/images/gen2/account-setup/sso-enable-dialog.png) + +Next, we are going to automate a number of steps that simulate the operations of setting up a user in the IAM Identity Center console. To get started open CloudShell, located in the console footer. + +Paste the following command in the CloudShell terminal and enter an email address you would like to associate with this AWS account: + +```bash title="CloudShell" showLineNumbers={false} +read -p "Enter email address: " user_email # hit enter +``` + +```console showLineNumbers={false} +Enter email address: +``` + +Now, run the following command + +```bash title="CloudShell" +response=$(aws sso-admin list-instances) +ssoId=$(echo $response | jq '.Instances[0].IdentityStoreId' -r) +ssoArn=$(echo $response | jq '.Instances[0].InstanceArn' -r) +email_json=$(jq -n --arg email "$user_email" '{"Type":"Work","Value":$email}') +response=$(aws identitystore create-user --identity-store-id $ssoId --user-name amplify-admin --display-name 'Amplify Admin' --name Formatted=string,FamilyName=Admin,GivenName=Amplify --emails "$email_json") +userId=$(echo $response | jq '.UserId' -r) +response=$(aws sso-admin create-permission-set --name amplify-policy --instance-arn=$ssoArn --session-duration PT12H) +permissionSetArn=$(echo $response | jq '.PermissionSet.PermissionSetArn' -r) +aws sso-admin attach-managed-policy-to-permission-set --instance-arn $ssoArn --permission-set-arn $permissionSetArn --managed-policy-arn arn:aws:iam::aws:policy/service-role/AmplifyBackendDeployFullAccess +accountId=$(aws sts get-caller-identity | jq '.Account' -r) +aws sso-admin create-account-assignment --instance-arn $ssoArn --target-id $accountId --target-type AWS_ACCOUNT --permission-set-arn $permissionSetArn --principal-type USER --principal-id $userId +# Hit enter +``` + +To validate that this worked, run the following command in the CloudShell. If something failed in this process, please **[report an issue](https://github.com/aws-amplify/amplify-backend/issues)**. Keep this information readily available for [the next step](#2-set-up-local-aws-profile). + +```bash title="CloudShell" showLineNumbers={false} +// highlight-next-line +printf "\n\nStart session url: https://$ssoId.awsapps.com/start\nRegion: $AWS_REGION\nUsername: amplify-admin\n\n" + +# you should see +Start session url: https://d-XXXXXXXXXX.awsapps.com/start +Region: us-east-1 +Username: amplify-admin +``` + +
    Prefer a manual set up? + +- After the AWS Organization is created and IAM Identity Center is enabled, you are presented with a dashboard. In the navigation pane, select **Permission sets**. + + ![AWS IAM Identity Center dashboard indicating "permission sets" in the navigation pane.](/images/gen2/account-setup/sso-dashboard-highlight-permission-sets.png) + +- Select **Create permission set**. +- When prompted for the permission set type, choose **Custom permission set**. Then choose **Next**. Expand **AWS Managed Policies (set)** and search for _amplify_. Select **AmplifyBackendDeployFullAccess** and choose **Next**. + + ![AWS IAM Identity Center custom permission set page with the "AmplifyBackendDeployFullAccess" AWS managed policy selected.](/images/gen2/account-setup/sso-permission-set-custom.png) + +- Name the permission set _amplify-policy_ and optionally change the session duration. Choose **Next**. + + ![AWS IAM Identity Center custom permission set details page with the name "AmplifySet".](/images/gen2/account-setup/sso-permission-set-custom-details.png) + +- Review the permission set and choose **Create**. +- Once the permission set is created, you will return to the IAM Identity Center dashboard. You are now ready to create your first user. Using the navigation pane, select **Users**. +- Enter the user details, then choose **Next**. + + ![AWS IAM Identity Center user creation with the username "amplify-admin".](/images/gen2/account-setup/sso-create-user.png) + +- Optionally create and add the user to a group, and choose **Next**. +- Review the user information and select **Add user**. The user will then need to verify their email using the email specified during user creation. +- Once the new user is created, you will return to the IAM Identity Center dashboard. The next step is to grant the user access to an AWS account. For this demo, we will use the AWS account we used to create the Organization, but you can create a new AWS account under your organization for use with Amplify. Select the checkbox next to the management account and choose **Assign users or groups**. + + ![AWS IAM Identity Center "AWS accounts" page with the management account checked.](/images/gen2/account-setup/sso-aws-accounts.png) + +- When prompted to assign a user or group, select the **Users** tab, select the user created in step 13, and choose **Next**. + + ![AWS IAM Identity Center "AWS accounts" page assigning "amplify-admin" to the management AWS account](/images/gen2/account-setup/sso-aws-accounts-add-user.png) + +- Assign the permission set created in step 9 and choose **Next**. +- Review the assignment information and choose **Submit**. +- Now you are ready to sign in to the access portal. Navigate back to the IAM Identity Center dashboard. Within the **Settings summary** pane, copy the URL for your **AWS access portal URL**. + + ![AWS IAM Identity Center dashboard highlighting the AWS access portal URL.](/images/gen2/account-setup/sso-dashboard-access-portal.png) + +- Navigate to the copied URL and sign in as your user, _amplify-admin_. After signing in, you should have access to an AWS account. + + ![AWS IAM Identity Center access portal displaying an AWS account.](/images/gen2/account-setup/sso-access-portal.png) + +
    + +### 2. Create password for user + +Now create a password for the user that we need for the next step. In the IdC console, navigate to _Users > amplify_admin > Reset password > Send an email to the user with instructions for resetting the password_. + +Check your email (make sure you also check your spam folder). Click on the _Reset password_ link and choose a password of your choice. When signing in make sure to use _amplify-admin_ as the _Username_. + +![](/images/gen2/account-setup/sso-reset-password.png) + +## Finish local setup + +Now, set up an AWS profile that is linked to the user you just created on your local machine. There are a few options for [getting IAM Identity Center user credentials](https://docs.aws.amazon.com/singlesignon/latest/userguide/howtogetcredentials.html), but we will use the AWS CLI configuration wizard. + +### 3. Install the AWS CLI + +Install the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). + +#### [Mac] +In your browser, download the macOS pkg file: + +[Install on Mac](https://awscli.amazonaws.com/AWSCLIV2.pkg) + +#### [Windows] +In your browser, Download and run the AWS CLI MSI installer for Windows (64-bit): + +[Install on Windows](https://awscli.amazonaws.com/AWSCLIV2.msi) + +To install the AWS CLI, run the following commands. + +#### [Linux] + +```bash showLineNumbers={false} +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" +unzip awscliv2.zip +./aws/install -i /usr/local/aws-cli -b /usr/local/bin +``` + +### 4. Set up local AWS profile + +Open your terminal, you are ready to configure an AWS profile that uses the SSO user. Use the information from CloudShell to populate the information below. + +```console title="Terminal" showLineNumbers={false} +//highlight-next-line +aws configure sso + +| SSO session name (Recommended): amplify-admin +| SSO start URL: +| SSO region: +| SSO registration scopes [sso:account:access]: +| Attempting to automatically open the SSO authorization page in your default browser. +| If the browser does not open or you wish to use a different device to authorize this request, open the following URL: +| +| https://device.sso.us-east-2.amazonaws.com/ +| +| Then enter the code: +| +| SOME-CODE + +## browser opens +``` + +After you provide this information, the browser will automatically open asking you to sign in with the username and password you just created and configure a multi-factor device to authenticate. + +Now return to the terminal and enter the following information: + +```console title="Terminal" showLineNumbers={false} +The only AWS account available to you is: +Using the account ID +The only role available to you is: amplify-policy +Using the role name "amplify-policy" +CLI default client Region [us-east-1]: +CLI default output format [None]: +``` + +**Make sure to set the profile name to `default`**. Alternatively, remember the auto-generated profile name; you will need this later. + +```console title="Terminal" showLineNumbers={false} +CLI profile name [amplify-policy-]: default +To use this profile, specify the profile name using --profile, as shown: + +aws s3 ls --profile default +``` + +If you inspect `~/.aws/config`, you should now see the SSO profile: + +```ini title="~/.aws/config" +[profile default] +sso_session = amplify-admin +sso_account_id = +sso_role_name = AdministratorAccess +region = +[sso-session amplify-admin] +sso_start_url = https://xxxxxx.awsapps.com/start# +sso_region = +sso_registration_scopes = sso:account:access +``` + +### 5. Bootstrap your AWS account + +Now you are ready to use this AWS profile with AWS Amplify. Open your Amplify project and start the sandbox. If you have multiple local profiles or named your profile something other than `default`, you can specify a profile with `--profile`. + +```bash title="Terminal" showLineNumbers={false} +// highlight-next-line +npx ampx sandbox + +# OR + +// highlight-next-line +npx ampx sandbox --profile + +``` + +Before you can start deploying resources in the cloud sandbox environment, Amplify will need to complete a one-time bootstrap setup for the account and AWS Region before it can start deploying resources. + +
    What is bootstrapping? + +Bootstrapping is the process of provisioning resources for the AWS CDK before you can deploy AWS CDK apps into an AWS environment. These resources include an Amazon S3 bucket for storing files and IAM roles that grant permissions needed to perform deployments. The required resources are defined in an AWS CloudFormation stack, called the bootstrap stack, which is usually named `CDKToolkit`. Like any AWS CloudFormation stack, it appears in the AWS CloudFormation console once it is deployed. You can learn more about this process in the [CDK documentation](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html). + +
    + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox --profile amplify-admin +The region us-east-1 has not been bootstrapped. Sign in to the AWS console as a Root user or Admin to complete the bootstrap process, then restart the sandbox. +If this is not the region you are expecting to bootstrap, check for any AWS environment variables that may be set in your shell or use --profile to specify a profile with the correct region. +``` + +During the first-time setup, `npx ampx sandbox` will ask you to sign in to the AWS Management Console. You must sign in as the account **root user** or as a user that has **AdministratorAccess** permissions. Once signed in, you will be redirected to the Amplify console. On the **Create new app** page, choose **Initialize setup now**. It may take a few minutes for the bootstrapping process to complete. + +![](/images/gen2/account-setup/profile5.png) + +## Success + +You have successfully completed the bootstrapping process and you can now return to the terminal to create a new Amplify sandbox environment: + +```bash showLineNumbers={false} +npx ampx sandbox --profile +``` + +--- + +--- +title: "Manual installation" +section: "start" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-10-28T20:46:05.000Z" +url: "https://docs.amplify.aws/react/start/manual-installation/" +--- + +To get started with AWS Amplify we recommend that you use our [quickstart](/[platform]/start/quickstart) starter template. However, for some use cases, it may be preferable to start from scratch, either with a brand new directory or an existing frontend app. In that case we recommend to use [npm](https://npmjs.com) with [`create-amplify`](https://www.npmjs.com/package/create-amplify). + +```bash title="Terminal" showLineNumbers={false} +npm create amplify@latest +``` + +```console title="Terminal" showLineNumbers={false} +? Where should we create your project? (.) # press enter +``` + +Running this command will scaffold a lightweight Amplify project in your current project with the following files: + +```text +β”œβ”€β”€ amplify/ +β”‚ β”œβ”€β”€ auth/ +β”‚ β”‚ └── resource.ts +β”‚ β”œβ”€β”€ data/ +β”‚ β”‚ └── resource.ts +β”‚ β”œβ”€β”€ backend.ts +β”‚ β”œβ”€β”€ tsconfig.json +β”‚ └── package.json +β”œβ”€β”€ node_modules/ +β”œβ”€β”€ .gitignore +β”œβ”€β”€ package-lock.json +β”œβ”€β”€ package.json +└── tsconfig.json +``` + + If needed, you can manually install AWS Amplify without using `create-amplify` or the starter template. This guide will walk you through how to initialize your project, install dependencies, and author your first backend. + +## Manual setup + +First, if your frontend framework of choice doesn't have it already, create your project's `package.json` with `npm init -y`. Then, install the Amplify dependencies for building a backend: + +```bash title="Terminal" showLineNumbers={false} +npm add --save-dev @aws-amplify/backend@latest @aws-amplify/backend-cli@latest typescript +``` + +> **Info:** **Note**: TypeScript is not a requirement but is recommended for an optimal experience. + +Next, create the entry point for your backend, `amplify/backend.ts`, with the following code: + +```ts +import { defineBackend } from '@aws-amplify/backend'; + +defineBackend({}); +``` + +Now you can run `npx ampx sandbox` to create your first backend! + +> **Warning:** Amplify Gen 2 requires your backend to be configured for use with [ECMAScript modules (ESM)](https://nodejs.org/api/esm.html). If you encounter the following error during `ampx sandbox`, consider modifying your `package.json` with `"type": "module"`: +> +> ```text +The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("@aws-amplify/backend")' call instead. +``` +> +> Or, you can create a local file in the Amplify backend directory, `amplify/package.json`: +> +> ```json +{ + "type": "module" +} +``` + +You can use `define*` functions to _define_ your resources. For example, you can define authentication: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from '@aws-amplify/backend'; + +export const auth = defineAuth({ + loginWith: { + email: true + } +}); +``` + +Or define your data resource: + +```ts title="amplify/data/resource.ts" +import { a, defineData, type ClientSchema } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a.model({ + content: a.string(), + isDone: a.boolean() + }) + .authorization(allow => [allow.publicApiKey()]) +}); + +export type Schema = ClientSchema; +export const data = defineData({ + schema +}); +``` + +Each of these newly defined resources are then imported and set in the backend definition: + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +import { data } from './data/resource'; + +defineBackend({ + auth, + data +}); +``` + +## Upgrade existing projects + +You can also update an existing frontend app. To upgrade existing Amplify code-first DX (Gen 2) apps, use your Node.js package manager (for example, `npm`) to update relevant backend packages: + +```bash title="Terminal" showLineNumbers={false} +npm update @aws-amplify/backend @aws-amplify/backend-cli +``` + +## Next steps + +We recommend the following next steps: + +- [Learn more about defining authentication](/[platform]/build-a-backend/auth) +- [Learn more about defining data](/[platform]/build-a-backend/data) +- [Get started with cloud sandbox](/[platform]/deploy-and-host/sandbox-environments) +- [Deploy and host your first app](/[platform]/deploy-and-host/fullstack-branching) + +--- + +--- +title: "Connect to AWS resources" +section: "start" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-08-07T21:03:28.000Z" +url: "https://docs.amplify.aws/react/start/connect-to-aws-resources/" +--- + +export async function getStaticPaths() { + return getCustomStaticPath(meta.platforms); +} + +Amplify client libraries provide you with the flexibility to directly connect your application to AWS resources such as AWS AppSync, Amazon Cognito, Amazon S3, and more. + +To get started, client libraries must be _configured_. This is typically done by using the [`amplify_outputs.json` file](/[platform]/reference/amplify_outputs) generated by the Amplify backend tooling, however using the client libraries does not require backend resources to be created by Amplify. + + +For JavaScript-based applications, the client library can be configured by using the generated outputs file: + +```ts title="src/main.ts" +import { Amplify } from "aws-amplify" +import outputs from "../amplify_outputs.json" + +Amplify.configure(outputs) +``` + +Or by configuring the library directly by passing a [`ResourcesConfig`](https://aws-amplify.github.io/amplify-js/api/interfaces/aws_amplify.index.ResourcesConfig.html) object. For example, to configure the client library for use with Amazon Cognito, specify the `Auth` configuration: + +```ts title="src/main.ts" +import { Amplify } from "aws-amplify" + +Amplify.configure({ + Auth: { + Cognito: { + userPoolId: "", + userPoolClientId: "", + identityPoolId: "", + loginWith: { + email: true, + }, + signUpVerificationMethod: "code", + userAttributes: { + email: { + required: true, + }, + }, + allowGuestAccess: true, + passwordFormat: { + minLength: 8, + requireLowercase: true, + requireUppercase: true, + requireNumbers: true, + requireSpecialCharacters: true, + }, + }, + }, +}) +``` + +By configuring the client library, Amplify automates the communication with the underlying AWS resources, and provides a friendly API to author your business logic. In the snippet below, the `signIn` function does not require passing information from your Cognito resource to initiate the sign-in flow. + +```ts title="src/main.ts" +import { signIn } from "aws-amplify/auth" + +await signIn({ + username: "john.doe@example.com", + password: "hunter2", +}) +``` + + +For mobile platforms, the client library can be configured by creating an `amplify_outputs.json` file in your project's directory. To get started, create the file and specify your resource configuration: + +```json title="amplify_outputs.json" +{ + "$schema": "https://raw.githubusercontent.com/aws-amplify/amplify-backend/main/packages/client-config/src/client-config-schema/schema_v1.json", + "version": "1", + "auth": { + "user_pool_id": "", + "aws_region": "", + "user_pool_client_id": "", + "identity_pool_id": "", + "mfa_methods": [], + "standard_required_attributes": [ + "email" + ], + "username_attributes": [ + "email" + ], + "user_verification_types": [ + "email" + ], + "mfa_configuration": "NONE", + "password_policy": { + "min_length": 8, + "require_lowercase": true, + "require_numbers": true, + "require_symbols": true, + "require_uppercase": true + }, + "unauthenticated_identities_enabled": true + } +} +``` + + +For more information about how to use the Amplify client libraries with existing AWS resources, visit the guides: + + + +[Connect to Cognito](/[platform]/build-a-backend/auth/use-existing-cognito-resources/) + +Connect to Cognito resources using Amplify Auth's client library + + + +--- + +--- +title: "Kotlin Coroutines support" +section: "start" +platforms: ["android"] +gen: 2 +last-updated: "2024-05-06T19:20:38.000Z" +url: "https://docs.amplify.aws/react/start/kotlin-coroutines/" +--- + +Amplify provides an optional and separate API surface which is entirely focused on using Kotlin's [coroutines](https://developer.android.com/kotlin/coroutines) and [flows](https://developer.android.com/kotlin/flow). + +To use it, import **`Amplify`** facade from `core-kotlin` instead of from `core`. See the Installation notes below for more details. + +With the Coroutines APIs, most Amplify functions are expressed as `suspend` functions. Suspending functions can be launched using one of the [lifecycle-aware coroutine scopes](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope) in the Android Architecture components: + +```kotlin +import com.amplifyframework.kotlin.core.Amplify +// ... + +val post = Post.builder() + .title("My First Post") + .build() + +lifecycleScope.launch { + try { + Amplify.DataStore.save(post) // This is suspending function! + Log.i("AmplifyKotlinDemo", "Saved a post") + } catch (failure: DataStoreException) { + Log.e("AmplifyKotlinDemo", "Save failed", failure) + } +} +``` + +Coroutines can greatly improve the readability of dependent, asynchronous calls. Moreover, you can use scopes, dispatchers, and other Kotlin coroutine primitives to get more control over your execution context. + +Let's consider what happens when you have three dependent operations. You want to save a `Post`, then an `Editor`, and finally a `PostEditor`. With Amplify's coroutines interface, you can write these operations sequentially: + +```kotlin +lifecycleScope.launch { + try { + listOf(post, editor, postEditor) + .forEach { Amplify.DataStore.save(it) } + Log.i("AmplifyKotlinDemo", "Post, Editor, and PostEditor saved") + } catch (failure: DataStoreException) { + Log.e("AmplifyKotlinDemo", "An item failed to save", failure) + } +} +``` + +In Amplify's vanilla APIs, this would have created a large block of code with three nested callbacks. + +## Installation + +Amplify's coroutine support is included in an optional module, `core-kotlin`. + +1. Under **Gradle Scripts**, open **build.gradle.kts (Module :app)**, and add the following line in `dependencies`: + + ```kotlin title="app/build.gradle.kts" + dependencies { + // Add the below line in `dependencies` + implementation("com.amplifyframework:core-kotlin:ANDROID_VERSION") + } + ``` + +2. Wherever you use the **`Amplify`** facade, import `com.amplifyframework.kotlin.core.Amplify` instead of `com.amplifyframework.core.Amplify`: + + ```kotlin + import com.amplifyframework.kotlin.core.Amplify + ``` + +## Usage + +Amplify tries to map the behavior of your callback-based APIs to Kotlin primitives in an intuitive way. Functions whose callbacks emit a single value (or error) are now expressed as suspending functions, returning the value instead. Functions whose callbacks emit a stream of values will now return Kotlin `Flow`s, instead. + +## Special cases + +Some APIs return an operation which can be cancelled. Examples include realtime subscriptions to an API, and uploading/downloading objects from Storage. + +### API subscriptions + +The API category's `subscribe()` function uses both a suspend function _and_ a Flow. The function suspends until the API subscription is established. Then, it starts emitting values over the Flow. + +```kotlin +lifecycleScope.async { + try { + Amplify.API.subscribe(request) // Suspends until subscription established + .catch { Log.e("AmplifyKotlinDemo", "Error on subscription", it) } + .collect { Log.i("AmplifyKotlinDemo", "Data on subscription = $it") } + } catch (error: ApiException) { + Log.e("AmplifyKotlinDemo", "Failed to establish subscription", error) + } +} +``` + +### Storage upload & download operations + +The Storage category's `downloadFile()` and `uploadFile()` functions are bit more complex. These APIs allow you to observe transfer progress, and also to obtain a result. Progress results are delivered over a Flow, returned from the `progress()` function. Completion events are delivered by a suspending `result()` function. + +```kotlin +// Download +val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example.txt"), localFile) + +lifecycleScope.async { + download + .progress() + .collect { Log.i("AmplifyKotlinDemo", "Download progress = $it") } +} + +lifecycleScope.async { + try { + val result = download.result() + Log.i("AmplifyKotlinDemo", "Download finished! ${result.file.path}") + } catch (failure: StorageException) { + Log.e("AmplifyKotlinDemo", "Download failed", failure) + } +} + +// Upload +val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example.txt"), localFile) + +lifecycleScope.async { + upload + .progress() + .collect { Log.i("AmplifyKotlinDemo", "Upload progress = $it") } +} +lifecycleScope.async { + try { + val result = upload.result() + Log.i("AmplifyKotlinDemo", "Upload finished! ${result.path}") + } catch (failure: StorageException) { + Log.e("AmplifyKotlinDemo", "Upload failed", failure) + } +} +``` + +--- + +--- +title: "Gen 2 for Gen 1 customers" +section: "start" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-06-24T18:00:35.000Z" +url: "https://docs.amplify.aws/react/start/migrate-to-gen2/" +--- + +## Migrating from Gen 1 to Gen 2 + +We are actively developing migration tooling to aid in transitioning your project from Gen 1 to Gen 2. Until then, we recommend you continue working with your Gen 1 Amplify project. We remain committed to supporting both Gen 1 and Gen 2 for the foreseeable future. For new projects, we recommend adopting Gen 2 to take advantage of its [enhanced capabilities](/[platform]/how-amplify-works/concepts/). Meanwhile, customers on Gen 1 will continue to receive support for high-priority bugs and essential security updates. + +## Gen 1 vs. Gen 2 feature matrix + +The tables below present a feature matrix for Gen 1 customers who are considering Gen 2 for their apps. This will help determine the support availability for various features. + +### Auth + +| Feature | Gen 1 | Gen 2 | +|---|---|---| +| Configure username | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | +| Configure email | Yes | Yes | +| Configure phone number | Yes | Yes | +| Facebook | Yes | Yes | +| Google | Yes | Yes | +| Amazon | Yes | Yes | +| Sign-in with Apple | Yes | Yes | +| Add user pool groups | Yes | Yes | +| User pool group preference | Yes | Yes | +| Email verification link redirect | Yes | Yes | +| Sign-up attributes | Yes | Yes | +| Auth trigger support | Yes | Yes | +| Auth trigger templates: Add Google reCaptcha Challenge | Yes | Yes | +| Auth trigger templates: Add user to Group | Yes | Yes | +| Auth trigger templates: Email Domain Filtering (denylist) | Yes | Yes | +| Auth trigger templates: Email Domain Filtering (allowlist) | Yes | Yes | +| Auth trigger templates: Override ID Token Claims | Yes | Yes | +| Auth trigger templates: Custom Auth Challenge Flow| Yes | No | +| Configure default password policy | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | +| Configure read/write capabilities for attributes | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | +| Oauth flow: Configure authorization v implicit grant | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | +| Admin queries | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | +| MFA login (on/off/optional) | Yes | Yes | +| MFA: SMS | Yes | Yes | +| MFA: TOTP | Yes | Yes | +| Zero-config Authenticator support | Yes | Yes | +| User management in console | Yes | Yes | +| Configure Oauth scopes | Yes | Yes | +| Email verification - code | Yes | Yes | +| Email Verification - Link | Yes | Yes | +| Oauth flow: Configure redirect URIs | Yes | Yes | +| Ability to set a friendly name for User Pool | Yes | Yes | +| Unauthenticated logins | Yes | Yes | +| Custom attributes | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | +| Oauth flow: Configure domain name prefix | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | +| Auth configuration in console | Yes | No | +| First class OIDC support | No | Yes | +| First class SAML support | No | Yes | +| Import auth | Yes | No | + +### Data + +| Feature | Gen 1 | Gen2 | +|---|---|---| +| model | Yes | Yes | +| primaryKey | Yes | Yes | +| secondaryKey (name, sortKeyFields, query) | Yes | Yes | +| hasOne | Yes | Yes | +| hasMany | Yes | Yes | +| belongsTo | Yes | Yes | +| manyToMany | Yes | No | +| default | Yes | Yes | +| **auth - model level** | | | +| auth - public - apiKey | Yes | Yes | +| auth - public - iam | Yes | Yes | +| auth - owner - userPools | Yes | Yes | +| auth - owner - ownerField - userPools | Yes | Yes | +| auth - owner - ownerField as array - userPools | Yes | Yes | +| auth - owner - oidc | Yes | Yes | +| auth - owner - ownerField - oidc | Yes | Yes | +| auth - owner - ownerField as array - oidc | Yes | Yes | +| auth - private - userPools | Yes | Yes | +| auth - private - oidc | Yes | Yes | +| auth - private - iam | Yes | Yes | +| auth - group - userPools | Yes | Yes | +| auth - group - dynamic - userPools | Yes | Yes | +| auth - group - oidc | Yes | Yes | +| auth - group - dynamic - oidc | Yes | Yes | +| auth - custom - function | Yes | Yes | +| **auth - field level** | | | +| auth - public - apiKey | Yes | Yes | +| auth - public - iam | Yes | Yes | +| auth - owner - userPools | Yes | Yes | +| auth - owner - ownerField - userPools | Yes | Yes | +| auth - owner - ownerField as array - userPools | Yes | Yes | +| auth - owner - oidc | Yes | Yes | +| auth - owner - ownerField - oidc | Yes | Yes | +| auth - owner - ownerField as array - oidc | Yes | Yes | +| auth - private - userPools | Yes | Yes | +| auth - private - oidc | Yes | Yes | +| auth - private - iam | Yes | Yes | +| auth - group - userPools | Yes | Yes | +| auth - group - dynamic - userPools | Yes | Yes | +| auth - group - oidc | Yes | Yes | +| auth - group - dynamic - oidc | Yes | Yes | +| auth - custom - function | Yes | Yes | +| **other directives** | | | +| searchable | Yes | No but we offer a guide using Zero-ETL DynamoDB-to-OpenSearch | +| predictions | Yes | No but we offer a guide with AI service integrations | +| **Custom Mutations, Queries, Subscriptions** | Yes | Yes | +| VTL handler | Yes | Yes with CDK | +| JavaScript resolver handler | No | Yes | +| function handler | Yes | Yes | +| http handler | Yes | Yes - we support custom data sources including `http` | +| **Other configurations** | | | +| DataStore support | Yes | No but we'll offer a migration guide soon | +| Visual configuration | Yes | No - Gen 2 is code-first by design | +| @model queries, mutations, subscriptions, and timestamps modifiers | Yes | No | +| Custom GraphQL Transformer plugins | Yes | No | +| MySQL and PostgreSQL support | No | Yes | +| In-IDE end-to-end type safety | No | Yes | +| @hasOne, @hasMany, and @belongsTo on required fields | Yes | No | +| fields argument on @hasOne, @hasMany, and @belongsTo | Yes | No | + +### Storage + +| Feature | Gen 1 | Gen 2 | +|---|---|---| +| Ability to provision S3 bucket | Yes | Yes | +| Auth and Guest access | Yes | [Yes](/[platform]/build-a-backend/storage/authorization/#for-gen-1-public-protected-and-private-access-pattern) | +| Auth - Configure CRUD access | Yes | Yes | +| Configure Cognito Group CRUD access | Yes | Yes | +| Guest - Configure CRUD access | Yes | Yes | +| Lambda trigger for S3 bucket | Yes | Yes | +| Import an S3 bucket | Yes | Yes | +| File browser in console | Yes | Yes | +| Ability to override/custom | Yes | Yes | +| S3 Lambda triggers | Yes | Yes | +| Locally test | Yes | Yes - with sandbox environments | +| Visual configuration | Yes | No - Gen 2 is code-first by design | +| File Browser in console | Yes | Yes | +| Import S3 buckets | Yes | No | + +### Functions + +| Feature | Gen 1 | Gen 2 | +|---|---|---| +| Function runtime: TypeScript | No | Yes | +| Function resource access permissions: auth | Yes | Yes | +| Function resource access permissions: function | Yes | Yes | +| Function resource access permissions: API | Yes | Yes | +| Function resource access permissions CRUD operations | Yes | Yes | +| Function resource access permissions: custom | No | Yes | +| Environment variables | Yes | Yes | +| Secrets | Yes | Yes | +| Cron jobs | Yes | Yes | +| Configure memory size | Yes | Yes | +| Function build options for Node.js | Yes | Yes | +| Function templates: AWS AppSync - GraphQL API request (with IAM) | Yes | Yes | +| Function templates: CRUD function for DynamoDB (Integration with API Gateway) | Yes | Yes | +| Function templates: GraphQL Lambda Authorizer | Yes | Yes | +| Function templates: Hello World | Yes | Yes | +| Function templates: Lambda trigger | Yes | Yes | +| Function logs in console | Yes | Yes | +| Function resource access permissions: geo | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/grant-access-to-other-resources/#using-cdk) | +| Function resource access permissions: analytics | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/grant-access-to-other-resources/#using-cdk) | +| Function runtime: .NET 6 | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/custom-functions/) | +| Function runtime: Go | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/custom-functions/) | +| Function runtime: Java | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/custom-functions/) | +| Function runtime: JavaScript | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/custom-functions/) | +| Function runtime: Python | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/custom-functions/) | +| Lambda layers | Yes | No | + +### Other categories + + +[Amplify JS Auth API documentation](/[platform]/build-a-backend/auth/connect-your-frontend/) + +| Feature | Gen 1 | Gen 2 | +|---|---|---| +| REST API| Yes| No +| Analytics| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/analytics/) +| Geo| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/geo/) +| Predictions | Yes| No +| Interactions| Yes| No + + + +| Feature | Gen 1 | Gen 2 | +|---|---|---| +| REST API| Yes| Yes with custom CDK +| Analytics| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/analytics/) +| Geo| No| No +| Predictions | No| No +| Interactions| No| No + + + +| Feature | Gen 1 | Gen 2 | +|---|---|---| +| REST API| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/rest-api/) +| Analytics| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/analytics/) +| Geo| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/geo/) +| Predictions | Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/predictions/) +| Interactions| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/interactions/) + + +--- + +--- +title: "Platform setup" +section: "start" +platforms: ["flutter"] +gen: 2 +last-updated: "2025-12-29T21:54:06.000Z" +url: "https://docs.amplify.aws/react/start/platform-setup/" +--- + +## iOS + +Amplify requires a minimum deployment target of 13.0 and Xcode 15.0 or higher when targeting iOS. Follow the steps below to update the minimum deployment target. + +Open `ios/Podfile` and update the target iOS platform to 13.0 or higher. + +> **Info:** If there is no file located at `ios/Podfile`, add `amplify_flutter` to your `pubspec.yaml` and run `pub get`. This will automatically create the file. + +```diff title="ios/Podfile" +- # Uncomment this line to define a global platform for your project +- # platform :ios, '12.0' ++ platform :ios, '13.0' +``` + +Open your project in Xcode and select Runner, Targets -> Runner and then the "General" tab. Under the "Minimum Deployments" section, update the iOS version to 13.0 or higher. + +![Setting the iOS version to 13.0 or higher in the minimum deployments section of the Runner general window.](/images/project-setup/flutter/ios/target-min-deployment-version.png) + +Select Runner, Project -> Runner and then the "Build Settings" tab. Update "iOS Deployment Target" to 13.0 or higher. + +![Setting the iOS version to 13.0 or higher in the deployment targets section of the Runner info window.](/images/project-setup/flutter/ios/project-min-deployment-version.png) + +## Android + +Amplify Flutter supports API level 24+ (Android 7.0+), and requires Gradle 8+, Kotlin 1.9+, and Java 17+ when targeting Android. Follow the steps below to apply these changes in your app. + +> **Warning:** The steps below are intended for Flutter apps created with Flutter version 3.16+. If your app was created prior to version 3.16, please follow the guide [here](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) to migrate to Gradle's declarative plugins block before following the steps below. + +#### [Gradle Kotlin] +1. Open `android/settings.gradle.kts` and update the Android Gradle plugin and kotlin versions: + +```diff title="android/settings.gradle.kts" +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" +- id("com.android.application") version "8.7.0" apply false +- id("org.jetbrains.kotlin.android") version "1.8.22" apply false ++ id("com.android.application") version "8.12.1" apply false ++ id("org.jetbrains.kotlin.android") version "2.2.0" apply false +} +``` + +2. Open `android/gradle/wrapper/gradle-wrapper.properties` and update the Gradle `distributionUrl`. + +```diff title="android/gradle/wrapper/gradle-wrapper.properties" +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip ++distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip +``` + +3. Open `android/app/build.gradle.kts` and update the Java version and minimum Android SDK version. + +```diff title="android/app/build.gradle.kts" +android { + namespace = "com.example.myapp" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + compileOptions { +- sourceCompatibility = JavaVersion.VERSION_1_8 +- targetCompatibility = JavaVersion.VERSION_1_8 ++ sourceCompatibility = JavaVersion.VERSION_17 ++ targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { +- jvmTarget = JavaVersion.VERSION_11.toString() ++ jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.myapp" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. +- minSdk = flutter.minSdkVersion ++ minSdk = 24 + targetSdk = flutter.targetSdkVersion + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} +``` + +#### [Gradle Groovy] +1. Open `android/settings.gradle` and update the Android Gradle plugin and kotlin versions: + +```diff title="android/settings.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" +- id "com.android.application" version "7.3.0" apply false +- id "org.jetbrains.kotlin.android" version "1.7.10" apply false ++ id "com.android.application" version "8.12.1" apply false ++ id "org.jetbrains.kotlin.android" version "2.2.0" apply false +} +``` + +2. Open `android/gradle/wrapper/gradle-wrapper.properties` and update the Gradle `distributionUrl`. + +```diff title="android/gradle/wrapper/gradle-wrapper.properties" +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip ++distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip +``` + +3. Open `android/app/build.gradle` and update the Java version and minimum Android SDK version. + +```diff title="android/app/build.gradle" +android { + namespace = "com.example.myapp" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + compileOptions { +- sourceCompatibility = JavaVersion.VERSION_11 +- targetCompatibility = JavaVersion.VERSION_11 ++ sourceCompatibility = JavaVersion.VERSION_17 ++ targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.myapp" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. +- minSdk = flutter.minSdkVersion ++ minSdk = 24 + targetSdk = flutter.targetSdkVersion + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} +``` + +> **Info:** If you would like to use a higher version of Gradle or Android Gradle plugin see the compatibility matrix [here](https://developer.android.com/build/releases/gradle-plugin#updating-gradle). + +### Network Permissions for Release Builds + +Flutter apps have access to make network requests by default in debug mode. This permission needs to be added when building in release mode. To do this, open `android/app/src/main/AndroidManifest.xml` and make the following addition. + +```xml title="android/app/src/main/AndroidManifest.xml" + +// highlight-start + +// highlight-end +... + +``` + +## Web + +There are no Amplify specific requirements or setup instructions when targeting web. You will need to use a browser supported by Flutter. See the following Flutter docs for more info: + +- [Supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) +- [FAQ: Which web browsers are supported by Flutter?](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter) + +## macOS + +Amplify requires a minimum deployment target of 10.15 and Xcode 15.0 or higher when targeting macOS. Additionally, you will need to enable networking, keychain entitlements, and code signing. + +### Update Minimum Version + +Open `macos/Podfile` and update the target macOS platform to 10.15 or higher. + +> **Info:** If there is no file located at `macos/Podfile`, add `amplify_flutter` to your `pubspec.yaml` and run `pub get`. This will automatically create the file. + +```diff title="ios/Podfile" +- platform :osx, '10.14' ++ platform :osx, '10.15' +``` + +Open your project in Xcode and select Runner, Targets -> Runner and then the "General" tab. Under the "Minimum Deployments" section, update the macOS version to 10.15 or higher. + +![Setting the macOS version to 10.15 or higher in the Minimum Deployments tab of the Runner general section.](/images/project-setup/flutter/mac/target-min-deployment-version.png) + +Select Runner, Project -> Runner and then the "Info" tab. Update "macOS Deployment Target" to 10.15 or higher. + +![Setting the macOS version to 10.15 or higher in the macOS Deployment Target tab of the Runner info section.](/images/project-setup/flutter/mac/project-min-deployment-version.png) + +### Enable Network Calls + +Open your project in Xcode and select Runner, Targets -> Runner and then the "Signing and Capabilities" tab. Under "App Sandbox" select "Outgoing Connections (Client)". + +![Selecting outgoing connections in the app sandbox section of the runner signing and capabilities tab.](/images/project-setup/flutter/mac/xcode-entitlements.png) + +For more info on the Networking entitlement, see Apple's documentation on [com.apple.security.network.client](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_network_client). + +### Enable Keychain Sharing + +> **Info:** This capability is required because Amplify uses the Data Protection Keychain on macOS as a platform best practice. +> See [TN3137: macOS keychain APIs and implementations](https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains) +> for more information on how Keychain works on macOS and the Keychain Sharing entitlement. + +Open your project in Xcode and select Runner, Targets -> Runner and then the "Signing and Capabilities" tab. + +1. Click the "+ icon". + +![Plus icon circled in the signing and capabilities section of the runner tab.](/images/project-setup/flutter/mac/enable-keychain-access.png) + +2. Search for "Keychain Sharing" in the subsequent modal, and add it. + +![Keychain Sharing search result after searching keychain.](/images/project-setup/flutter/mac/search-keychain-sharing.png) + +3. Scroll down to "Keychain Sharing" in the "Signing and Capabilities" and click the "+" icon. By default, your bundle ID will be used. + +![Plus icon highlighted in the keychain sharing section of the signing and capabilities section of runner.](/images/project-setup/flutter/mac/adding-keychain-access-group.png) + +4. Finally, add a development team and enable signing. + +![Team selector and Enable Development Signing button highlighted in the signing and capabilities section of the runner tab.](/images/project-setup/flutter/mac/enable-signing.png) + +## Windows + +There are no Amplify specific requirements or setup instructions when targeting Windows. You will need to use a Windows version supported by Flutter. See the following Flutter docs for more info: + +- [Supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) + +## Linux + +Amplify Flutter depends on the [libsecret](https://wiki.gnome.org/Projects/Libsecret) library when targeting Linux. + +### Local Development + +To run and debug an app that depends on Amplify Flutter, you must install `libsecret-1-dev`. Run the following commands to install `libsecret-1-dev`. this will also install dependencies of `libsecret-1-dev`, such as `libglib2.0-dev`. + +> **Info:** The command below is intended for Ubuntu. The command may vary on other Linux distributions. + +```terminal +sudo apt-get update +sudo apt-get install -y libsecret-1-dev +``` + +### Packaging Your App + +To include the required dependencies when packaging your app with Snapcraft, include them in your `snapcraft.yaml` file. For more info, see [Flutter's documentation on releasing to the Snap Store](https://docs.flutter.dev/deployment/linux). + +```yaml +parts: + my-app: + plugin: flutter + source: . + flutter-target: lib/main.dart + build-packages: + - libsecret-1-dev + stage-packages: + - libsecret-1-0 +``` + +--- + +--- +title: "Build with AI assistants" +section: "start" +platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] +gen: 2 +last-updated: "2025-12-11T15:26:24.000Z" +url: "https://docs.amplify.aws/react/start/mcp-server/" +--- + +AWS MCP Server brings the power of AI coding assistants to your Amplify development workflow. Using the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/)β€”an open protocol that enables AI tools to access external data and capabilitiesβ€”you can build, deploy, and manage fullstack Amplify applications faster than ever. + +Whether you're using Amazon Kiro, Cursor, Claude Desktop, or another MCP-compatible AI coding assistant, AWS MCP Server provides the context and tools your AI needs to help you build production-ready Amplify applications. + +## Why use AWS MCP Server? + +Building fullstack applications with Amplify involves many moving parts: authentication, data modeling, storage, serverless functions, and deployment. AWS MCP Server helps your AI coding assistant understand these components and guide you through implementing them correctly. + +Instead of copying and pasting from documentation or debugging configuration issues, you can describe what you want to build in natural language. Your AI assistant uses AWS MCP Server to access the right information and execute AWS operations on your behalf. + +## Core capabilities + +AWS MCP Server provides three core capabilities that enhance your AI-assisted development experience: + +### 1. Pre-built workflows + +Pre-built workflows (called "Agent SOPs" in AWS terminology) are step-by-step guides that help your AI assistant complete complex Amplify tasks following best practices. These workflows cover common scenarios like: + +- **Backend Implementation**: Setting up authentication, data models, storage, serverless functions, and AI features +- **Frontend Integration**: Connecting your frontend framework to Amplify backend services +- **Deployment Guide**: Configuring sandbox environments, production deployments, and CI/CD pipelines + +When you ask your AI assistant to help with one of these tasks, it follows the pre-built workflow to ensure you get a production-ready implementation. + +### 2. Documentation access + +AWS MCP Server gives your AI assistant direct access to the latest Amplify documentation. This means your assistant can provide accurate, up-to-date guidance without you needing to search through docs yourself. + +The documentation access includes: +- API references and code examples +- Configuration options and best practices +- Troubleshooting guides and common patterns + +### 3. AWS API execution + +With proper authentication, AWS MCP Server can execute AWS operations directly from your AI assistant. This enables workflows like: + +- Creating and configuring Amplify backends +- Deploying applications to AWS +- Managing AWS resources + +All operations respect your IAM permissions, so your AI assistant can only perform actions you're authorized to do. + +## Getting started + +Ready to supercharge your Amplify development with AI? Follow these guides to get started: + +--- + +--- +title: "Set up AWS MCP Server" +section: "start/mcp-server" +platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] +gen: 2 +last-updated: "2025-12-11T15:26:24.000Z" +url: "https://docs.amplify.aws/react/start/mcp-server/set-up-mcp/" +--- + +Follow the [AWS MCP Server setup guide](https://docs.aws.amazon.com/aws-mcp/latest/userguide/getting-started-aws-mcp-server.html) to configure AWS MCP Server with your AI coding assistant. The guide covers: + +- Installing prerequisites (AWS CLI, uv package manager) +- Configuring your MCP client (Amazon Kiro, Cursor, Claude Desktop, and others) +- Setting up AWS credentials and IAM permissions +- Testing your connection + +## Amplify-specific setup tips + +Once you have AWS MCP Server configured, here are some tips for Amplify development: + +### AWS credentials for Amplify + +If you haven't set up AWS credentials for Amplify development yet, follow the [Configure AWS for local development](/[platform]/start/account-setup/) guide. This ensures you have the right permissions for both AWS MCP Server and Amplify CLI operations. + +### IAM permissions + +The base AWS MCP Server permissions allow your AI assistant to access the guided workflows and documentation. For Amplify development, your credentials also need permissions to create and manage Amplify resources. + +If you're using the `AmplifyBackendDeployFullAccess` managed policy for Amplify development, you likely have sufficient permissions. If you encounter permission errors, check with your AWS administrator. + +### Verify Amplify workflows are available + +After setup, verify that the Amplify workflows are accessible by asking your AI assistant: + +``` +Which guided workflows are available for Amplify development? +``` + +Your assistant should describe the Backend Implementation, Frontend Integration, and Deployment Guide workflows. + +## Next steps + +Now that you have AWS MCP Server configured, learn about the [guided workflows](/[platform]/start/mcp-server/amplify-workflows/) available to accelerate your Amplify development. + +--- + +--- +title: "Guided workflows" +section: "start/mcp-server" +platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] +gen: 2 +last-updated: "2025-12-11T15:26:24.000Z" +url: "https://docs.amplify.aws/react/start/mcp-server/amplify-workflows/" +--- + +AWS MCP Server includes pre-built workflows (called "Agent SOPs" in AWS terminology) that guide your AI coding assistant through complex Amplify development tasks. These workflows encode best practices and ensure consistent, production-ready implementations. + +This page describes the three main workflows available and shows you how to use them effectively. + +## Available workflows + +AWS MCP Server provides three pre-built workflows designed for different stages of Amplify development. These workflows work with both new and existing projects. + +| Workflow | Purpose | +| --- | --- | +| **Backend Implementation** | Add or modify authentication, data models, storage, functions, and AI features in new or existing backends | +| **Frontend Integration** | Add Amplify to an existing frontend app or enhance an app that already uses Amplify | +| **Deployment Guide** | Configure sandbox environments, production deployments, and CI/CD for any Amplify project | + +## Backend Implementation workflow + +The Backend Implementation workflow helps you add or modify Amplify backend features following best practices. Whether you're starting fresh or adding features to an existing Amplify backend, this workflow guides you through the implementation. + +### Supported features + +
    Authentication (Auth) + +The workflow guides you through setting up Amplify Auth, including: + +- Configuring sign-up and sign-in flows +- Setting up multi-factor authentication (MFA) +- Configuring social sign-in providers (Google, Facebook, Apple, Amazon) +- Customizing authentication UI components +- Implementing password policies and account recovery + +**Example prompt:** +``` +Guide me through setting up Amplify authentication with email sign-in and Google social login. +``` + +
    + +
    Data modeling (Data) + +The workflow helps you design and implement your data layer: + +- Creating GraphQL schemas with proper relationships +- Setting up authorization rules for data access +- Configuring real-time subscriptions +- Implementing optimistic UI updates +- Setting up conflict resolution for offline scenarios + +**Example prompt:** +``` +Help me create a data model following Amplify best practices for a blog with posts, +comments, and user profiles. Posts should only be editable by their authors. +``` + +
    + +
    File storage (Storage) + +The workflow guides storage implementation: + +- Configuring S3 buckets with proper access controls +- Setting up file upload and download functionality +- Implementing access levels (public, protected, private) +- Configuring file validation and size limits + +**Example prompt:** +``` +Walk me through adding file storage so users can upload profile pictures with a 5MB limit. +``` + +
    + +
    Serverless functions (Functions) + +The workflow helps you create and deploy Lambda functions: + +- Setting up function triggers (API, scheduled, event-driven) +- Configuring environment variables and secrets +- Implementing function layers for shared code +- Setting up proper IAM permissions + +**Example prompt:** +``` +Guide me through creating a Lambda function that sends a welcome email when a new user signs up. +``` + +
    + +
    AI features (AI) + +The workflow guides AI/ML feature integration: + +- Setting up Amazon Bedrock for generative AI +- Implementing text generation and summarization +- Adding image analysis capabilities +- Configuring conversation flows + +**Example prompt:** +``` +Help me add an AI summarization feature following Amplify best practices for generative AI. +``` + +
    + +## Frontend Integration workflow + +The Frontend Integration workflow helps you add Amplify to your frontend application or enhance an existing Amplify integration. Whether you have an existing React, Vue, Angular, or mobile app that needs Amplify, or you're adding new features to an app that already uses Amplify, this workflow provides framework-specific patterns and best practices. + +### Framework patterns + +The workflow adapts its guidance based on your frontend framework: + +
    React and Next.js + +- Setting up the Amplify client library +- Using React hooks for authentication state +- Implementing data fetching with proper loading states +- Configuring server-side rendering (SSR) for Next.js +- Using Amplify UI components + +**Example prompt:** +``` +Help me connect my React app to Amplify following best practices. I need a sign-in page and protected routes. +``` + +
    + +
    Vue and Angular + +- Configuring Amplify with framework-specific patterns +- Implementing authentication guards and state management +- Setting up data binding with Amplify Data +- Using framework-appropriate UI components + +**Example prompt:** +``` +Guide me through setting up Amplify in my Vue app with a protected dashboard route. +``` + +
    + +
    React Native + +- Configuring Amplify for mobile development +- Implementing secure storage for tokens +- Setting up push notifications +- Handling offline scenarios with DataStore + +**Example prompt:** +``` +Walk me through adding offline support to my React Native app following Amplify best practices. +``` + +
    + +
    Flutter + +- Setting up Amplify Flutter packages +- Implementing authentication flows +- Configuring API and storage access +- Using Amplify UI components for Flutter + +**Example prompt:** +``` +Help me set up authentication in my Flutter app using the Amplify Authenticator widget. +``` + +
    + +
    Swift (iOS) + +- Configuring Amplify Swift packages +- Implementing async/await patterns for Amplify operations +- Setting up Combine publishers for real-time updates +- Using SwiftUI with Amplify + +**Example prompt:** +``` +Guide me through adding real-time data sync to my SwiftUI app following Amplify best practices. +``` + +
    + +
    Android (Kotlin) + +- Setting up Amplify Android libraries +- Implementing coroutines for async operations +- Configuring Jetpack Compose with Amplify +- Handling Android lifecycle with Amplify + +**Example prompt:** +``` +Walk me through connecting my Kotlin Android app to Amplify with user authentication. +``` + +
    + +## Deployment Guide workflow + +The Deployment Guide workflow helps you deploy your Amplify application through different stages, from local development to production. This works for new projects as well as existing applications that need deployment configuration or CI/CD setup. + +### Sandbox environments + +The workflow guides you through setting up personal cloud sandbox environments for development: + +- Creating isolated sandbox environments +- Configuring hot-reload for backend changes +- Managing sandbox lifecycle +- Sharing sandbox configurations with team members + +**Example prompt:** +``` +Guide me through setting up an Amplify sandbox environment for testing backend changes. +``` + +### Production deployment + +The workflow helps you deploy to production: + +- Configuring production-ready settings +- Setting up custom domains +- Implementing environment variables for different stages +- Configuring monitoring and logging + +**Example prompt:** +``` +Walk me through deploying my Amplify app to production with a custom domain. +``` + +### CI/CD integration + +The workflow guides continuous integration and deployment setup: + +- Configuring Amplify Hosting for automatic deployments +- Setting up branch-based deployments (preview, staging, production) +- Implementing pull request previews +- Configuring build settings and environment variables + +**Example prompt:** +``` +Help me set up CI/CD following Amplify best practices so my app deploys automatically when I push to main. +``` + +## Practical example: Building a fullstack app + +Here's an example of how you might use AWS MCP Server to build a complete Amplify application. This demonstrates how the pre-built workflows guide your AI assistant through each step. + +### Step 1: Set up the backend + +Start by describing your application to your AI assistant: + +``` +Help me build a task management app following Amplify best practices. Users should be able to: +- Sign up and sign in with email +- Create, edit, and delete their own tasks +- Mark tasks as complete +- See their tasks in real-time across devices +``` + +Your AI assistant uses the **Backend Implementation workflow** to: +1. Configure authentication with email sign-in +2. Create a Task data model with proper authorization rules +3. Set up real-time subscriptions for task updates + +### Step 2: Connect the frontend + +Next, ask your assistant to connect your frontend: + +``` +Guide me through connecting my React app to the Amplify backend. I need a sign-in page +and a dashboard that shows the user's tasks with real-time updates. +``` + +Your AI assistant uses the **Frontend Integration workflow** to: +1. Install and configure Amplify libraries +2. Set up the Authenticator component +3. Create data fetching hooks for tasks +4. Implement real-time updates in the UI + +### Step 3: Deploy the application + +Finally, deploy your application: + +``` +Walk me through setting up Amplify deployment so the app deploys automatically when I push +to GitHub. I also want preview deployments for pull requests. +``` + +Your AI assistant uses the **Deployment Guide workflow** to: +1. Connect your GitHub repository to Amplify Hosting +2. Configure branch-based deployments +3. Set up pull request previews +4. Configure production environment settings + +## Tips for effective prompts + +To get the best results from AWS MCP Server, keep these tips in mind: + +> **Info:** **Be specific about your requirements.** Instead of "add authentication," try "add authentication with email sign-in, Google social login, and MFA." + +- **Describe the user experience** you want, not just the technical implementation +- **Mention your framework** so the workflow can provide framework-specific guidance +- **Ask follow-up questions** if you need clarification on any step +- **Request explanations** if you want to understand why certain patterns are recommended + +### Optional: Steering files for teams + +For guaranteed consistency across all developers, you can add steering files that instruct the AI to always use the workflows. + +
    Amazon Kiro (IDE/CLI) + +Create a steering file at `.kiro/steering/amplify.md`: + +```markdown title=".kiro/steering/amplify.md" +# Amplify Development Guidelines + +When working on Amplify projects, use the AWS MCP Server guided workflows: + +- For backend features (auth, data, storage, functions, AI), call `retrieve_agent_sop` with `amplify-backend-implementation` +- For frontend integration, call `retrieve_agent_sop` with `amplify-frontend-integration` +- For deployment tasks, call `retrieve_agent_sop` with `amplify-deployment-guide` +``` + +
    + +
    Claude (Code/Desktop) + +Add to your `CLAUDE.md` file in the project root: + +```markdown title="CLAUDE.md" +# Amplify Development Guidelines + +When working on Amplify projects, use the AWS MCP Server guided workflows: + +- For backend features (auth, data, storage, functions, AI), call `retrieve_agent_sop` with `amplify-backend-implementation` +- For frontend integration, call `retrieve_agent_sop` with `amplify-frontend-integration` +- For deployment tasks, call `retrieve_agent_sop` with `amplify-deployment-guide` +``` + +
    + +
    Cursor + +Create a rules file at `.cursor/rules/amplify.mdc`: + +```markdown title=".cursor/rules/amplify.mdc" +--- +description: Amplify development guidelines +globs: + - "amplify/**" + - "src/**" +--- + +# Amplify Development Guidelines + +When working on Amplify projects, use the AWS MCP Server guided workflows: + +- For backend features (auth, data, storage, functions, AI), call `retrieve_agent_sop` with `amplify-backend-implementation` +- For frontend integration, call `retrieve_agent_sop` with `amplify-frontend-integration` +- For deployment tasks, call `retrieve_agent_sop` with `amplify-deployment-guide` +``` + +
    + +## Next steps + +Now that you understand the pre-built workflows, try using them in your next Amplify project: + +1. [Set up AWS MCP Server](/[platform]/start/mcp-server/set-up-mcp/) if you haven't already +2. Start a conversation with your AI assistant about what you want to build +3. Let the pre-built workflows guide you through implementation + +For more information about specific Amplify features, explore the documentation: + +- [Authentication](/[platform]/build-a-backend/auth/) +- [Data](/[platform]/build-a-backend/data/) +- [Storage](/[platform]/build-a-backend/storage/) +- [Functions](/[platform]/build-a-backend/functions/) + +--- + +--- +title: "Build & connect backend" +section: "build-a-backend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-02-21T20:06:17.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/" +--- + + + +--- + +--- +title: "Authentication" +section: "build-a-backend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-02T22:35:36.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/" +--- + +export async function getStaticPaths() { + return getCustomStaticPath(meta.platforms); +} + +--- + +--- +title: "Set up Amplify Auth" +section: "build-a-backend/auth" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-12-03T11:13:25.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/set-up-auth/" +--- + +Amplify Auth is powered by [Amazon Cognito](https://aws.amazon.com/cognito/). Cognito is a robust user directory service that handles user registration, authentication, account recovery, and other operations. [Review the concepts to learn more](/[platform]/build-a-backend/auth/concepts/). + +To get started with defining your authentication resource, open or create the auth resource file: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" + +/** + * Define and configure your auth resource + * @see https://docs.amplify.aws/gen2/build-a-backend/auth + */ +export const auth = defineAuth({ + loginWith: { + email: true, + }, +}) +``` + +By default, your auth resource is scaffolded using `email` as the default login mechanism. You can also configure your auth resource to allow signing in with: + +- Phone numbers +- External providers (Google, Facebook, Amazon, or Sign in with Apple) + +- [Passwordless authentication](/[platform]/build-a-backend/auth/concepts/passwordless/) (Email OTP, SMS OTP, or WebAuthn passkeys) + + +> **Info:** **Note:** At a minimum you will need to pass a `loginWith` value to set up how your users sign in to your app. Signing in with email and password is configured by default if you do not provide any value. + + +## Enable passwordless authentication + +You can enable passwordless authentication methods to provide a more secure and user-friendly experience: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from '@aws-amplify/backend'; + +export const auth = defineAuth({ + loginWith: { + email: { + otpLogin: true // Enable email-based one-time passwords + } + } +}); +``` + +[Learn more about passwordless authentication options](/[platform]/build-a-backend/auth/concepts/passwordless/). + + +## Deploy auth resource + +After you have chosen and defined your authentication resource, run the following command to create your resource in your personal cloud sandbox. + + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox --outputs-format dart --outputs-out-dir lib +``` + + +> **Warning:** Be sure to add a "raw" folder under `app/src/main/res` directory if it does not exist. + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox --outputs-out-dir +``` + + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + + +After a successful deployment, this command also generates an outputs file (`amplify_outputs.json`) to enable your frontend app to connect to your backend resources. The values you configure in your backend authentication resource are set in the generated outputs file to automatically configure the frontend [`Authenticator connected component`](https://ui.docs.amplify.aws/react/connected-components/authenticator). + +## Connect your application code to your auth resource + +Creating and correctly implementing the sign-in flow can be challenging and time-consuming. Amplify's Authenticator UI component streamlines this by enabling you to rapidly build the entire authentication flow for your app. The component works seamlessly with configuration in `amplify/auth/resource.ts` to automatically connect with your backend resources. + +Amplify has pre-built UI components for React, Vue, Angular, React Native, Swift, Android, and Flutter. In this guide, we are focusing on those for web applications. + + +First, install the `@aws-amplify/ui-react` library: + +```bash title="Terminal" showLineNumbers={false} +npm add @aws-amplify/ui-react +``` + +Next, open **pages/\_app.tsx** and add the `Authenticator` component. + +```ts title="pages/_app.tsx" +import type { AppProps } from 'next/app'; +import { Authenticator } from '@aws-amplify/ui-react'; +import { Amplify } from 'aws-amplify'; +import outputs from '@/amplify_outputs.json'; +import '@aws-amplify/ui-react/styles.css'; + +Amplify.configure(outputs); + +export default function App({ Component, pageProps }: AppProps) { + return ( + + {({ signOut, user }) => ( +
    +

    Hello {user?.username}

    + + +
    + )} +
    + ); +}; +``` + + + +#### [Vue 3] + +First, install the `@aws-amplify/ui-vue` library: + +```bash title="Terminal" showLineNumbers={false} +npm add @aws-amplify/ui-vue +``` + +Next, open **src/App.vue** and add the `Authenticator` component. + +**Authenticator** + +The `Authenticator` component offers a simple way to add authentication flows into your app. This component encapsulates an authentication workflow in the framework of your choice and is backed by your backend Auth resources. `Authenticator` passes the `user` info and `signOut` function to the inner template. + +```html + + + +``` + +#### [Vue 2] + +First, install the `@aws-amplify/ui-components` library: + +```bash title="Terminal" showLineNumbers={false} +npm add @aws-amplify/ui-components +``` + +Now open **src/main.ts** and add the following below your last import: + +```js title="src/main.ts" +import '@aws-amplify/ui-components'; +import { + applyPolyfills, + defineCustomElements +} from '@aws-amplify/ui-components/loader'; +import Vue from 'vue'; + +Vue.config.ignoredElements = [/amplify-\w*/]; + +applyPolyfills().then(() => { + defineCustomElements(window); +}); +``` + +Next, open **src/App.ts** and add the `amplify-authenticator` component. + +**amplify-authenticator** + +The `amplify-authenticator` component offers a simple way to add authentication flows into your app. This component encapsulates an authentication workflow in the framework of your choice and is backed by your backend Auth resources. The optional `amplify-sign-out` component is available if you would like to render a sign-out button. + +```html title="src/App.ts" + +``` + + + +First, install the `@aws-amplify/ui-angular` library: + +```bash title="Terminal" showLineNumbers={false} +npm add @aws-amplify/ui-angular +``` + +Now open **app.module.ts** and add the Amplify imports and configuration: + +```js title="app.module.ts" +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular'; + +import { AppComponent } from './app.component'; +import outputs from './amplify_outputs.json'; + +Amplify.configure(outputs); + +@NgModule({ + declarations: [AppComponent], + imports: [BrowserModule, AmplifyAuthenticatorModule], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule {} +``` + +Next, import the default theme inside **styles.css**: + +```css title="styles.css" +@import '~@aws-amplify/ui-angular/theme.css'; +``` + +Next, open **app.component.html** and add the `amplify-authenticator` component. + +**amplify-authenticator** + +The `Authenticator` component offers a simple way to add authentication flows into your app. This component encapsulates an authentication workflow in the framework of your choice and is backed by your backend Auth resources. `Authenticator` passes the `user` info and `signOut` function to the inner template. + +The `amplify-authenticator` component offers a simple way to add authentication flows into your app. This component encapsulates an authentication workflow in the framework of your choice and is backed by your backend Auth resources. `amplify-authenticator` passes the `user` info and `signOut` function to the inner template. + +```html title="app.component.html" + + +

    Welcome {{ user.username }}!

    + +
    +
    +``` + + +First, install the `@aws-amplify/ui-react-native` library: + +```bash title="Terminal" showLineNumbers={false} +npm add \ + @aws-amplify/react-native \ + @aws-amplify/ui-react-native \ + aws-amplify \ + @react-native-community/netinfo \ + @react-native-async-storage/async-storage \ + react-native-safe-area-context@^4.2.5 \ + react-native-get-random-values +``` + +> **Info:** If your project will support Federated Sign In using the `React Native Authenticator` the `@aws-amplify/rtn-web-browser` package is also required: +> +> ```bash title="Terminal" showLineNumbers={false} +npm add @aws-amplify/rtn-web-browser +``` + +Then install the iOS cocoapods by running: + +```bash title="Terminal" showLineNumbers={false} +npx pod-install +``` + +> **Warning:** For calling native libraries and platform dependencies from Expo, you need to run the prebuild command for generating the folders for related platforms. +> +> ```bash title="Terminal" showLineNumbers={false} +npx expo prebuild +``` +Next, update the `App.tsx` file with the following to set up the authentication flow: + +```typescript +import React from "react"; +import { Button, View, StyleSheet } from "react-native"; +import { Amplify } from "aws-amplify"; +import { Authenticator, useAuthenticator } from "@aws-amplify/ui-react-native"; +import outputs from "./amplify_outputs.json"; + +Amplify.configure(outputs); + +const SignOutButton = () => { + const { signOut } = useAuthenticator(); + + return ( + + + + )} + + ); +} +``` + + +The Authenticator component is automatically configured based on the outputs generated from your backend. To learn more about the Authenticator and how to customize its appearance, visit the [Amplify UI documentation](https://ui.docs.amplify.aws/). + + +Conversely, you can bring your own UI and leverage the library from [`aws-amplify`](https://www.npmjs.com/package/aws-amplify) to handle authentication flows manually. + + +--- + +--- +title: "Sign-up" +section: "build-a-backend/auth/connect-your-frontend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-12-09T19:54:14.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-up/" +--- + +Amplify provides a client library that enables you to interact with backend resources such as Amplify Auth. + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/swift/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/flutter/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/android/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + +To get started, you can use the `signUp()` API to create a new user in your backend: + + +```ts +import { signUp } from "aws-amplify/auth" + +const { isSignUpComplete, userId, nextStep } = await signUp({ + username: "hello@mycompany.com", + password: "hunter2", + options: { + userAttributes: { + email: "hello@mycompany.com", + phone_number: "+15555555555" // E.164 number convention + }, + } +}); +``` + + +```dart +/// Signs a user up with a username, password, and email. The required +/// attributes may be different depending on your app's configuration. +Future signUpUser({ + required String username, + required String password, + required String email, + String? phoneNumber, +}) async { + try { + final userAttributes = { + AuthUserAttributeKey.email: email, + if (phoneNumber != null) AuthUserAttributeKey.phoneNumber: phoneNumber, + // additional attributes as needed + }; + final result = await Amplify.Auth.signUp( + username: username, + password: password, + options: SignUpOptions( + userAttributes: userAttributes, + ), + ); + await _handleSignUpResult(result); + } on AuthException catch (e) { + safePrint('Error signing up user: ${e.message}'); + } +} +``` + +```dart +Future _handleSignUpResult(SignUpResult result) async { + switch (result.nextStep.signUpStep) { + case AuthSignUpStep.confirmSignUp: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + break; + case AuthSignUpStep.done: + safePrint('Sign up is complete'); + break; + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` + + + +#### [Java] + +```java +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); + +Amplify.Auth.signUp( + "username", + "Password123", + AuthSignUpOptions.builder().userAttributes(attributes).build(), + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val attrs = mapOf( + AuthUserAttributeKey.email() to "my@email.com", + AuthUserAttributeKey.phoneNumber() to "+15551234567" +) +val options = AuthSignUpOptions.builder() + .userAttributes(attrs.map { AuthUserAttribute(it.key, it.value) }) + .build() +Amplify.Auth.signUp("username", "Password123", options, + { Log.i("AuthQuickstart", "Sign up result = $it") }, + { Log.e("AuthQuickstart", "Sign up failed", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val attrs = mapOf( + AuthUserAttributeKey.email() to "my@email.com", + AuthUserAttributeKey.phoneNumber() to "+15551234567" +) +val options = AuthSignUpOptions.builder() + .userAttributes(attrs.map { AuthUserAttribute(it.key, it.value) }) + .build() +try { + val result = Amplify.Auth.signUp("username", "Password123", options) + Log.i("AuthQuickstart", "Sign up OK: $result") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign up failed", error) +} +``` + +#### [RxJava] + +```java +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); + +RxAmplify.Auth.signUp( + "username", + "Password123", + AuthSignUpOptions.builder().userAttributes(attributes).build()) + .subscribe( + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func signUp(username: String, password: String, email: String, phonenumber: String) async { + let userAttributes = [AuthUserAttribute(.email, value: email), AuthUserAttribute(.phoneNumber, value: phonenumber)] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + + do { + let signUpResult = try await Amplify.Auth.signUp( + username: username, + password: password, + options: options + ) + + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } catch let error as AuthError { + print("An error occurred while registering a user \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func signUp(username: String, password: String, email: String, phonenumber: String) -> AnyCancellable { + let userAttributes = [ + AuthUserAttribute(.email, value: email), + AuthUserAttribute(.phoneNumber, value: phonenumber) + ] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + Amplify.Publisher.create { + try await Amplify.Auth.signUp( + username: username, + password: password, + options: options + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while registering a user \(authError)") + } + } + receiveValue: { signUpResult in + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } + return sink +} +``` + + + +The `signUp` API response will include a `nextStep` property, which can be used to determine if further action is required. It may return the following next steps: + + +| Next Step | Description | +| --------- | ----------- | +| `CONFIRM_SIGN_UP` | The sign up needs to be confirmed by collecting a code from the user and calling `confirmSignUp`. | +| `DONE` | The sign up process has been fully completed. | +| `COMPLETE_AUTO_SIGN_IN` | The sign up process needs to complete by invoking the `autoSignIn` API. | + + + +| Next Step | Description | +| --------- | ----------- | +| `CONFIRM_SIGN_UP_STEP` | The sign up needs to be confirmed by collecting a code from the user and calling `confirmSignUp`. | +| `DONE` | The sign up process has been fully completed. | + + + +| Next Step | Description | +| --------- | ----------- | +| `confirmSignUp` | The sign up needs to be confirmed by collecting a code from the user and calling `confirmSignUp`. | +| `done` | The sign up process has been fully completed. | + + +## Confirm sign-up + +By default, each user that signs up remains in the unconfirmed status until they verify with a confirmation code that was sent to their email or phone number. The following are the default verification methods used when either `phone` or `email` are used as `loginWith` options. + +| Login option | User account verification channel | +| ------------------- | --------------------------------- | +| `phone` | Phone Number | +| `email` | Email | +| `email` and `phone` | Email | + +You can confirm the sign-up after receiving a confirmation code from the user: + + +```ts +import { confirmSignUp } from 'aws-amplify/auth'; + +const { isSignUpComplete, nextStep } = await confirmSignUp({ + username: "hello@mycompany.com", + confirmationCode: "123456" +}); +``` + + +```dart +Future confirmUser({ + required String username, + required String confirmationCode, +}) async { + try { + final result = await Amplify.Auth.confirmSignUp( + username: username, + confirmationCode: confirmationCode, + ); + // Check if further confirmations are needed or if + // the sign up is complete. + await _handleSignUpResult(result); + } on AuthException catch (e) { + safePrint('Error confirming user: ${e.message}'); + } +} +``` + + + +#### [Java] + +```java +Amplify.Auth.confirmSignUp( + "username", + "the code you received via email", + result -> Log.i("AuthQuickstart", result.isSignUpComplete() ? "Confirm signUp succeeded" : "Confirm sign up not complete"), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.confirmSignUp( + "username", "the code you received via email", + { result -> + if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Confirm signUp succeeded") + } else { + Log.i("AuthQuickstart","Confirm sign up not complete") + } + }, + { Log.e("AuthQuickstart", "Failed to confirm sign up", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val code = "code you received via email" + val result = Amplify.Auth.confirmSignUp("username", code) + if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Signup confirmed") + } else { + Log.i("AuthQuickstart", "Signup confirmation not yet complete") + } +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to confirm signup", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.confirmSignUp("username", "the code you received via email") + .subscribe( + result -> Log.i("AuthQuickstart", result.isSignUpComplete() ? "Confirm signUp succeeded" : "Confirm sign up not complete"), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func confirmSignUp(for username: String, with confirmationCode: String) async { + do { + let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") + } catch let error as AuthError { + print("An error occurred while confirming sign up \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while confirming sign up \(authError)") + } + } + receiveValue: { _ in + print("Confirm signUp succeeded") + } +} +``` + + + + +> **Info:** **Note:** When specifying `email` or `phone` as a way for your users to sign-in, these are attributes that are used in place of the username. Visit the [concepts page to learn more about usernames](/[platform]/build-a-backend/auth/concepts/). + + + +## Practical Example + + +```tsx title="src/App.tsx" +import type { FormEvent } from "react" +import { Amplify } from "aws-amplify" +// highlight-next-line +import { signUp } from "aws-amplify/auth" +import outputs from "../amplify_outputs.json" + +Amplify.configure(outputs) + +interface SignUpFormElements extends HTMLFormControlsCollection { + email: HTMLInputElement + password: HTMLInputElement +} + +interface SignUpForm extends HTMLFormElement { + readonly elements: SignUpFormElements +} + +export default function App() { + async function handleSubmit(event: FormEvent) { + event.preventDefault() + const form = event.currentTarget + // ... validate inputs + await signUp({ + username: form.elements.email.value, + password: form.elements.password.value, + }) + } + + return ( +
    + + + + + +
    + ) +} +``` + + + + +## Sign up with passwordless methods + +Your application's users can also sign up using passwordless methods. To learn more, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/). + +### SMS OTP + + +```typescript +// Sign up using a phone number +const { nextStep: signUpNextStep } = await signUp({ + username: 'hello', + options: { + userAttributes: { + phone_number: '+15555551234', + }, + }, +}); + +if (signUpNextStep.signUpStep === 'DONE') { + console.log(`SignUp Complete`); +} + +if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { + console.log( + `Code Delivery Medium: ${signUpNextStep.codeDeliveryDetails.deliveryMedium}`, + ); + console.log( + `Code Delivery Destination: ${signUpNextStep.codeDeliveryDetails.destination}`, + ); +} + +// Confirm sign up with the OTP received +const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ + username: 'hello', + confirmationCode: '123456', +}); + +if (confirmSignUpNextStep.signUpStep === 'DONE') { + console.log(`SignUp Complete`); +} +``` + + + +#### [Java] + +```java +// Sign up using a phone number +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); + +Amplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build(), + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +// Sign up using a phone number +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15555551234") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() + +Amplify.Auth.signUp( + "hello@example.com", + null, + options, + { result -> + if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") + } else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + { result -> + if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +// Sign up using a phone number +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15555551234") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() +var result = Amplify.Auth.signUp("hello@example.com", null, options) + +if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") +} else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") +} + +// Confirm sign up with the OTP received +result = Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + +if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") +} +``` + +#### [RxJava] + +```java +// Sign up using a phone number +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); + +RxAmplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build() +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); + +// Confirm sign up with the OTP received +RxAmplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +// Sign up using an phone number +func signUp(username: String, phonenumber: String) async { + let userAttributes = [ + AuthUserAttribute(.phoneNumber, value: phonenumber) + ] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + do { + let signUpResult = try await Amplify.Auth.signUp( + username: username, + options: options + ) + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } catch let error as AuthError { + print("An error occurred while registering a user \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +// Confirm sign up with the OTP received +func confirmSignUp(for username: String, with confirmationCode: String) async { + do { + let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") + } catch let error as AuthError { + print("An error occurred while confirming sign up \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +// Sign up using a phone number +func signUp(username: String, phonenumber: String) -> AnyCancellable { + let userAttributes = [ + AuthUserAttribute(.phoneNumber, value: phonenumber) + ] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + let sink = Amplify.Publisher.create { + try await Amplify.Auth.signUp( + username: username, + options: options + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while registering a user \(authError)") + } + } + receiveValue: { signUpResult in + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } + return sink +} + +// Confirm sign up with the OTP received +func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while confirming sign up \(authError)") + } + } + receiveValue: { _ in + print("Confirm signUp succeeded") + } +} +``` + + + +### Email OTP + + +```typescript +// Sign up using an email address +const { nextStep: signUpNextStep } = await signUp({ + username: 'hello', + options: { + userAttributes: { + email: 'hello@example.com', + }, + }, +}); + +if (signUpNextStep.signUpStep === 'DONE') { + console.log(`SignUp Complete`); +} + +if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { + console.log( + `Code Delivery Medium: ${signUpNextStep.codeDeliveryDetails.deliveryMedium}`, + ); + console.log( + `Code Delivery Destination: ${signUpNextStep.codeDeliveryDetails.destination}`, + ); +} + +// Confirm sign up with the OTP received +const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ + username: 'hello', + confirmationCode: '123456', +}); + +if (confirmSignUpNextStep.signUpStep === 'DONE') { + console.log(`SignUp Complete`); +} +``` + + + +#### [Java] + +```java +// Sign up using an email address +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "hello@example.com")); + +Amplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build(), + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +// Sign up using an email address +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() + +Amplify.Auth.signUp( + "hello@example.com", + null, + options, + { result -> + if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") + } else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + { result -> + if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +// Sign up using an email address +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() +var result = Amplify.Auth.signUp("hello@example.com", null, options) + +if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") +} else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") +} + +// Confirm sign up with the OTP received +result = Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + +if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") +} +``` + +#### [RxJava] + +```java +// Sign up using an email address +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); + +RxAmplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build() +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); + +// Confirm sign up with the OTP received +RxAmplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +// Sign up using an email +func signUp(username: String, email: String) async { + let userAttributes = [ + AuthUserAttribute(.email, value: email) + ] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + do { + let signUpResult = try await Amplify.Auth.signUp( + username: username, + options: options + ) + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } catch let error as AuthError { + print("An error occurred while registering a user \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +// Confirm sign up with the OTP received +func confirmSignUp(for username: String, with confirmationCode: String) async { + do { + let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") + } catch let error as AuthError { + print("An error occurred while confirming sign up \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +// Sign up using an email +func signUp(username: String, email: String) -> AnyCancellable { + let userAttributes = [ + AuthUserAttribute(.email, value: email) + ] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + let sink = Amplify.Publisher.create { + try await Amplify.Auth.signUp( + username: username, + options: options + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while registering a user \(authError)") + } + } + receiveValue: { signUpResult in + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } + return sink +} + +// Confirm sign up with the OTP received +func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while confirming sign up \(authError)") + } + } + receiveValue: { _ in + print("Confirm signUp succeeded") + } +} +``` + + + +### Auto Sign In + + +```typescript +// Call `signUp` API with `USER_AUTH` as the authentication flow type for `autoSignIn` +const { nextStep: signUpNextStep } = await signUp({ + username: 'hello', + options: { + userAttributes: { + email: 'hello@example.com', + phone_number: '+15555551234', + }, + autoSignIn: { + authFlowType: 'USER_AUTH', + }, + }, +}); + +if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { + console.log( + `Code Delivery Medium: ${signUpNextStep.codeDeliveryDetails.deliveryMedium}`, + ); + console.log( + `Code Delivery Destination: ${signUpNextStep.codeDeliveryDetails.destination}`, + ); +} + +// Call `confirmSignUp` API with the OTP received +const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ + username: 'hello', + confirmationCode: '123456', +}); + +if (confirmSignUpNextStep.signUpStep === 'COMPLETE_AUTO_SIGN_IN') { + // Call `autoSignIn` API to complete the flow + const { nextStep } = await autoSignIn(); + + if (nextStep.signInStep === 'DONE') { + console.log('Successfully signed in.'); + } +} + +``` + + + +#### [Java] + +```java +private void confirmSignUp(String username, String confirmationCode) { + // Confirm sign up with the OTP received then auto sign in + Amplify.Auth.confirmSignUp( + username, + confirmationCode, + result -> { + if (result.getNextStep().getSignUpStep() == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in"); + autoSignIn(); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +} + +private void autoSignIn() { + Amplify.Auth.autoSignIn( + result -> Log.i("AuthQuickstart", "Sign in is complete"), + error -> Log.e("AuthQuickstart", error.toString()) + ); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +fun confirmSignUp(username: String, confirmationCode: String) { + // Confirm sign up with the OTP received + Amplify.Auth.confirmSignUp( + username, + confirmationCode, + { signUpResult -> + if (signUpResult.nextStep.signUpStep == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in") + autoSignIn() + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } + ) +} +fun autoSignIn() { + Amplify.Auth.autoSignIn( + { signInResult -> + Log.i("AuthQuickstart", "Sign in is complete") + }, + { Log.e("AuthQuickstart", "Failed to sign in", it) } + ) +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +suspend fun confirmSignUp(username: String, confirmationCode: String) { + // Confirm sign up with the OTP received then auto sign in + val result = Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456" + ) + + if (result.nextStep.signUpStep == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in") + autoSignIn() + } +} + +suspend fun autoSignIn() { + val result = Amplify.Auth.autoSignIn() + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Sign in is complete") + } else { + Log.e("AuthQuickstart", "Sign in did not complete $result") + } +} +``` + +#### [RxJava] + +```java +private void confirmSignUp(String username, String confirmationCode) { + // Confirm sign up with the OTP received then auto sign in + RxAmplify.Auth.confirmSignUp( + username, + confirmationCode + ) + .subscribe( + result -> { + if (result.getNextStep().getSignUpStep() == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in"); + autoSignIn(); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +} + +private void autoSignIn() { + RxAmplify.Auth.autoSignIn() + .subscribe( + result -> Log.i("AuthQuickstart", "Sign in is complete" + result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +} +``` + + + + +#### [Async/Await] + +```swift +// Confirm sign up with the OTP received and auto sign in +func confirmSignUp(for username: String, with confirmationCode: String) async { + do { + let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + if case .completeAutoSignIn(let session) = confirmSignUpResult.nextStep { + let autoSignInResult = try await Amplify.Auth.autoSignIn() + print("Auto sign in result: \(autoSignInResult.isSignedIn)") + } else { + print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") + } + } catch let error as AuthError { + print("An error occurred while confirming sign up \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +// Confirm sign up with the OTP received and auto sign in +func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while confirming sign up \(authError)") + } + } + receiveValue: { confirmSignUpResult in + if case let .completeAutoSignIn(session) = confirmSignUpResult.nextStep { + print("Confirm Sign Up succeeded. Next step is auto sign in") + // call `autoSignIn()` API to complete sign in + } else { + print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") + } + } +} + +func autoSignIn() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.autoSignIn() + }.sink { + if case let .failure(authError) = $0 { + print("Auto Sign in failed \(authError)") + } + } + receiveValue: { autoSignInResult in + if autoSignInResult.isSignedIn { + print("Auto Sign in succeeded") + } + } +} +``` + + + + +--- + +--- +title: "Sign-in" +section: "build-a-backend/auth/connect-your-frontend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-06-24T13:29:28.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-in/" +--- + +Amplify provides a client library that enables you to interact with backend resources such as Amplify Auth. + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/swift/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/flutter/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/android/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + +## Using the signIn API + + +```ts +import { signIn } from 'aws-amplify/auth' + +await signIn({ + username: "hello@mycompany.com", + password: "hunter2", +}) +``` + + +```dart +Future signInUser(String username, String password) async { + try { + final result = await Amplify.Auth.signIn( + username: username, + password: password, + ); + await _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error signing in: ${e.message}'); + } +} +``` + +Depending on your configuration and how the user signed up, one or more confirmations will be necessary. Use the `SignInResult` returned from `Amplify.Auth.signIn` to check the next step for signing in. When the value is `done`, the user has successfully signed in. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + case AuthSignInStep.confirmSignInWithSmsMfaCode: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + break; + case AuthSignInStep.confirmSignInWithNewPassword: + safePrint('Enter a new password to continue signing in'); + break; + case AuthSignInStep.confirmSignInWithCustomChallenge: + final parameters = result.nextStep.additionalInfo; + final prompt = parameters['prompt']!; + safePrint(prompt); + break; + case AuthSignInStep.resetPassword: + final resetResult = await Amplify.Auth.resetPassword( + username: username, + ); + await _handleResetPasswordResult(resetResult); + break; + case AuthSignInStep.confirmSignUp: + // Resend the sign up code to the registered device. + final resendResult = await Amplify.Auth.resendSignUpCode( + username: username, + ); + _handleCodeDelivery(resendResult.codeDeliveryDetails); + break; + case AuthSignInStep.done: + safePrint('Sign in is complete'); + break; + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` + + + +#### [Java] + +```java +Amplify.Auth.signIn( + "username", + "password", + result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.signIn("username", "password", + { result -> + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Sign in succeeded") + } else { + Log.i("AuthQuickstart", "Sign in not complete") + } + }, + { Log.e("AuthQuickstart", "Failed to sign in", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.signIn("username", "password") + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Sign in succeeded") + } else { + Log.e("AuthQuickstart", "Sign in not complete") + } +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.signIn("username", "password") + .subscribe( + result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func signIn(username: String, password: String) async { + do { + let signInResult = try await Amplify.Auth.signIn( + username: username, + password: password + ) + if signInResult.isSignedIn { + print("Sign in succeeded") + } + } catch let error as AuthError { + print("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func signIn(username: String, password: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.signIn( + username: username, + password: password + ) + }.sink { + if case let .failure(authError) = $0 { + print("Sign in failed \(authError)") + } + } + receiveValue: { signInResult in + if signInResult.isSignedIn { + print("Sign in succeeded") + } + } +} +``` + + + +The `signIn` API response will include a `nextStep` property, which can be used to determine if further action is required. It may return the following next steps: + + +| Next Step | Description | +| --------- | ----------- | +| `CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED` | The user was created with a temporary password and must set a new one. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE` | The sign-in must be confirmed with a custom challenge response. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_SMS_CODE` | The sign-in must be confirmed with an SMS code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` | The sign-in must be confirmed with an EMAIL code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `"EMAIL"` or `"TOTP"` to `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_EMAIL_SETUP` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | +| `RESET_PASSWORD` | The user must reset their password via `resetPassword`. | +| `CONFIRM_SIGN_UP` | The user hasn't completed the sign-up flow fully and must be confirmed via `confirmSignUp`. | +| `DONE` | The sign in process has been completed. | + + + +| Next Step | Description | +| --------- | ----------- | +| `CONFIRM_SIGN_IN_WITH_NEW_PASSWORD` | The user was created with a temporary password and must set a new one. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE` | The sign-in must be confirmed with a custom challenge response. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_OTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.EMAIL.challengeResponse` or `MFAType.TOTP.challengeResponse` to `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | +| `RESET_PASSWORD` | The user must reset their password via `resetPassword`. | +| `CONFIRM_SIGN_UP` | The user hasn't completed the sign-up flow fully and must be confirmed via `confirmSignUp`. | +| `DONE` | The sign in process has been completed. | + + + +| Next Step | Description | +| --------- | ----------- | +| `confirmSignInWithNewPassword` | The user was created with a temporary password and must set a new one. Complete the process with `confirmSignIn`. | +| `confirmSignInWithCustomChallenge` | The sign-in must be confirmed with a custom challenge response. Complete the process with `confirmSignIn`. | +| `confirmSignInWithTOTPCode` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | +| `confirmSignInWithSMSMFACode` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | +| `confirmSignInWithOTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | +| `confirmSignInWithPassword` | The user must set a new password. Complete the process with `confirmSignIn`. | +| `continueSignInWithFirstFactorSelection` | The user must select their preferred mode of First Factor authentication. Complete the process with `confirmSignIn`. | +| `continueSignInWithMFASelection` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | +| `continueSignInWithMFASetupSelection` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.email.challengeResponse` or `MFAType.totp.challengeResponse ` to `confirmSignIn`. | +| `continueSignInWithTOTPSetup` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | +| `continueSignInWithEmailMFASetup` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | +| `resetPassword` | The user must reset their password via `resetPassword`. | +| `confirmSignUp` | The user hasn't completed the sign-up flow fully and must be confirmed via `confirmSignUp`. | +| `done` | The sign in process has been completed. | + + + +| Next Step | Description | +| --------- | ----------- | +| `confirmSignInWithNewPassword` | The user was created with a temporary password and must set a new one. Complete the process with `confirmSignIn`. | +| `confirmSignInWithCustomChallenge` | The sign-in must be confirmed with a custom challenge response. Complete the process with `confirmSignIn`. | +| `confirmSignInWithTotpMfaCode` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | +| `confirmSignInWithSmsMfaCode` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | +| `confirmSignInWithOtpCode` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | +| `continueSignInWithMfaSelection` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | +| `continueSignInWithMfaSetupSelection` | The user must select their mode of MFA verification to setup. Complete the process by passing either `"EMAIL"` or `"TOTP"` to `confirmSignIn`. | +| `continueSignInWithTotpSetup` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | +| `continueSignInWithEmailMfaSetup` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | +| `resetPassword` | The user must reset their password via `resetPassword`. | +| `confirmSignUp` | The user hasn't completed the sign-up flow fully and must be confirmed via `confirmSignUp`. | +| `done` | The sign in process has been completed. | + + +For more information on handling the MFA steps that may be returned, see [multi-factor authentication](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/). + + + + + + +#### [Async/Await] + +```swift +func confirmSignIn() async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: "") + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Confirm sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmSignIn() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: "") + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } +} +``` + + + + + + + +### Practical Example + + +```tsx title="src/App.tsx" +import type { FormEvent } from "react" +import { Amplify } from "aws-amplify" +// highlight-next-line +import { signIn } from "aws-amplify/auth" +import outputs from "../amplify_outputs.json" + +Amplify.configure(outputs) + +interface SignInFormElements extends HTMLFormControlsCollection { + email: HTMLInputElement + password: HTMLInputElement +} + +interface SignInForm extends HTMLFormElement { + readonly elements: SignInFormElements +} + +export default function App() { + async function handleSubmit(event: FormEvent) { + event.preventDefault() + const form = event.currentTarget + // ... validate inputs + await signIn({ + username: form.elements.email.value, + password: form.elements.password.value, + }) + } + + return ( +
    + + + + + +
    + ) +} +``` + + + +## With multi-factor auth enabled + +When you have Email or SMS MFA enabled, Cognito will send messages to your users on your behalf. Email and SMS messages require that your users have email address and phone number attributes respectively. It is recommended to set these attributes as required in your user pool if you wish to use either Email MFA or SMS MFA. When these attributes are required, a user must provide these details before they can complete the sign up process. + +If you have set MFA to be required and you have activated more than one authentication factor, Cognito will prompt new users to select an MFA factor they want to use. Users must have a phone number to select SMS and an email address to select email MFA. + +If a user doesn't have the necessary attributes defined for any available message based MFA, Cognito will prompt them to set up TOTP. + +Visit the [multi-factor authentication documentation](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/) to learn more about enabling MFA on your backend auth resource. + + + +#### [Java] + +```java +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); + +Amplify.Auth.signUp( + "username", + "Password123", + AuthSignUpOptions.builder().userAttributes(attributes).build(), + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val attrs = mapOf( + AuthUserAttributeKey.email() to "my@email.com", + AuthUserAttributeKey.phoneNumber() to "+15551234567" +) +val options = AuthSignUpOptions.builder() + .userAttributes(attrs.map { AuthUserAttribute(it.key, it.value) }) + .build() +Amplify.Auth.signUp("username", "Password123", options, + { Log.i("AuthQuickstart", "Sign up result = $it") }, + { Log.e("AuthQuickstart", "Sign up failed", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val attrs = mapOf( + AuthUserAttributeKey.email() to "my@email.com", + AuthUserAttributeKey.phoneNumber() to "+15551234567" +) +val options = AuthSignUpOptions.builder() + .userAttributes(attrs.map { AuthUserAttribute(it.key, it.value) }) + .build() +try { + val result = Amplify.Auth.signUp("username", "Password123", options) + Log.i("AuthQuickstart", "Sign up OK: $result") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign up failed", error) +} +``` + +#### [RxJava] + +```java +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); + +RxAmplify.Auth.signUp( + "username", + "Password123", + AuthSignUpOptions.builder().userAttributes(attributes).build()) + .subscribe( + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func signUp(username: String, password: String, email: String, phonenumber: String) async { + let userAttributes = [AuthUserAttribute(.email, value: email), AuthUserAttribute(.phoneNumber, value: phonenumber)] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + + do { + let signUpResult = try await Amplify.Auth.signUp( + username: username, + password: password, + options: options + ) + + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } catch let error as AuthError { + print("An error occurred while registering a user \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func signUp(username: String, password: String, email: String, phonenumber: String) -> AnyCancellable { + let userAttributes = [ + AuthUserAttribute(.email, value: email), + AuthUserAttribute(.phoneNumber, value: phonenumber) + ] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + Amplify.Publisher.create { + try await Amplify.Auth.signUp( + username: username, + password: password, + options: options + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while registering a user \(authError)") + } + } + receiveValue: { signUpResult in + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } + return sink +} +``` + + + +### Confirm sign-in + + +Following sign in, you will receive a `nextStep` in the sign-in result of one of the following types. Collect the user response and then pass to the `confirmSignIn` API to complete the sign in flow. +| Next Step | Description | +| --------- | ----------- | +| `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_SMS_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` | The sign-in must be confirmed with a EMAIL code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `"EMAIL"` or `"TOTP"` to `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_EMAIL_SETUP` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | + + + +Following sign in, you will receive a `nextStep` in the sign-in result of one of the following types. Collect the user response and then pass to the `confirmSignIn` API to complete the sign in flow. +| Next Step | Description | +| --------- | ----------- | +| `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_OTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.EMAIL.challengeResponse` or `MFAType.TOTP.challengeResponse` to `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | + + + +Following sign in, you will receive a `nextStep` in the sign-in result of one of the following types. Collect the user response and then pass to the `confirmSignIn` API to complete the sign in flow. +| Next Step | Description | +| --------- | ----------- | +| `confirmSignInWithTOTPCode` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | +| `confirmSignInWithSMSMFACode` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | +| `confirmSignInWithOTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | +| `confirmSignInWithPassword` | The user must set a new password. Complete the process with `confirmSignIn`. | +| `continueSignInWithFirstFactorSelection` | The user must select their preferred mode of First Factor authentication. Complete the process with `confirmSignIn`. | +| `continueSignInWithMFASelection` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | +| `continueSignInWithMFASetupSelection` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.email.challengeResponse` or `MFAType.totp.challengeResponse ` to `confirmSignIn`. | +| `continueSignInWithTOTPSetup` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | +| `continueSignInWithEmailMFASetup` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | + + + +Following sign in, you will receive a `nextStep` in the sign-in result of one of the following types. Collect the user response and then pass to the `confirmSignIn` API to complete the sign in flow. +| Next Step | Description | +| --------- | ----------- | +| `confirmSignInWithTotpMfaCode` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | +| `confirmSignInWithSmsMfaCode` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | +| `confirmSignInWithOtpCode` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | +| `continueSignInWithMfaSelection` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | +| `continueSignInWithMfaSetupSelection` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MfaType.email.confirmationValue` or `MfaType.totp.confirmationValue` to `confirmSignIn`. | +| `continueSignInWithTotpSetup` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | +| `continueSignInWithEmailMfaSetup` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | + + + +> **Info:** **Note:** you must call `confirmSignIn` in the same app session as you call `signIn`. If you close the app, you will need to call `signIn` again. As a result, for testing purposes, you'll at least need an input field where you can enter the code sent via SMS and pass it to `confirmSignIn`. + + + +```ts title="src/main.ts" +import { confirmSignIn, signIn } from "aws-amplify/auth"; + +const { nextStep } = await signIn({ + username: "hello@mycompany.com", + password: "hunter2", +}); + +if ( + nextStep.signInStep === "CONFIRM_SIGN_IN_WITH_SMS_CODE" || + nextStep.signInStep === "CONFIRM_SIGN_IN_WITH_EMAIL_CODE" || + nextStep.signInStep === "CONFIRM_SIGN_IN_WITH_TOTP_CODE" +) { + // collect OTP from user + await confirmSignIn({ + challengeResponse: "123456", + }); +} + +if (nextStep.signInStep === "CONTINUE_SIGN_IN_WITH_MFA_SELECTION") { + // present nextStep.allowedMFATypes to user + // collect user selection + await confirmSignIn({ + challengeResponse: "EMAIL", // 'EMAIL', 'SMS', or 'TOTP' + }); +} + +if (nextStep.signInStep === "CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION") { + // present nextStep.allowedMFATypes to user + // collect user selection + await confirmSignIn({ + challengeResponse: "EMAIL", // 'EMAIL' or 'TOTP' + }); +} + +if (nextStep.signInStep === "CONTINUE_SIGN_IN_WITH_EMAIL_SETUP") { + // collect email address from user + await confirmSignIn({ + challengeResponse: "hello@mycompany.com", + }); +} + +if (nextStep.signInStep === "CONTINUE_SIGN_IN_WITH_TOTP_SETUP") { + // present nextStep.totpSetupDetails.getSetupUri() to user + // collect OTP from user + await confirmSignIn({ + challengeResponse: "123456", + }); +} + +``` +> **Info:** **Note:** The Amplify authentication flow will persist relevant session data throughout the lifespan of a page session. This enables the `confirmSignIn` API to be leveraged even after a full page refresh in a multi-page application, such as when redirecting from a login page to a sign in confirmation page. + + + + +#### [Java] + +```java +Amplify.Auth.confirmSignIn( + "confirmation code received via SMS", + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.confirmSignIn("code received via SMS", + { Log.i("AuthQuickstart", "Confirmed signin: $it") }, + { Log.e("AuthQuickstart", "Failed to confirm signin", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.confirmSignIn("code received via SMS") + Log.i("AuthQuickstart", "Confirmed signin: $result") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to confirm signin", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.confirmSignIn("confirmation code received via SMS") + .subscribe( + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func confirmSignIn() async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: "") + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Confirm sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmSignIn() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: "") + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } +} +``` + + + +## Sign in with an external identity provider + + +To sign in using an external identity provider such as Google, use the `signInWithRedirect` function. + +> **Info:** For guidance on configuring an external Identity Provider with Amplify see [External Identity Providers](/[platform]/build-a-backend/auth/concepts/external-identity-providers/) + +```ts +import { signInWithRedirect } from "aws-amplify/auth" + +signInWithRedirect({ provider: "Google" }) +``` + +> **Info:** **Note:** if you do not pass an argument to `signInWithRedirect` it will redirect your users to the [Cognito Hosted UI](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-integration.html), which has limited support for customization. + +Alternatively if you have configured OIDC or SAML-based identity providers in your auth resource, you can specify a "custom" provider in `signInWithRedirect`: + +```ts +import { signInWithRedirect } from "aws-amplify/auth" + +signInWithRedirect({ provider: { + custom: "MyOidcProvider" +}}) +``` + +## Auto sign-in + +The `autoSignIn` API will automatically sign-in a user when it was previously enabled by the `signUp` API and after any of the following cases has completed: + +- User confirmed their account with a verification code sent to their phone or email (default option). +- User confirmed their account with a verification link sent to their phone or email. In order to enable this option you need to go to the [Amazon Cognito console](https://aws.amazon.com/pm/cognito), look for your userpool, then go to the `Messaging` tab and enable `link` mode inside the `Verification message` option. Finally you need to define the `signUpVerificationMethod` to `link` inside the `Cognito` option of your `Auth` config. + +```ts title="src/main.ts" +import { autoSignIn } from 'aws-amplify/auth'; + +await autoSignIn(); +``` + +**Note**: When MFA is enabled, your users may be presented with multiple consecutive steps that require them to enter an OTP to proceed with the sign up and subsequent sign in flow. This requirement is not present when using the `USER_AUTH` flow. + + + + +### Install native module + +`signInWithRedirect` displays the sign-in UI inside a platform-dependent webview. On iOS devices, an [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) will be launched and, on Android, a [Custom Tab](https://developer.chrome.com/docs/android/custom-tabs/). After the sign-in process is complete, the sign-in UI will redirect back to your app. + +To enable this capability, an additional dependency must be installed. + +```bash title="Terminal" showLineNumbers={false} +npm add @aws-amplify/rtn-web-browser +``` + +### Platform Setup + +On iOS, there are no additional setup steps. + +#### Android + +After a successful sign-in, the sign-in UI will attempt to redirect back to your application. To register the redirect URI scheme you configured above with the device, an `intent-filter` must be added to your application's `AndroidManifest.xml` file which should be located in your React Native app's `android/app/src/main` directory. + +Add the `intent-filter` to your application's main activity, replacing `myapp` with your redirect URI scheme as necessary. + +```xml title="android/app/src/main/AndroidManifest.xml" + + + ... + + + + + + + ... + + +``` + + + +To sign in using an external identity provider such as Google, use the `signInWithWebUI` function. + +### How It Works + +Sign-in with web UI will display the sign-in UI inside a webview. After the sign-in process is complete, the sign-in UI will redirect back to your app. + +### Platform Setup + +#### Web + +To use Hosted UI in your Flutter web application locally, you must run the app with the `--web-port=3000` argument (with the value being whichever port you assigned to localhost host when configuring your redirect URIs). + +#### Android + +Add the following `queries` element to the `AndroidManifest.xml` file in your app's `android/app/src/main` directory, as well as the following `intent-filter` to the `MainActivity` in the same file. + +Replace `myapp` with your redirect URI scheme as necessary: + +```xml + + + + + + + ... + + + + + + + + + ... + +``` + +#### macOS + +Open XCode and enable the App Sandbox capability and then select "Incoming Connections (Server)" under "Network". + +![Incoming Connections setting selected in the App Sandbox section of the runner signing and capabilities tab.](/images/project-setup/flutter/mac/xcode-entitlements.png) + +#### iOS, Windows and Linux + +No specific platform configuration is required. + +### Launch Social Web UI Sign In + +You're now ready to launch sign in with your external provider's web UI. + +```dart +Future socialSignIn() async { + try { + final result = await Amplify.Auth.signInWithWebUI( + provider: AuthProvider.google, + ); + safePrint('Sign in result: $result'); + } on AuthException catch (e) { + safePrint('Error signing in: ${e.message}'); + } +} +``` + + +To sign in using an external identity provider such as Google, use the `signInWithSocialWebUI` function. + +### Update AndroidManifest.xml + +Add the following activity and queries tag to your app's `AndroidManifest.xml` file, replacing `myapp` with +your redirect URI prefix if necessary: + +```xml + + ... + + + + + + + + + ... + +``` + +### Launch Social Web UI Sign In + +Sweet! You're now ready to launch sign in with your social provider's web UI. + +For now, just add this method to the `onCreate` method of MainActivity with whatever provider you're using (shown with Facebook below): + +#### [Java] + +```java +// Replace facebook with your chosen auth provider such as google, amazon, or apple +Amplify.Auth.signInWithSocialWebUI( + AuthProvider.facebook(), + this, + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +// Replace facebook with your chosen auth provider such as google, amazon, or apple +Amplify.Auth.signInWithSocialWebUI( + AuthProvider.facebook(), + this, + { Log.i("AuthQuickstart", "Sign in OK: $it") }, + { Log.e("AuthQuickstart", "Sign in failed", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + // Replace facebook with your chosen auth provider such as google, amazon, or apple + val result = Amplify.Auth.signInWithSocialWebUI(AuthProvider.facebook(), this) + Log.i("AuthQuickstart", "Sign in OK: $result") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + +#### [RxJava] + +```java +// Replace facebook with your chosen auth provider such as google, amazon, or apple +RxAmplify.Auth.signInWithSocialWebUI(AuthProvider.facebook(), this) + .subscribe( + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +To sign in using an external identity provider such as Google, use the `signInWithWebUI` function. + +### Update Info.plist + +Sign-in with web UI requires the Amplify plugin to show up the sign-in UI inside a webview. After the sign-in process is complete it will redirect back to your app. +You have to enable this in your app's `Info.plist`. Right click Info.plist and then choose Open As > Source Code. Add the following entry in the URL scheme: + +```xml + + + + + + + + + CFBundleURLTypes + + + CFBundleURLSchemes + + myapp + + + + + + +``` + +When creating a new SwiftUI app using Xcode 13 no longer require configuration files such as the Info.plist. If you are missing this file, click on the project target, under Info, Url Types, and click '+' to add a new URL Type. Add `myapp` to the URL Schemes. You should see the Info.plist file now with the entry for CFBundleURLSchemes. + +### Launch Social Web UI Sign In + +Invoke the following API with the provider you're using (shown with Facebook below): + +#### [Async/Await] + +```swift +func socialSignInWithWebUI() async { + do { + let signInResult = try await Amplify.Auth.signInWithWebUI(for: .facebook, presentationAnchor: self.view.window!) + if signInResult.isSignedIn { + print("Sign in succeeded") + } + } catch let error as AuthError { + print("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func socialSignInWithWebUI() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.signInWithWebUI(for: .facebook, presentationAnchor: self.view.window!) + }.sink { + if case let .failure(authError) = $0 { + print("Sign in failed \(authError)") + } + } + receiveValue: { signInResult in + if signInResult.isSignedIn { + print("Sign in succeeded") + } + } +} +``` + + + + +## Sign in with passwordless methods + +Your application's users can also sign in using passwordless methods. To learn more, including how to setup the various passwordless authentication flows, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/). + +### SMS OTP + + +Pass `SMS_OTP` as the `preferredChallenge` when calling the `signIn` API in order to initiate a passwordless authentication flow with SMS OTP. + +```ts +const { nextStep: signInNextStep } = await signIn({ + username: '+15551234567', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'SMS_OTP', + }, +}); + +if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_CODE') { + // prompt user for otp code delivered via SMS + const { nextStep: confirmSignInNextStep } = await confirmSignIn({ + challengeResponse: '123456', + }); + + if (confirmSignInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); + } +} +``` + + +Pass `SMS_OTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with SMS OTP. + +#### [Java] + +```java +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP + .build(); + +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password + options, + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP + .build() + +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password + options, + { result: AuthSignInResult -> + if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } +) + +// Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + { result: AuthSignInResult? -> }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP + .build() + +// Sign in the user +val result = Amplify.Auth.signIn( + username = username, + password = null, + options = options +) +if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP +} + +// Then pass that OTP into the confirmSignIn API +val confirmResult = Amplify.Auth.confirmSignIn( + challengeResponse = "123456" +) +// confirmResult.nextStep.signInStep should be "DONE" +``` + +#### [RxJava] + +```java +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP + .build(); + +// Sign in the user +RxAmplify.Auth.signIn( + username, + null, // no password + options +).subscribe( + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +) + +// Then pass that OTP into the confirmSignIn API +RxAmplify.Auth.confirmSignIn("123456") + .subscribe( + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + +Pass `smsOTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with SMS OTP. + +#### [Async/Await] + +```swift +// sign in with `smsOTP` as preferred factor +func signIn(username: String) async { + do { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .smsOTP)) + let signInResult = try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: pluginOptions)) + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +// confirm sign in with the code received +func confirmSignIn() async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: "") + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Confirm sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +``` + +#### [Combine] + +```swift +// sign in with `smsOTP` as preferred factor +func signIn(username: String) -> AnyCancellable { + Amplify.Publisher.create { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .smsOTP)) + try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: pluginOptions)) + }.sink { + if case let .failure(authError) = $0 { + print("Sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } +} + +// confirm sign in with the code received +func confirmSignIn() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: "") + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } +} +``` + + + +### Email OTP + + +Pass `EMAIL_OTP` as the `preferredChallenge` when calling the `signIn` API in order to initiate a passwordless authentication flow using email OTP. + +```ts +const { nextStep: signInNextStep } = await signIn({ + username: 'hello@example.com', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'EMAIL_OTP', + }, +}); + +if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE') { + // prompt user for otp code delivered via email + const { nextStep: confirmSignInNextStep } = await confirmSignIn({ + challengeResponse: '123456', + }); + + if (confirmSignInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); + } +} +``` + + +Pass `EMAIL_OTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with Email OTP. + +#### [Java] + +```java +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP + .build(); + +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password + options, + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP + .build() + +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password + options, + { result: AuthSignInResult -> + if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } +) + +// Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + { result: AuthSignInResult? -> }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP + .build() + +// Sign in the user +val result = Amplify.Auth.signIn( + username = username, + password = null, + options = options +) +if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP +} + +// Then pass that OTP into the confirmSignIn API +val confirmResult = Amplify.Auth.confirmSignIn( + challengeResponse = "123456" +) +// confirmResult.nextStep.signInStep should be "DONE" +``` + +#### [RxJava] + +```java +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP + .build(); + +// Sign in the user +RxAmplify.Auth.signIn( + username, + null, // no password + options +).subscribe( + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +) + +// Then pass that OTP into the confirmSignIn API +RxAmplify.Auth.confirmSignIn("123456") + .subscribe( + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + +Pass `emailOTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with Email OTP. + +#### [Async/Await] + +```swift +// sign in with `emailOTP` as preferred factor +func signIn(username: String) async { + do { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .emailOTP)) + let signInResult = try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: pluginOptions)) + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +// confirm sign in with the code received +func confirmSignIn() async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: "") + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Confirm sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +``` + +#### [Combine] + +```swift +// sign in with `emailOTP` as preferred factor +func signIn(username: String) -> AnyCancellable { + Amplify.Publisher.create { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .emailOTP)) + try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: pluginOptions)) + }.sink { + if case let .failure(authError) = $0 { + print("Sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } +} + +// confirm sign in with the code received +func confirmSignIn() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: "") + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } +} +``` + + + +### WebAuthn Passkeys + + +Pass `WEB_AUTHN` as the `preferredChallenge` in order to initiate the passwordless authentication flow using a WebAuthn credential. + +```ts +const { nextStep: signInNextStep } = await signIn({ + username: 'hello@example.com', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'WEB_AUTHN', + }, +}); + +if (signInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); +} +``` + + +Pass `WEB_AUTHN` as the `preferredFirstFactor` in order to initiate the passwordless authentication flow using a WebAuthn credential. This flow +completes without any additional interaction from your application, so there is only one `Amplify.Auth` call needed for WebAuthn. + + +The user must have previously associated a credential to use this auth factor. To learn more, visit the [manage WebAuthn credentials page](/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/). + + + +Amplify requires an `Activity` reference to attach the PassKey UI to your Application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack) when using WebAuthn - if an `Activity` is not supplied then the UI will appear in a separate Task. For this reason, we strongly recommend always passing the `callingActivity` option to both the `signIn` and `confirmSignIn` APIs if your application allows users to sign in with passkeys. + + +#### [Java] + +```java +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn + .build(); + +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password + options, + result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn + .build() + +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password + options, + { result: AuthSignInResult -> Log.i("AuthQuickStart", "Next sign in step: ${result.nextStep}") }, + { error: AuthException -> Log.e("AuthQuickStart", error.toString()) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn + .build() + +// Sign in the user +val result = Amplify.Auth.signIn( + username = username, + password = null, + options = options +) + +// result.nextStep.signInStep should be "DONE" if use granted access to the passkey +// NOTE: `signIn` will throw a UserCancelledException if user dismissed the passkey UI +``` + +#### [RxJava] + +```java +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn + .build(); + +// Sign in the user +RxAmplify.Auth.signIn( + username, + null, // no password + options +).subscribe( + result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), + error -> Log.e("AuthQuickstart", error.toString()) +) +``` + +Using WebAuthn sign in may result in a number of possible exception types. + +- `UserCancelledException` - If the user declines to authorize access to the passkey in the system UI. You can retry the WebAuthn flow by invoking `confirmSignIn` again, or restart the `signIn` process to select a different `AuthFactorType`. +- `WebAuthnNotEnabledException` - This indicates WebAuthn is not enabled in your user pool. +- `WebAuthnNotSupportedException` - This indicates WebAuthn is not supported on the user's device. +- `WebAuthnRpMismatchException` - This indicates there is a problem with the `assetlinks.json` file deployed to your relying party. +- `WebAuthnFailedException` - This exception is used for other errors that may occur with WebAuthn. Inspect the `cause` to determine the best course of action. + + + + +Pass `webAuthn` as the `preferredFirstFactor` in order to initiate the passwordless authentication flow using a WebAuthn credential. + +#### [Async/Await] + +```swift +// sign in with `webAuthn` as preferred factor +func signIn(username: String) async { + do { + let authFactorType : AuthFactorType + if #available(iOS 17.4, *) { + authFactorType = .webAuthn + } else { + // Fallback on earlier versions + authFactorType = .passwordSRP + } + + let signInResult = try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: AWSAuthSignInOptions( + authFlowType: .userAuth( + preferredFirstFactor: authFactorType)))) + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +``` + +#### [Combine] + +```swift +// sign in with `webAuthn` as preferred factor +func signIn(username: String) async { + Amplify.Publisher.create { + let authFactorType : AuthFactorType + if #available(iOS 17.4, *) { + authFactorType = .webAuthn + } else { + // Fallback on earlier versions + authFactorType = .passwordSRP + } + + try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: AWSAuthSignInOptions( + authFlowType: .userAuth( + preferredFirstFactor: authFactorType)))) + }.sink { + if case let .failure(authError) = $0 { + print("Sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } +} +``` + + + +### Password + + +Pass either `PASSWORD` or `PASSWORD_SRP` as the `preferredChallenge` in order to initiate a traditional password based authentication flow. + +```ts +const { nextStep: signInNextStep } = await signIn({ + username: 'hello@example.com', + password: 'example-password', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'PASSWORD_SRP', // or 'PASSWORD' + }, +}); + +if (confirmSignInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); +} +``` + + + +Pass either `PASSWORD` or `PASSWORD_SRP` as the `preferredFirstFactor` in order to initiate a traditional password based authentication flow. + +#### [Java] + +```java +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD) // Sign in using Password + .build(); + +// Sign in the user +Amplify.Auth.signIn( + username, + password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP + options, + result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD) // Sign in using Password + .build() + +// Sign in the user +Amplify.Auth.signIn( + username, + password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP + options, + { result: AuthSignInResult -> Log.i("AuthQuickStart", "Next sign in step: ${result.nextStep}") }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD) // Sign in using Password + .build() + +// Sign in the user +val result = Amplify.Auth.signIn( + username = username, + password = password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP + options = options +) + +// result.nextStep.signInStep should be "DONE" +``` + +#### [RxJava] + +```java +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.Password) // Sign in using Password + .build(); + +// Sign in the user +RxAmplify.Auth.signIn( + username, + password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP + options +).subscribe( + result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), + error -> Log.e("AuthQuickstart", error.toString()) +) +``` + + + +Pass either `password` or `passwordSRP` as the `preferredFirstFactor` in order to initiate a traditional password based authentication flow. + +#### [Async/Await] + +```swift +// sign in with `password` as preferred factor +func signIn(username: String) async { + do { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .password)) + let signInResult = try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: pluginOptions)) + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +``` + +#### [Combine] + +```swift +// sign in with `password` as preferred factor +func signIn(username: String) async { + Amplify.Publisher.create { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .password)) + try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: pluginOptions)) + }.sink { + if case let .failure(authError) = $0 { + print("Sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } +} +``` + + + +### First Factor Selection + + +Omit the `preferredChallenge` parameter to discover which first factors are available for a given user. This is useful to allow +users to choose how they would like to sign in. + +The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. + +```ts +const { nextStep: signInNextStep } = await signIn({ + username: '+15551234567', + options: { + authFlowType: 'USER_AUTH', + }, +}); + +if ( + signInNextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION' +) { + // present user with list of available challenges + console.log(`Available Challenges: ${signInNextStep.availableChallenges}`); + + // respond with user selection using `confirmSignIn` API + const { nextStep: nextConfirmSignInStep } = await confirmSignIn({ + challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' + }); +} + +``` + + + +Omit the `preferredFirstFactor` option to discover which first factors are available for a given user. This is useful to allow +users to choose how they would like to sign in. + +The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. + +#### [Java] + +```java +// Omit preferredFirstFactor. If the user has more than one factor available then +// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .build(); + +// Step 1: Sign in the user +Amplify.Auth.signIn( + "hello@example.com", + null, + options, + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { + Log.i( + "AuthQuickstart", + "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors() + ); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Step 2: Select SMS OTP for sign in +Amplify.Auth.confirmSignIn( + AuthFactorType.SMS_OTP.getChallengeResponse(), + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + Log.i( + "AuthQuickStart", + "OTP code sent to " + result.getNextStep().getCodeDeliveryDetails() + ) + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Step 3: Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +// Omit preferredFirstFactor. If the user has more than one factor available then +// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. +val options: AuthSignInOptions = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .build() + +// Step 1: Sign in the user +Amplify.Auth.signIn( + "hello@example.com", + null, + options, + { result: AuthSignInResult -> + if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { + Log.i( + "AuthQuickstart", + "Available authentication factors for this user: ${result.nextStep.availableFactors}" + ) + } + }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } +) + +// Step 2: Select SMS OTP for sign in +Amplify.Auth.confirmSignIn( + AuthFactorType.SMS_OTP.getChallengeResponse(), + { result: AuthSignInResult -> + if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + Log.i( + "AuthQuickStart", + "OTP code sent to ${result.nextStep.codeDeliveryDetails}" + ) + // Show UI to collect OTP + } + }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } +) + +// Step 3: Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + { result: AuthSignInResult? -> + // result.nextStep.signInStep should be "DONE" now + }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +// Omit preferredFirstFactor. If the user has more than one factor available then +// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. +val options: AuthSignInOptions = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .build() + +// Step 1: Sign in the user +val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = null, + options = options +) + +if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { + Log.i( + "AuthQuickStart", + "Available authentication factors for this user: ${result.nextStep.availableFactors}" + ) +} + +// Step 2: Select SMS OTP for sign in +val selectFactorResult = Amplify.Auth.confirmSignIn(challengeResponse = AuthFactorType.SMS_OTP.challengeResponse) + +if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + Log.i( + "AuthQuickStart", + "OTP code sent to ${result.nextStep.codeDeliveryDetails}" + ) + // Show UI to collect OTP +} + +// Step 3: Then pass that OTP into the confirmSignIn API +val confirmResult = Amplify.Auth.confirmSignIn(challengeResponse = "123456") + +// confirmResult.nextStep.signInStep should be "DONE" now +``` + +#### [RxJava] + +```java +// Omit preferredFirstFactor. If the user has more than one factor available then +// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .build(); + +// Step 1: Sign in the user +RxAmplify.Auth.signIn( + username, + null, // no password + options +).subscribe( + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { + Log.i( + "AuthQuickstart", + "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors() + ); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +) + +// Step 2: Select SMS OTP for sign in +RxAmplify.Auth.confirmSignIn(AuthFactorType.SMS_OTP.getChallengeResponse()) + .subscribe( + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + Log.i( + "AuthQuickStart", + "OTP code sent to " + result.getNextStep().getCodeDeliveryDetails() + ) + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); + +// Step 3: Then pass that OTP into the confirmSignIn API +RxAmplify.Auth.confirmSignIn("123456") + .subscribe( + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + +Omit the `preferredFirstFactor` parameter to discover which first factors are available for a given user. This is useful to allow +users to choose how they would like to sign in. + +The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. + +#### [Async/Await] + +```swift +// Step 1: Initiate UserAuth Sign-In +let pluginOptions = AWSAuthSignInOptions(authFlowType: .userAuth) +let signInResult = try await Amplify.Auth.signIn( + username: "user@example.com", + options: .init(pluginOptions: pluginOptions) +) + +switch signInResult.nextStep { +case .continueSignInWithFirstFactorSelection(let availableFactors): + print("Available factors to select: \(availableFactors)") + // Prompt the user to select a first factor +default: + break +} + +// Step 2: Select Authentication Factor +let confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: AuthFactorType.emailOTP.challengeResponse +) + +switch confirmSignInResult.nextStep { +case .confirmSignInWithOTP(let deliveryDetails): + print("Delivery details: \(deliveryDetails)") + // Prompt the user to enter email OTP code received +default: + break +} + +// Step 3: Complete Sign-In with OTP Code +let finalSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: "" +) + +if case .done = finalSignInResult.nextStep { + print("Login successful") +} +``` + +#### [Combine] + +```swift +func signInWithUserAuth(username: String, getOTP: @escaping () async -> String) -> AnyCancellable { + Amplify.Publisher.create { + // Step 1: Initiate UserAuth Sign-In + let pluginOptions = AWSAuthSignInOptions(authFlowType: .userAuth) + let signInResult = try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: pluginOptions) + ) + + // Step 2: Handle factor selection + if case .continueSignInWithFirstFactorSelection(let availableFactors) = signInResult.nextStep { + print("Available factors to select: \(availableFactors)") + + // For this example, we select emailOTP. You could prompt the user here. + let confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: AuthFactorType.emailOTP.challengeResponse + ) + + // Step 3: Handle OTP delivery + if case .confirmSignInWithOTP(let deliveryDetails) = confirmSignInResult.nextStep { + print("Delivery details: \(deliveryDetails)") + + // Prompt user for OTP code (async closure) + let code = await getOTP() + + // Step 4: Complete sign-in with OTP code + let finalSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: code + ) + + return finalSignInResult + } else { + // Handle other next steps if needed + return confirmSignInResult + } + } else { + // Handle other next steps or immediate sign-in + return signInResult + } + } + .sink( + receiveCompletion: { completion in + if case let .failure(authError) = completion { + print("Sign in failed: \(authError)") + } + }, + receiveValue: { result in + if result.isSignedIn || (result.nextStep == .done) { + print("Sign in succeeded") + } else { + print("Next step: \(result.nextStep)") + } + } + ) +} +``` + + + +--- + +--- +title: "Switching authentication flows" +section: "build-a-backend/auth/connect-your-frontend" +platforms: ["angular", "javascript", "nextjs", "react", "react-native", "swift", "vue", "android"] +gen: 2 +last-updated: "2024-12-09T19:54:14.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/" +--- + + +`AWSCognitoAuthPlugin` allows you to switch between different auth flows while initiating signIn. You can configure the flow in the `amplify_outputs.json` file or pass the `authFlowType` as a runtime parameter to the `signIn` api call. + +For client side authentication there are four different flows that can be configured during runtime: + +1. `userSRP`: The `userSRP` flow uses the [SRP protocol (Secure Remote Password)](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) where the password never leaves the client and is unknown to the server. This is the recommended flow and is used by default. + +2. `userPassword`: The `userPassword` flow will send user credentials unencrypted to the back-end. If you want to migrate users to Cognito using the "Migration" trigger and avoid forcing users to reset their passwords, you will need to use this authentication type because the Lambda function invoked by the trigger needs to verify the supplied credentials. + +3. `customWithSRP`: The `customWithSRP` flow is used to start with SRP authentication and then switch to custom authentication. This is useful if you want to use SRP for the initial authentication and then use custom authentication for subsequent authentication attempts. + +4. `customWithoutSRP`: The `customWithoutSRP` flow is used to start authentication flow **WITHOUT** SRP and then use a series of challenge and response cycles that can be customized to meet different requirements. + +5. `userAuth`: The `userAuth` flow is a choice-based authentication flow that allows the user to choose from the list of available authentication methods. This flow is useful when you want to provide the user with the option to choose the authentication method. The choices that may be available to the user are `emailOTP`, `smsOTP`, `webAuthn`, `password` or `passwordSRP`. + +`Auth` can be configured to use the different flows at runtime by calling `signIn` with `AuthSignInOptions`'s `authFlowType` as `AuthFlowType.userPassword`, `AuthFlowType.customAuthWithoutSrp` or `AuthFlowType.customAuthWithSrp`. If you do not specify the `AuthFlowType` in `AuthSignInOptions`, the default flow (`AuthFlowType.userSRP`) will be used. + + + +Runtime configuration will take precedence and will override any auth flow type configuration present in amplify_outputs.json + + + +> For more information about authentication flows, please visit [Amazon Cognito developer documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-custom-authentication-flow) + +## USER_AUTH (Choice-based authentication) flow + +A use case for the `USER_AUTH` authentication flow is to provide the user with the option to choose the authentication method. The choices that may be available to the user are `emailOTP`, `smsOTP`, `webAuthn`, `password` or `passwordSRP`. + +```swift +let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth) +let signInResult = try await Amplify.Auth.signIn( + username: username, + password: password, + options: .init(pluginOptions: pluginOptions)) +guard case .continueSignInWithFirstFactorSelection(let availableFactors) = signInResult.nextStep else { + return +} +print("Available factors: \(availableFactors)") +``` + +The selection of the authentication method is done by the user. The user can choose from the available factors and proceed with the selected factor. You should call the `confirmSignIn` API with the selected factor to continue the sign-in process. Following is an example if you want to proceed with the `emailOTP` factor selection: + +```swift +// Select emailOTP as the factor +var confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: AuthFactorType.emailOTP.challengeResponse) +``` + +## USER_PASSWORD_AUTH flow + +A use case for the `USER_PASSWORD_AUTH` authentication flow is migrating users into Amazon Cognito + +A user migration Lambda trigger helps migrate users from a legacy user management system into your user pool. If you choose the USER_PASSWORD_AUTH authentication flow, users don't have to reset their passwords during user migration. This flow sends your user's password to the service over an encrypted SSL connection during authentication. + +When you have migrated all your users, switch flows to the more secure SRP flow. The SRP flow doesn't send any passwords over the network. + +```swift +func signIn(username: String, password: String) async throws { + + let option = AWSAuthSignInOptions(authFlowType: .userPassword) + do { + let result = try await Amplify.Auth.signIn( + username: username, + password: password, + options: AuthSignInRequest.Options(pluginOptions: option)) + print("Sign in succeeded with result: \(result)") + } catch { + print("Failed to sign in with error: \(error)") + } +} +``` + +### Migrate users with Amazon Cognito + +Amazon Cognito provides a trigger to migrate users from your existing user directory seamlessly into Cognito. You achieve this by configuring your User Pool's "Migration" trigger which invokes a Lambda function whenever a user that does not already exist in the user pool authenticates, or resets their password. + +In short, the Lambda function will validate the user credentials against your existing user directory and return a response object containing the user attributes and status on success. An error message will be returned if an error occurs. There's documentation around [how to set up this migration flow](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html) and more detailed instructions on [how the lambda should handle request and response objects](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html#cognito-user-pools-lambda-trigger-syntax-user-migration). + +## CUSTOM_AUTH flow + +Amazon Cognito User Pools supports customizing the authentication flow to enable custom challenge types, in addition to a password in order to verify the identity of users. The custom authentication flow is a series of challenge and response cycles that can be customized to meet different requirements. These challenge types may include CAPTCHAs or dynamic challenge questions. + +To define your challenges for custom authentication flow, you need to implement three Lambda triggers for Amazon Cognito. + +The flow is initiated by calling `signIn` with `AuthSignInOptions` configured with `AuthFlowType.customAuthWithSrp` OR `AuthFlowType.customAuthWithoutSrp`. + +Follow the instructions in [Custom Auth Sign In](/gen1/[platform]/build-a-backend/auth/sign-in-custom-flow/) to learn about how to integrate custom authentication flow in your application with the Auth APIs. + + + +For client side authentication there are four different flows: + +1. `USER_SRP_AUTH`: The `USER_SRP_AUTH` flow uses the [SRP protocol (Secure Remote Password)](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) where the password never leaves the client and is unknown to the server. This is the recommended flow and is used by default. + +2. `USER_PASSWORD_AUTH`: The `USER_PASSWORD_AUTH` flow will send user credentials to the backend without applying SRP encryption. If you want to migrate users to Cognito using the "Migration" trigger and avoid forcing users to reset their passwords, you will need to use this authentication type because the Lambda function invoked by the trigger needs to verify the supplied credentials. + +3. `CUSTOM_WITH_SRP` & `CUSTOM_WITHOUT_SRP`: Allows for a series of challenge and response cycles that can be customized to meet different requirements. + +4. `USER_AUTH`: The `USER_AUTH` flow is a choice-based authentication flow that allows the user to choose from the list of available authentication methods. This flow is useful when you want to provide the user with the option to choose the authentication method. The choices that may be available to the user are `EMAIL_OTP`, `SMS_OTP`, `WEB_AUTHN`, `PASSWORD` or `PASSWORD_SRP`. + +The Auth flow can be customized when calling `signIn`, for example: + +```ts title="src/main.ts" +await signIn({ + username: "hello@mycompany.com", + password: "hunter2", + options: { + authFlowType: 'USER_AUTH' + } +}) +``` + +> For more information about authentication flows, please visit [AWS Cognito developer documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-custom-authentication-flow) + +## USER_AUTH flow + +The `USER_AUTH` sign in flow supports the following methods as first factors for authentication: `WEB_AUTHN`, `EMAIL_OTP`, `SMS_OTP`, `PASSWORD`, and `PASSWORD_SRP`. + +If the desired first factor is known when authentication is initiated, it can be passed to the `signIn` API as the `preferredChallenge` to initiate the corresponding authentication flow. + +```ts +// PASSWORD_SRP / PASSWORD +// sign in with preferred challenge as password +// note password must be provided in same step +const { nextStep } = await signIn({ + username: "hello@mycompany.com", + password: "hunter2", + options: { + authFlowType: "USER_AUTH", + preferredChallenge: "PASSWORD_SRP" // or "PASSWORD" + }, +}); + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// sign in with preferred passwordless challenge +// no additional user input required at this step +const { nextStep } = await signIn({ + username: "hello@example.com", + options: { + authFlowType: "USER_AUTH", + preferredChallenge: "WEB_AUTHN" // or "EMAIL_OTP" or "SMS_OTP" + }, +}); +``` + +If the desired first factor is not known or you would like to provide users with the available options, `preferredChallenge` can be omitted from the initial `signIn` API call. + +This allows you to discover which authentication first factors are available for a user via the `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` step. You can then present the available options to the user and use the `confirmSignIn` API to respond with the user's selection. + +```ts +const { nextStep: signInNextStep } = await signIn({ + username: '+15551234567', + options: { + authFlowType: 'USER_AUTH', + }, +}); + +if ( + signInNextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION' +) { + // present user with list of available challenges + console.log(`Available Challenges: ${signInNextStep.availableChallenges}`); + + // respond with user selection using `confirmSignIn` API + const { nextStep: nextConfirmSignInStep } = await confirmSignIn({ + challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' + }); +} + +``` +Also, note that if the `preferredChallenge` passed to the initial `signIn` API call is unavailable for the user, Amplify will also respond with the `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` next step. + + +For more information about determining a first factor, and signing in with passwordless authentication factors, please visit the [Passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/) concepts page. + + +## USER_PASSWORD_AUTH flow + +A use case for the `USER_PASSWORD_AUTH` authentication flow is migrating users into Amazon Cognito + +### Set up auth backend + +In order to use the authentication flow `USER_PASSWORD_AUTH`, your Cognito app client has to be configured to allow it. In the AWS Console, this is done by ticking the checkbox at General settings > App clients > Show Details (for the affected client) > Enable username-password (non-SRP) flow. If you're using the AWS CLI or CloudFormation, update your app client by adding `USER_PASSWORD_AUTH` to the list of "Explicit Auth Flows". + +### Migrate users with Amazon Cognito + +Amazon Cognito provides a trigger to migrate users from your existing user directory seamlessly into Cognito. You achieve this by configuring your User Pool's "Migration" trigger which invokes a Lambda function whenever a user that does not already exist in the user pool authenticates, or resets their password. + +In short, the Lambda function will validate the user credentials against your existing user directory and return a response object containing the user attributes and status on success. An error message will be returned if an error occurs. Visit [Amazon Cognito user pools import guide](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html) for migration flow and more detailed instruction, and [Amazon Cognito Lambda trigger guide](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html#cognito-user-pools-lambda-trigger-syntax-user-migration) on how to set up lambda to handle request and response objects. + +## `CUSTOM_WITH_SRP` & `CUSTOM_WITHOUT_SRP` flows + +Amazon Cognito user pools supports customizing the authentication flow to enable custom challenge types, +in addition to a password in order to verify the identity of users. These challenge types may include CAPTCHAs +or dynamic challenge questions. The `CUSTOM_WITH_SRP` flow requires a password when calling `signIn`. Both of +these flows map to the `CUSTOM_AUTH` flow in Cognito. + + + +To define your challenges for custom authentication flow, you need to implement three Lambda triggers for Amazon Cognito. Please visit [AWS Amplify Custom Auth Challenge example](/[platform]/build-a-backend/functions/examples/custom-auth-flows/) for set up instructions. + + + + + +For more information about working with Lambda Triggers for custom authentication challenges, please visit [Amazon Cognito Developer Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). + + + +### Custom authentication flow + +To initiate a custom authentication flow in your app, call `signIn` without a password. A custom challenge needs to be answered using the `confirmSignIn` API: + +```ts title="src/main.ts" +import { signIn, confirmSignIn } from 'aws-amplify/auth'; + +const challengeResponse = 'the answer for the challenge'; + +const { nextStep } = await signIn({ + username, + options: { + authFlowType: 'CUSTOM_WITHOUT_SRP', + }, +}); + +if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE') { + // to send the answer of the custom challenge + await confirmSignIn({ challengeResponse }); +} +``` + +### CAPTCHA authentication + +To create a CAPTCHA challenge with a Lambda Trigger, please visit [AWS Amplify Google reCAPTCHA challenge example](/[platform]/build-a-backend/functions/examples/google-recaptcha-challenge/) for detailed examples. + + + +`AWSCognitoAuthPlugin` allows you to switch between different auth flows while initiating signIn. You can configure the flow in the `amplify_outputs.json` file or pass the `authFlowType` as a option to the `signIn` api call. + +For client side authentication there are four different flows that can be configured during runtime: + +1. `USER_SRP_AUTH`: The `USER_SRP_AUTH` flow uses the [SRP protocol (Secure Remote Password)](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) where the password never leaves the client and is unknown to the server. This is the recommended flow and is used by default. + +2. `USER_PASSWORD_AUTH`: The `USER_PASSWORD_AUTH` flow will send user credentials unencrypted to the back-end. If you want to migrate users to Cognito using the "Migration" trigger and avoid forcing users to reset their passwords, you will need to use this authentication type because the Lambda function invoked by the trigger needs to verify the supplied credentials. + +3. `CUSTOM_AUTH_WITH_SRP`: The `CUSTOM_AUTH_WITH_SRP` flow is used to start with SRP authentication and then switch to custom authentication. This is useful if you want to use SRP for the initial authentication and then use custom authentication for subsequent authentication attempts. + +4. `CUSTOM_AUTH_WITHOUT_SRP`: The `CUSTOM_AUTH_WITHOUT_SRP` flow is used to start authentication flow **WITHOUT** SRP and then use a series of challenge and response cycles that can be customized to meet different requirements. + +5. `USER_AUTH`: The `USER_AUTH` flow is a choice-based authentication flow that allows the user to choose from the list of available authentication methods. This flow is useful when you want to provide the user with the option to choose the authentication method. The choices that may be available to the user are `EMAIL_OTP`, `SMS_OTP`, `WEB_AUTHN`, `PASSWORD` or `PASSWORD_SRP`. + +`Auth` can be configured to use the different flows at runtime by calling `signIn` with `AWSCognitoAuthSignInOptions`'s `authFlowType` as `AuthFlowType.USER_PASSWORD_AUTH`, `AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP`, `AuthFlowType.CUSTOM_AUTH_WITH_SRP`, or `AuthFlowType.USER_AUTH`. If you do not specify the `AuthFlowType` in `AWSCognitoAuthSignInOptions`, the default flow specified in `amplify_outputs.json` will be used. + + + +Runtime configuration will take precedence and will override any auth flow type configuration present in `amplify_outputs.json`. + + + +For more information about authentication flows, please visit [Amazon Cognito developer documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-custom-authentication-flow) + +## USER_AUTH (Choice-based authentication) flow + +A use case for the `USER_AUTH` authentication flow is to provide the user with the option to choose the authentication method. The choices that may be available to the user are `EMAIL_OTP`, `SMS_OTP`, `WEB_AUTHN`, `PASSWORD` or `PASSWORD_SRP`. + + +Amplify requires an `Activity` reference to attach the PassKey UI to your Application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack) when using WebAuthn - if an `Activity` is not supplied then the UI will appear in a separate Task. For this reason, we strongly recommend passing the `callingActivity` option to both the `signIn` and `confirmSignIn` APIs if your application uses the `USER_AUTH` flow. + + +If the desired first factor is known before the sign in flow is initiated it can be passed to the initial sign in call. + +#### [Java] + +```java +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build(); +Amplify.Auth.signIn( + username, + password, + options, + result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) +); + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build(); +Amplify.Auth.signIn( + username, + null, + options, + result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build() +Amplify.Auth.signIn( + username, + password, + options, + { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, + { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } +) + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build() +Amplify.Auth.signIn( + username, + null, + options, + { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, + { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +try { + val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build() + val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = "password", + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +try { + val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build() + val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = null, + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + +#### [RxJava] + +```java +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build(); +RxAmplify.Auth.signIn(username, password, options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) + ); + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build(); +RxAmplify.Auth.signIn(username, null, options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) + ); +``` + +If the preferred first factor is not supplied or is unavailable, and the user has multiple factors available, the flow will continue to select an available first factor by returning an `AuthNextSignInStep.signInStep` value of `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, and a list of `AuthNextSignInStep.availableFactors`. + +The selection of the authentication method is done by the user. The user can choose from the available factors and proceed with the selected factor. You should call the `confirmSignIn` API with the selected factor to continue the sign-in process. Following is an example if you want to proceed with the `WEB_AUTHN` factor selection: + +#### [Java] + +```java +AuthConfirmSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build(); +Amplify.Auth.confirmSignIn( + AuthFactorType.WEB_AUTHN.getChallengeResponse(), + options, + result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build() +Amplify.Auth.confirmSignIn( + AuthFactorType.WEB_AUTHN.challengeResponse, + options, + { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, + { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build() + val result = Amplify.Auth.confirmSignIn( + challengeResponse = AuthFactorType.WEB_AUTHN.challengeResponse, + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + +#### [RxJava] + +```java +AuthConfirmSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build(); +RxAmplify.Auth.confirmSignIn(AuthFactorType.WEB_AUTHN.getChallengeResponse(), options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) + ); +``` + +## USER_PASSWORD_AUTH flow + +A use case for the `USER_PASSWORD_AUTH` authentication flow is migrating users into Amazon Cognito + +A user migration Lambda trigger helps migrate users from a legacy user management system into your user pool. If you choose the USER_PASSWORD_AUTH authentication flow, users don't have to reset their passwords during user migration. This flow sends your user's password to the service over an encrypted SSL connection during authentication. + +When you have migrated all your users, switch flows to the more secure SRP flow. The SRP flow doesn't send any passwords over the network. + +#### [Java] + +```java +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build(); +Amplify.Auth.signIn( + "hello@example.com", + "password", + options, + result -> Log.i("AuthQuickStart", "Sign in succeeded with result " + result), + error -> Log.e("AuthQuickStart", "Failed to sign in", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build() +Amplify.Auth.signIn( + "hello@example.com", + "password", + options, + { result -> + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build() + val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = "password", + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + +#### [RxJava] + +```java +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build(); +RxAmplify.Auth.signIn("hello@example.com", "password", options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + +### Set up auth backend + +In order to use the authentication flow `USER_PASSWORD_AUTH`, your Cognito app client has to be configured to allow it. Amplify Gen 2 enables SRP auth by default. To enable USER_PASSWORD_AUTH, you can update the `backend.ts` file with the following changes: + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend' +import { auth } from './auth/resource' +import { data } from './data/resource' + +const backend = defineBackend({ + auth, + data, +}); + +// highlight-start +backend.auth.resources.cfnResources.cfnUserPoolClient.explicitAuthFlows = [ + "ALLOW_USER_PASSWORD_AUTH", + "ALLOW_USER_SRP_AUTH", + "ALLOW_USER_AUTH", + "ALLOW_REFRESH_TOKEN_AUTH" +]; +// highlight-end +``` + +### Migrate users with Amazon Cognito + +Amazon Cognito provides a trigger to migrate users from your existing user directory seamlessly into Cognito. You achieve this by configuring your User Pool's "Migration" trigger which invokes a Lambda function whenever a user that does not already exist in the user pool authenticates, or resets their password. + +In short, the Lambda function will validate the user credentials against your existing user directory and return a response object containing the user attributes and status on success. An error message will be returned if an error occurs. There's documentation around [how to set up this migration flow](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html) and more detailed instructions on [how the lambda should handle request and response objects](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html#cognito-user-pools-lambda-trigger-syntax-user-migration). + +## CUSTOM_AUTH flow + +Amazon Cognito User Pools supports customizing the authentication flow to enable custom challenge types, in addition to a password in order to verify the identity of users. The custom authentication flow is a series of challenge and response cycles that can be customized to meet different requirements. These challenge types may include CAPTCHAs or dynamic challenge questions. + +To define your challenges for custom authentication flow, you need to implement three Lambda triggers for Amazon Cognito. + +The flow is initiated by calling `signIn` with `AWSCognitoAuthSignInOptions` configured with `AuthFlowType.CUSTOM_AUTH_WITH_SRP` OR `AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP`. + +Follow the instructions in [Custom Auth Sign In](/gen1/[platform]/build-a-backend/auth/sign-in-custom-flow/) to learn about how to integrate custom authentication flow in your application with the Auth APIs. + + + + +For more information about working with Lambda Triggers for custom authentication challenges, please visit [Amazon Cognito Developer Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). + + + +--- + +--- +title: "Sign-out" +section: "build-a-backend/auth/connect-your-frontend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-21T15:34:09.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-out/" +--- + +Amplify provides a client library that enables you to interact with backend resources such as Amplify Auth. + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/swift/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/flutter/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + + +> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/android/connected-components/authenticator), which provides a customizable UI and complete authentication flows. + + +To sign a user out of your application use the `signOut` API. + + +```ts +import { signOut } from 'aws-amplify/auth'; + +await signOut(); +``` + + +```dart +Future signOutCurrentUser() async { + final result = await Amplify.Auth.signOut(); + if (result is CognitoCompleteSignOut) { + safePrint('Sign out completed successfully'); + } else if (result is CognitoFailedSignOut) { + safePrint('Error signing user out: ${result.exception.message}'); + } +} +``` + + + +#### [Java] + +```java +Amplify.Auth.signOut( signOutResult -> { + if (signOutResult instanceof AWSCognitoAuthSignOutResult.CompleteSignOut) { + // Sign Out completed fully and without errors. + Log.i("AuthQuickStart", "Signed out successfully"); + } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.PartialSignOut) { + // Sign Out completed with some errors. User is signed out of the device. + AWSCognitoAuthSignOutResult.PartialSignOut partialSignOutResult = + (AWSCognitoAuthSignOutResult.PartialSignOut) signOutResult; + + HostedUIError hostedUIError = partialSignOutResult.getHostedUIError(); + if (hostedUIError != null) { + Log.e("AuthQuickStart", "HostedUI Error", hostedUIError.getException()); + // Optional: Re-launch hostedUIError.getUrl() in a Custom tab to clear Cognito web session. + } + + GlobalSignOutError globalSignOutError = partialSignOutResult.getGlobalSignOutError(); + if (globalSignOutError != null) { + Log.e("AuthQuickStart", "GlobalSignOut Error", globalSignOutError.getException()); + // Optional: Use escape hatch to retry revocation of globalSignOutError.getAccessToken(). + } + + RevokeTokenError revokeTokenError = partialSignOutResult.getRevokeTokenError(); + if (revokeTokenError != null) { + Log.e("AuthQuickStart", "RevokeToken Error", revokeTokenError.getException()); + // Optional: Use escape hatch to retry revocation of revokeTokenError.getRefreshToken(). + } + } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.FailedSignOut) { + AWSCognitoAuthSignOutResult.FailedSignOut failedSignOutResult = + (AWSCognitoAuthSignOutResult.FailedSignOut) signOutResult; + // Sign Out failed with an exception, leaving the user signed in. + Log.e("AuthQuickStart", "Sign out Failed", failedSignOutResult.getException()); + } +}); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.signOut { signOutResult -> + when(signOutResult) { + is AWSCognitoAuthSignOutResult.CompleteSignOut -> { + // Sign Out completed fully and without errors. + Log.i("AuthQuickStart", "Signed out successfully") + } + is AWSCognitoAuthSignOutResult.PartialSignOut -> { + // Sign Out completed with some errors. User is signed out of the device. + signOutResult.hostedUIError?.let { + Log.e("AuthQuickStart", "HostedUI Error", it.exception) + // Optional: Re-launch it.url in a Custom tab to clear Cognito web session. + + } + signOutResult.globalSignOutError?.let { + Log.e("AuthQuickStart", "GlobalSignOut Error", it.exception) + // Optional: Use escape hatch to retry revocation of it.accessToken. + } + signOutResult.revokeTokenError?.let { + Log.e("AuthQuickStart", "RevokeToken Error", it.exception) + // Optional: Use escape hatch to retry revocation of it.refreshToken. + } + } + is AWSCognitoAuthSignOutResult.FailedSignOut -> { + // Sign Out failed with an exception, leaving the user signed in. + Log.e("AuthQuickStart", "Sign out Failed", signOutResult.exception) + } + } +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +val signOutResult = Amplify.Auth.signOut() +when(signOutResult) { + is AWSCognitoAuthSignOutResult.CompleteSignOut -> { + // Sign Out completed fully and without errors. + Log.i("AuthQuickStart", "Signed out successfully") + } + is AWSCognitoAuthSignOutResult.PartialSignOut -> { + // Sign Out completed with some errors. User is signed out of the device. + signOutResult.hostedUIError?.let { + Log.e("AuthQuickStart", "HostedUI Error", it.exception) + // Optional: Re-launch it.url in a Custom tab to clear Cognito web session. + + } + signOutResult.globalSignOutError?.let { + Log.e("AuthQuickStart", "GlobalSignOut Error", it.exception) + // Optional: Use escape hatch to retry revocation of it.accessToken. + } + signOutResult.revokeTokenError?.let { + Log.e("AuthQuickStart", "RevokeToken Error", it.exception) + // Optional: Use escape hatch to retry revocation of it.refreshToken. + } + } + is AWSCognitoAuthSignOutResult.FailedSignOut -> { + // Sign Out failed with an exception, leaving the user signed in. + Log.e("AuthQuickStart", "Sign out Failed", signOutResult.exception) + } +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.signOut() + .subscribe(signOutResult -> { + if (signOutResult instanceof AWSCognitoAuthSignOutResult.CompleteSignOut) { + // Sign Out completed fully and without errors. + Log.i("AuthQuickStart", "Signed out successfully"); + } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.PartialSignOut) { + // Sign Out completed with some errors. User is signed out of the device. + AWSCognitoAuthSignOutResult.PartialSignOut partialSignOutResult = + (AWSCognitoAuthSignOutResult.PartialSignOut) signOutResult; + + HostedUIError hostedUIError = partialSignOutResult.getHostedUIError(); + if (hostedUIError != null) { + Log.e("AuthQuickStart", "HostedUI Error", hostedUIError.getException()); + // Optional: Re-launch hostedUIError.getUrl() in a Custom tab to clear Cognito web session. + } + + GlobalSignOutError globalSignOutError = partialSignOutResult.getGlobalSignOutError(); + if (globalSignOutError != null) { + Log.e("AuthQuickStart", "GlobalSignOut Error", globalSignOutError.getException()); + // Optional: Use escape hatch to retry revocation of globalSignOutError.getAccessToken(). + } + + RevokeTokenError revokeTokenError = partialSignOutResult.getRevokeTokenError(); + if (revokeTokenError != null) { + Log.e("AuthQuickStart", "RevokeToken Error", revokeTokenError.getException()); + // Optional: Use escape hatch to retry revocation of revokeTokenError.getRefreshToken(). + } + } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.FailedSignOut) { + AWSCognitoAuthSignOutResult.FailedSignOut failedSignOutResult = + (AWSCognitoAuthSignOutResult.FailedSignOut) signOutResult; + // Sign Out failed with an exception, leaving the user signed in. + Log.e("AuthQuickStart", "Sign out Failed", failedSignOutResult.getException()); + } + }); +``` + + + + +#### [Async/Await] + +```swift +func signOutLocally() async { + let result = await Amplify.Auth.signOut() + guard let signOutResult = result as? AWSCognitoSignOutResult + else { + print("Signout failed") + return + } + + print("Local signout successful: \(signOutResult.signedOutLocally)") + switch signOutResult { + case .complete: + // Sign Out completed fully and without errors. + print("Signed out successfully") + + case let .partial(revokeTokenError, globalSignOutError, hostedUIError): + // Sign Out completed with some errors. User is signed out of the device. + + if let hostedUIError = hostedUIError { + print("HostedUI error \(String(describing: hostedUIError))") + } + + if let globalSignOutError = globalSignOutError { + // Optional: Use escape hatch to retry revocation of globalSignOutError.accessToken. + print("GlobalSignOut error \(String(describing: globalSignOutError))") + } + + if let revokeTokenError = revokeTokenError { + // Optional: Use escape hatch to retry revocation of revokeTokenError.accessToken. + print("Revoke token error \(String(describing: revokeTokenError))") + } + + case .failed(let error): + // Sign Out failed with an exception, leaving the user signed in. + print("SignOut failed with \(error)") + } +} +``` + +#### [Combine] + +```swift +func signOutLocally() -> AnyCancellable { + Amplify.Publisher.create { + await Amplify.Auth.signOut() + }.sink(receiveValue: { result in + guard let signOutResult = result as? AWSCognitoSignOutResult + else { + print("Signout failed") + return + } + print("Local signout successful: \(signOutResult.signedOutLocally)") + switch signOutResult { + case .complete: + // Sign Out completed fully and without errors. + print("Signed out successfully") + + case let .partial(revokeTokenError, globalSignOutError, hostedUIError): + // Sign Out completed with some errors. User is signed out of the device. + if let hostedUIError = hostedUIError { + print("HostedUI error \(String(describing: hostedUIError))") + } + + if let globalSignOutError = globalSignOutError { + // Optional: Use escape hatch to retry revocation of globalSignOutError.accessToken. + print("GlobalSignOut error \(String(describing: globalSignOutError))") + } + + if let revokeTokenError = revokeTokenError { + // Optional: Use escape hatch to retry revocation of revokeTokenError.accessToken. + print("Revoke token error \(String(describing: revokeTokenError))") + } + + case .failed(let error): + // Sign Out failed with an exception, leaving the user signed in. + print("SignOut failed with \(error)") + } + }) +} +``` + + + +You can also sign out users from all devices by performing a global sign-out. This will also invalidate all refresh tokens issued to a user. The user's current access and ID tokens will remain valid on other devices until the refresh token expires (access and ID tokens expire one hour after they are issued). + + +```ts +import { signOut } from 'aws-amplify/auth'; + +await signOut({ global: true }); +``` + + +```dart +Future signOutGlobally() async { + final result = await Amplify.Auth.signOut( + options: const SignOutOptions(globalSignOut: true), + ); + if (result is CognitoCompleteSignOut) { + safePrint('Sign out completed successfully'); + } else if (result is CognitoPartialSignOut) { + final globalSignOutException = result.globalSignOutException!; + final accessToken = globalSignOutException.accessToken; + // Retry the global sign out using the access token, if desired + // ... + safePrint('Error signing user out: ${globalSignOutException.message}'); + } else if (result is CognitoFailedSignOut) { + safePrint('Error signing user out: ${result.exception.message}'); + } +} +``` + + + +#### [Java] + +```java +AuthSignOutOptions options = AuthSignOutOptions.builder() + .globalSignOut(true) + .build(); + +Amplify.Auth.signOut(options, signOutResult -> { + if (signOutResult instanceof AWSCognitoAuthSignOutResult.CompleteSignOut) { + // handle successful sign out + } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.PartialSignOut) { + // handle partial sign out + } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.FailedSignOut) { + // handle failed sign out + } +}); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val options = AuthSignOutOptions.builder() + .globalSignOut(true) + .build() + +Amplify.Auth.signOut(options) { signOutResult -> + when(signOutResult) { + is AWSCognitoAuthSignOutResult.CompleteSignOut -> { + // handle successful sign out + } + is AWSCognitoAuthSignOutResult.PartialSignOut -> { + // handle partial sign out + } + is AWSCognitoAuthSignOutResult.FailedSignOut -> { + // handle failed sign out + } + } +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +val options = AuthSignOutOptions.builder() + .globalSignOut(true) + .build() + +val signOutResult = Amplify.Auth.signOut(options) + +when(signOutResult) { + is AWSCognitoAuthSignOutResult.CompleteSignOut -> { + // handle successful sign out + } + is AWSCognitoAuthSignOutResult.PartialSignOut -> { + // handle partial sign out + } + is AWSCognitoAuthSignOutResult.FailedSignOut -> { + // handle failed sign out + } +} +``` + +#### [RxJava] + +```java +AuthSignOutOptions options = AuthSignOutOptions.builder() + .globalSignOut(true) + .build(); + +RxAmplify.Auth.signOut(options) + .subscribe(signOutResult -> { + if (signOutResult instanceof AWSCognitoAuthSignOutResult.CompleteSignOut) { + // handle successful sign out + } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.PartialSignOut) { + // handle partial sign out + } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.FailedSignOut) { + // handle failed sign out + } + }); +``` + + + + +#### [Async/Await] + +```swift +import AWSCognitoAuthPlugin + +func signOutGlobally() async { + let result = await Amplify.Auth.signOut(options: .init(globalSignOut: true)) + guard let signOutResult = result as? AWSCognitoSignOutResult + else { + print("Signout failed") + return + } + + print("Local signout successful: \(signOutResult.signedOutLocally)") + switch signOutResult { + case .complete: + // handle successful sign out + case .failed(let error): + // handle failed sign out + case let .partial(revokeTokenError, globalSignOutError, hostedUIError): + // handle partial sign out + } +} +``` + +#### [Combine] + +```swift +func signOutGlobally() -> AnyCancellable { + Amplify.Publisher.create { + await Amplify.Auth.signOut(options: .init(globalSignOut: true)) + }.sink(receiveValue: { result in + guard let signOutResult = result as? AWSCognitoSignOutResult + else { + print("Signout failed") + return + } + print("Local signout successful: \(signOutResult.signedOutLocally)") + switch signOutResult { + case .complete: + // handle successful sign out + case .failed(let error): + // handle failed sign out + case let .partial(revokeTokenError, globalSignOutError, hostedUIError): + // handle partial sign out + } + }) +} +``` + + + + +## Practical Example + + +```tsx title="src/App.tsx" +import { Amplify } from "aws-amplify" +// highlight-next-line +import { signOut } from "aws-amplify/auth" +import outputs from "../amplify_outputs.json" + +Amplify.configure(outputs) + +export default function App() { + async function handleSignOut() { + // highlight-next-line + await signOut() + } + + return ( + + ) +} +``` + + + +--- + +--- +title: "Manage user sessions" +section: "build-a-backend/auth/connect-your-frontend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-10-25T16:39:44.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/manage-user-sessions/" +--- + +Amplify Auth provides access to current user sessions and tokens to help you retrieve your user's information to determine if they are signed in with a valid session and control their access to your app. + + +## Retrieve your current authenticated user + +You can use the `getCurrentUser` API to get information about the currently authenticated user including the `username`, `userId` and `signInDetails`. + +```ts +import { getCurrentUser } from 'aws-amplify/auth'; + +const { username, userId, signInDetails } = await getCurrentUser(); + +console.log("username", username); +console.log("user id", userId); +console.log("sign-in details", signInDetails); +``` + +This method can be used to check if a user is signed in. It throws an error if the user is not authenticated. + +> **Info:** The user's `signInDetails` are not supported when using the `Hosted UI` or the `signInWithRedirect` API. + +## Retrieve a user session + +Your user's session is their signed-in state, which grants them access to your app. When your users sign in, their credentials are exchanged for temporary access tokens. You can get session details to access these tokens and use this information to validate user access or perform actions unique to that user. + +If you only need the session details, you can use the `fetchAuthSession` API which returns a `tokens` object containing the JSON Web Tokens (JWT). + +```ts +import { fetchAuthSession } from 'aws-amplify/auth'; + +const session = await fetchAuthSession(); + +console.log("id token", session.tokens.idToken) +console.log("access token", session.tokens.accessToken) +``` + +### Refreshing sessions + +The `fetchAuthSession` API automatically refreshes the user's session when the authentication tokens have expired and a valid `refreshToken` is present. Additionally, you can also refresh the session explicitly by calling the `fetchAuthSession` API with the `forceRefresh` flag enabled. + +```ts +import { fetchAuthSession } from 'aws-amplify/auth'; + +await fetchAuthSession({ forceRefresh: true }); +``` + +> **Warning:** **Warning:** by default, sessions from external identity providers cannot be refreshed. + + +An intentional decision with Amplify Auth was to avoid any public methods exposing credentials or manipulating them. + +With Auth, you simply sign in and it handles everything else needed to keep the credentials up to date and vend them to the other categories. + +However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation details by calling `fetchAuthSession` on the Cognito Auth Plugin. This will return a `CognitoAuthSession`, which has additional attributes compared to `AuthSession`, which is typically returned by `fetchAuthSession`. See the example below: + +```dart +Future fetchAuthSession() async { + try { + final result = await Amplify.Auth.fetchAuthSession(); + safePrint('User is signed in: ${result.isSignedIn}'); + } on AuthException catch (e) { + safePrint('Error retrieving auth session: ${e.message}'); + } +} +``` + +## Retrieving AWS credentials + +Sometimes it can be helpful to retrieve the instance of the underlying plugin which has more specific typing. In case of Cognito, calling `fetchAuthSession` on the Cognito plugin returns AWS-specific values such as the identity ID, AWS credentials, and Cognito User Pool tokens. + +```dart +Future fetchCognitoAuthSession() async { + try { + final cognitoPlugin = Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); + final result = await cognitoPlugin.fetchAuthSession(); + final identityId = result.identityIdResult.value; + safePrint("Current user's identity ID: $identityId"); + } on AuthException catch (e) { + safePrint('Error retrieving auth session: ${e.message}'); + } +} +``` + + +An intentional decision with Amplify Auth was to avoid any public methods exposing credentials or manipulating them. + +With Auth, you simply sign in and it handles everything else needed to keep the credentials up to date and vend them to the other categories. + +However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation details by casting the result of `fetchAuthSession` as follows: + +#### [Java] + +```java +Amplify.Auth.fetchAuthSession( + result -> { + AWSCognitoAuthSession cognitoAuthSession = (AWSCognitoAuthSession) result; + switch(cognitoAuthSession.getIdentityIdResult().getType()) { + case SUCCESS: + Log.i("AuthQuickStart", "IdentityId: " + cognitoAuthSession.getIdentityIdResult().getValue()); + break; + case FAILURE: + Log.i("AuthQuickStart", "IdentityId not present because: " + cognitoAuthSession.getIdentityIdResult().getError().toString()); + } + }, + error -> Log.e("AuthQuickStart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.fetchAuthSession( + { + val session = it as AWSCognitoAuthSession + when (session.identityIdResult.type) { + AuthSessionResult.Type.SUCCESS -> + Log.i("AuthQuickStart", "IdentityId = ${session.identityIdResult.value}") + AuthSessionResult.Type.FAILURE -> + Log.w("AuthQuickStart", "IdentityId not found", session.identityIdResult.error) + } + }, + { Log.e("AuthQuickStart", "Failed to fetch session", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val session = Amplify.Auth.fetchAuthSession() as AWSCognitoAuthSession + val id = session.identityIdResult + if (id.type == AuthSessionResult.Type.SUCCESS) { + Log.i("AuthQuickStart", "IdentityId: ${id.value}") + } else if (id.type == AuthSessionResult.Type.FAILURE) { + Log.i("AuthQuickStart", "IdentityId not present: ${id.error}") + } +} catch (error: AuthException) { + Log.e("AuthQuickStart", "Failed to fetch session", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.fetchAuthSession() + .subscribe( + result -> { + AWSCognitoAuthSession cognitoAuthSession = (AWSCognitoAuthSession) result; + + switch (cognitoAuthSession.getIdentityIdResult().getType()) { + case SUCCESS: + Log.i("AuthQuickStart", "IdentityId: " + cognitoAuthSession.getIdentityIdResult().getValue()); + break; + case FAILURE: + Log.i("AuthQuickStart", "IdentityId not present because: " + cognitoAuthSession.getIdentityIdResult().getError().toString()); + } + }, + error -> Log.e("AuthQuickStart", error.toString()) + ); +``` + +## Force refreshing session + +Through the plugin, you can force refresh the internal session by setting the `forceRefresh` option when calling the fetchAuthSession API. + +#### [Java] + +```java +AuthFetchSessionOptions options = AuthFetchSessionOptions.builder().forceRefresh(true).build(); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val option = AuthFetchSessionOptions.builder().forceRefresh(true).build() +``` + +#### [Kotlin - Coroutines] + +```kotlin +val option = AuthFetchSessionOptions.builder().forceRefresh(true).build() +``` + +#### [RxJava] + +```java +AuthFetchSessionOptions options = AuthFetchSessionOptions.builder().forceRefresh(true).build(); +``` + + + +An intentional decision with Amplify Auth was to avoid any public methods exposing credentials or manipulating them. + +With Auth, you simply sign in and it handles everything else needed to keep the credentials up to date and vend them to the other categories. + +However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation details by casting the result of `fetchAuthSession` as follows: + +```swift +import AWSPluginsCore + +do { + let session = try await Amplify.Auth.fetchAuthSession() + + // Get user sub or identity id + if let identityProvider = session as? AuthCognitoIdentityProvider { + let usersub = try identityProvider.getUserSub().get() + let identityId = try identityProvider.getIdentityId().get() + print("User sub - \(usersub) and identity id \(identityId)") + } + + // Get AWS credentials + if let awsCredentialsProvider = session as? AuthAWSCredentialsProvider { + let credentials = try awsCredentialsProvider.getAWSCredentials().get() + // Do something with the credentials + } + + // Get cognito user pool token + if let cognitoTokenProvider = session as? AuthCognitoTokensProvider { + let tokens = try cognitoTokenProvider.getCognitoTokens().get() + // Do something with the JWT tokens + } +} catch let error as AuthError { + print("Fetch auth session failed with error - \(error)") +} catch { +} +``` +If you have enabled guest user in Cognito Identity Pool and no user is signed in, you will be able to access only identityId and AWS credentials. All other session details will give you an error. + +```swift +import AWSPluginsCore + +do { + let session = try await Amplify.Auth.fetchAuthSession() + + // Get identity id + if let identityProvider = session as? AuthCognitoIdentityProvider { + let identityId = try identityProvider.getIdentityId().get() + print("Identity id \(identityId)") + } + + // Get AWS credentials + if let awsCredentialsProvider = session as? AuthAWSCredentialsProvider { + let credentials = try awsCredentialsProvider.getAWSCredentials().get() + // Do something with the credentials + } +} catch let error as AuthError { + print("Fetch auth session failed with error - \(error)") +} catch { + print("Unexpected error: \(error)") +} +``` + +## Force refreshing session + +Through the plugin, you can force refresh the internal session by passing an api options `forceRefresh` while calling the fetchAuthSession api. + +```swift +Amplify.Auth.fetchAuthSession(options: .forceRefresh()) + +``` + + +--- + +--- +title: "Manage user attributes" +section: "build-a-backend/auth/connect-your-frontend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-11-14T19:12:03.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/manage-user-attributes/" +--- + +User attributes such as email address, phone number help you identify individual users. Defining the user attributes you include for your user profiles makes user data easy to manage at scale. This information will help you personalize user journeys, tailor content, provide intuitive account control, and more. You can capture information upfront during sign-up or enable customers to update their profile after sign-up. In this section we take a closer look at working with user attributes, how to set them up and manage them. + + +## Pass user attributes during sign-up + +You can create user attributes during sign-up or when the user is authenticated. To do this as part of sign-up you can pass them in the `userAttributes` object of the `signUp` API: + +```ts +import { signUp } from "aws-amplify/auth"; + +await signUp({ + username: "jdoe", + password: "mysecurerandompassword#123", + options: { + userAttributes: { + email: "me@domain.com", + phone_number: "+12128601234", // E.164 number convention + given_name: "Jane", + family_name: "Doe", + nickname: "Jane", + }, + }, +}); +``` + + + +## Configure custom user attributes during sign-up + +Custom attributes can be passed in with the `userAttributes` option of the `signUp` API: + + +```ts +import { signUp } from "aws-amplify/auth"; + +await signUp({ + username: 'john.doe@example.com', + password: 'hunter2', + options: { + userAttributes: { + 'custom:display_name': 'john_doe123', + } + } +}); +``` + + +```dart +Future _signUp({ + required String username, + required String password, + required String email, + required String customValue, +}) async { + final userAttributes = { + AuthUserAttributeKey.email: email, + // Create and pass a custom attribute + const CognitoUserAttributeKey.custom('my-custom-attribute'): customValue + }; + await Amplify.Auth.signUp( + username: username, + password: password, + options: SignUpOptions( + userAttributes: userAttributes, + ), + ); +} +``` + + +```swift +func signUp(username: String, password: String, email: String) async { + do { + let signUpResult = try await Amplify.Auth.signUp( + username: username, + password: password, + options: .init(userAttributes: [ + AuthUserAttribute(.email, value: email), + AuthUserAttribute(.custom("my-custom-attribute"), value: ) + ]) + ) + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } catch let error as AuthError { + print("An error occurred while registering a user \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + + + +## Retrieve user attributes + +You can retrieve user attributes for your users to read in their profile using the `fetchUserAttributes` API. This helps you personalize their frontend experience as well as control what they will see. + + +```ts +import { fetchUserAttributes } from 'aws-amplify/auth'; + +await fetchUserAttributes(); +``` + + + +#### [Async/Await] + +```swift +func fetchAttributes() async { + do { + let attributes = try await Amplify.Auth.fetchUserAttributes() + print("User attributes - \(attributes)") + } catch let error as AuthError{ + print("Fetching user attributes failed with error \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func fetchAttributes() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.fetchUserAttributes() + }.sink { + if case let .failure(authError) = $0 { + print("Fetch user attributes failed with error \(authError)") + } + } + receiveValue: { attributes in + print("User attributes - \(attributes)") + } +} +``` + + + + +#### [Java] + +```java +Amplify.Auth.fetchUserAttributes( + attributes -> Log.i("AuthDemo", "User attributes = " + attributes.toString()), + error -> Log.e("AuthDemo", "Failed to fetch user attributes.", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.fetchUserAttributes( + { Log.i("AuthDemo", "User attributes = $it") }, + { Log.e("AuthDemo", "Failed to fetch user attributes", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val attributes = Amplify.Auth.fetchUserAttributes() + Log.i("AuthDemo", "User attributes = $attributes") +} catch (error: AuthException) { + Log.e("AuthDemo", "Failed to fetch user attributes", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.fetchUserAttributes() + .doOnSubscribe(() -> Log.i("AuthDemo", "Attributes:")) + .flatMapObservable(Observable::fromIterable) + .subscribe( + eachAttribute -> Log.i("AuthDemo", eachAttribute.toString()), + error -> Log.e("AuthDemo", "Failed to fetch attributes.", error) + ); +``` + + + +```dart +Future fetchCurrentUserAttributes() async { + try { + final result = await Amplify.Auth.fetchUserAttributes(); + for (final element in result) { + safePrint('key: ${element.userAttributeKey}; value: ${element.value}'); + } + } on AuthException catch (e) { + safePrint('Error fetching user attributes: ${e.message}'); + } +} +``` + + +## Update user attribute + +You can use the `updateUserAttribute` API to create or update existing user attributes. + + + +#### [TypeScript] + +```typescript +import { + updateUserAttribute, + type UpdateUserAttributeOutput +} from 'aws-amplify/auth'; + +async function handleUpdateUserAttribute(attributeKey: string, value: string) { + try { + const output = await updateUserAttribute({ + userAttribute: { + attributeKey, + value + } + }); + handleUpdateUserAttributeNextSteps(output); + } catch (error) { + console.log(error); + } +} + +function handleUpdateUserAttributeNextSteps(output: UpdateUserAttributeOutput) { + const { nextStep } = output; + + switch (nextStep.updateAttributeStep) { + case 'CONFIRM_ATTRIBUTE_WITH_CODE': + const codeDeliveryDetails = nextStep.codeDeliveryDetails; + console.log( + `Confirmation code was sent to ${codeDeliveryDetails?.deliveryMedium}.` + ); + // Collect the confirmation code from the user and pass to confirmUserAttribute. + break; + case 'DONE': + console.log(`attribute was successfully updated.`); + break; + } +} +``` + +#### [JavaScript] + +```javascript +import { updateUserAttribute } from 'aws-amplify/auth'; + +async function handleUpdateUserAttribute(attributeKey, value) { + try { + const output = await updateUserAttribute({ + userAttribute: { + attributeKey, + value + } + }); + handleUpdateUserAttributeNextSteps(output); + } catch (error) { + console.log(error); + } +} + +function handleUpdateUserAttributeNextSteps(output) { + const { nextStep } = output; + + switch (nextStep.updateAttributeStep) { + case 'CONFIRM_ATTRIBUTE_WITH_CODE': + const codeDeliveryDetails = nextStep.codeDeliveryDetails; + console.log( + `Confirmation code was sent to ${codeDeliveryDetails?.deliveryMedium}.` + ); + // Collect the confirmation code from the user and pass to confirmUserAttribute. + break; + case 'DONE': + console.log(`attribute was successfully updated.`); + break; + } +} +``` + + + Note: If you change an attribute that requires confirmation (i.e. email or phone_number), the user will receive a confirmation code either to their email or cellphone. This code can be used with the confirmUserAttribute API to confirm the change. + + + + + +#### [Async/Await] + +```swift +func updateAttribute() async { + do { + let updateResult = try await Amplify.Auth.update( + userAttribute: AuthUserAttribute(.phoneNumber, value: "+2223334444") + ) + + switch updateResult.nextStep { + case .confirmAttributeWithCode(let deliveryDetails, let info): + print("Confirm the attribute with details send to - \(deliveryDetails) \(String(describing: info))") + case .done: + print("Update completed") + } + } catch let error as AuthError { + print("Update attribute failed with error \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func updateAttribute() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.update( + userAttribute: AuthUserAttribute(.phoneNumber, value: "+2223334444") + ) + }.sink { + if case let .failure(authError) = $0 { + print("Update attribute failed with error \(authError)") + } + } + receiveValue: { updateResult in + switch updateResult.nextStep { + case .confirmAttributeWithCode(let deliveryDetails, let info): + print("Confirm the attribute with details send to - \(deliveryDetails) \(info)") + case .done: + print("Update completed") + } + } +} +``` + + + + + +#### [Java] + +```java +AuthUserAttribute userEmail = + new AuthUserAttribute(AuthUserAttributeKey.email(), "email@email.com"); +Amplify.Auth.updateUserAttribute(userEmail, + result -> Log.i("AuthDemo", "Updated user attribute = " + result.toString()), + error -> Log.e("AuthDemo", "Failed to update user attribute.", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.updateUserAttribute( + AuthUserAttribute(AuthUserAttributeKey.email(), "email@email.com"), + { Log.i("AuthDemo", "Updated user attribute = $it") }, + { Log.e("AuthDemo", "Failed to update user attribute.", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val attribute = + AuthUserAttribute(AuthUserAttributeKey.email(), "email@email.com") +try { + val result = Amplify.Auth.updateUserAttribute(attribute) + Log.i("AuthDemo", "Updated user attribute = $result") +} catch (error: AuthException) { + Log.e("AuthDemo", "Failed to update user attribute.", error) +} +``` + +#### [RxJava] + +```java +AuthUserAttribute userEmail = + new AuthUserAttribute(AuthUserAttributeKey.email(), "email@email.com"); +RxAmplify.Auth.updateUserAttribute(userEmail) + .subscribe( + result -> Log.i("AuthDemo", "Updated user attribute = " + result.toString()), + error -> Log.e("AuthDemo", "Failed to update user attribute.", error) + ); +``` + + + + +```dart +Future updateUserEmail({ + required String newEmail, +}) async { + try { + final result = await Amplify.Auth.updateUserAttribute( + userAttributeKey: AuthUserAttributeKey.email, + value: newEmail, + ); + _handleUpdateUserAttributeResult(result); + } on AuthException catch (e) { + safePrint('Error updating user attribute: ${e.message}'); + } +} +``` + +User attribute updates may require additional verification before they're complete. Check the +`UpdateUserAttributeResult` returned from `Amplify.Auth.updateUserAttribute` to see which next +step, if any, is required. When the update is complete, the next step will be `done`. + +```dart +void _handleUpdateUserAttributeResult( + UpdateUserAttributeResult result, +) { + switch (result.nextStep.updateAttributeStep) { + case AuthUpdateAttributeStep.confirmAttributeWithCode: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + break; + case AuthUpdateAttributeStep.done: + safePrint('Successfully updated attribute'); + break; + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` + +To update multiple user attributes at a time, call `updateUserAttributes`: + +```dart +Future updateUserAttributes() async { + const attributes = [ + AuthUserAttribute( + userAttributeKey: AuthUserAttributeKey.email, + value: 'email@email.com', + ), + AuthUserAttribute( + userAttributeKey: AuthUserAttributeKey.familyName, + value: 'MyFamilyName', + ), + ]; + try { + final result = await Amplify.Auth.updateUserAttributes( + attributes: attributes, + ); + result.forEach((key, value) { + switch (value.nextStep.updateAttributeStep) { + case AuthUpdateAttributeStep.confirmAttributeWithCode: + final destination = value.nextStep.codeDeliveryDetails?.destination; + safePrint('Confirmation code sent to $destination for $key'); + break; + case AuthUpdateAttributeStep.done: + safePrint('Update completed for $key'); + break; + } + }); + } on AuthException catch (e) { + safePrint('Error updating user attributes: ${e.message}'); + } +} +``` + + + +## Update user attributes + +You can use the `updateUserAttributes` API to create or update multiple existing user attributes. + + +```typescript +import { updateUserAttributes, type UpdateUserAttributesOutput } from "aws-amplify/auth"; + +await updateUserAttributes({ + userAttributes: { + email: "me@domain.com", + name: "Jon Doe", + }, +}); +``` + + + + +#### [Java] + +```java +Amplify.Auth.updateUserAttributes( + attributes, // attributes is a list of AuthUserAttribute + result -> Log.i("AuthDemo", "Updated user attributes = " + result.toString()), + error -> Log.e("AuthDemo", "Failed to update user attributes.", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.updateUserAttributes( + attributes, // attributes is a list of AuthUserAttribute + { Log.i("AuthDemo", "Updated user attributes = $it") }, + { Log.e("AuthDemo", "Failed to update user attributes", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.updateUserAttributes(attributes) + Log.i("AuthDemo", "Updated user attributes = $result") +} catch (error: AuthException) { + Log.e("AuthDemo", "Failed to update user attributes", error) +} +``` + +#### [RxJava] + +```java +// attributes is a list of AuthUserAttribute +RxAmplify.Auth.updateUserAttributes(attributes) + .subscribe( + result -> Log.i("AuthDemo", "Updated user attributes = " + result.toString()), + error -> Log.e("AuthDemo", "Failed to update user attributes.", error) + ); +``` + + + + +## Verify user attribute + + +Some attributes require confirmation for the attribute update to complete. If the attribute needs to be confirmed, part of the result of the `updateUserAttribute` or `updateUserAttributes` APIs will be `CONFIRM_ATTRIBUTE_WITH_CODE`. A confirmation code will be sent to the delivery medium mentioned in the delivery details. When the user gets the confirmation code, you can present a UI to the user to enter the code and invoke the `confirmUserAttribute` API with their input: + + + +Some attributes require confirmation for the attribute update to complete. If the attribute needs to be confirmed, part of the result of the `updateUserAttribute` or `updateUserAttributes` APIs will be `confirmAttributeWithCode`. A confirmation code will be sent to the delivery medium mentioned in the delivery details. When the user gets the confirmation code, you can present a UI to the user to enter the code and invoke the `confirmUserAttribute` API with their input: + + + +```typescript +import { + confirmUserAttribute, + type ConfirmUserAttributeInput +} from 'aws-amplify/auth'; + +async function handleConfirmUserAttribute({ + userAttributeKey, + confirmationCode +}: ConfirmUserAttributeInput) { + try { + await confirmUserAttribute({ userAttributeKey, confirmationCode }); + } catch (error) { + console.log(error); + } +} +``` + + + + +#### [Async/Await] + +```swift +func confirmAttribute() async { + do { + try await Amplify.Auth.confirm(userAttribute: .email, confirmationCode: "390739") + print("Attribute verified") + } catch let error as AuthError { + print("Update attribute failed with error \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmAttribute() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirm(userAttribute: .email, confirmationCode: "390739") + }.sink { + if case let .failure(authError) = $0 { + print("Update attribute failed with error \(authError)") + } + } + receiveValue: { _ in + print("Attribute verified") + } +} +``` + + + + + +#### [Java] + +```java +Amplify.Auth.confirmUserAttribute(AuthUserAttributeKey.email(), "344299", + () -> Log.i("AuthDemo", "Confirmed user attribute with correct code."), + error -> Log.e("AuthDemo", "Failed to confirm user attribute. Bad code?", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.confirmUserAttribute(AuthUserAttributeKey.email(), "344299", + { Log.i("AuthDemo", "Confirmed user attribute with correct code.") }, + { Log.e("AuthDemo", "Failed to confirm user attribute. Bad code?", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + Amplify.Auth.confirmUserAttribute(AuthUserAttributeKey.email(), "344299") + Log.i("AuthDemo", "Confirmed user attribute with correct code.") +} catch (error: AuthException) { + Log.e("AuthDemo", "Failed to confirm user attribute. Bade code?", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.confirmUserAttribute(AuthUserAttributeKey.email(), "344299") + .subscribe( + () -> Log.i("AuthDemo", "Confirmed user attribute using correct code."), + error -> Log.e("AuthDemo", "Failed to confirm user attribute. Bad code?", error) + ); +``` + + + + +```dart +Future verifyAttributeUpdate() async { + try { + await Amplify.Auth.confirmUserAttribute( + userAttributeKey: AuthUserAttributeKey.email, + confirmationCode: '390739', + ); + } on AuthException catch (e) { + safePrint('Error confirming attribute update: ${e.message}'); + } +} +``` + + +## Send user attribute verification code + +If an attribute needs to be verified while the user is authenticated, invoke the `sendUserAttributeVerificationCode` API as shown below: + + +```ts +import { + sendUserAttributeVerificationCode, + type VerifiableUserAttributeKey +} from 'aws-amplify/auth'; + +async function handleSendUserAttributeVerificationCode( + key: VerifiableUserAttributeKey +) { + try { + await sendUserAttributeVerificationCode({ + userAttributeKey: key + }); + } catch (error) { + console.log(error); + } +} +``` + + + + +#### [Async/Await] + +```swift +func sendVerificationCode() async { + do { + let deliveryDetails = try await Amplify.Auth.sendVerificationCode(forUserAttributeKey: .email) + print("Resend code send to - \(deliveryDetails)") + } catch let error as AuthError { + print("Resend code failed with error \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func sendVerificationCode() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.sendVerificationCode(forUserAttributeKey: .email) + }.sink { + if case let .failure(authError) = $0 { + print("Resend code failed with error \(authError)") + } + } + receiveValue: { deliveryDetails in + print("Resend code sent to - \(deliveryDetails)") + } +} +``` + + + + + +#### [Java] + +```java +Amplify.Auth.resendUserAttributeConfirmationCode(AuthUserAttributeKey.email(), + result -> Log.i("AuthDemo", "Code was sent again: " + result.toString()), + error -> Log.e("AuthDemo", "Failed to resend code.", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.resendUserAttributeConfirmationCode( + AuthUserAttributeKey.email(), + { Log.i("AuthDemo", "Code was sent again: $it") }, + { Log.e("AuthDemo", "Failed to resend code", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val attr = AuthUserAttributeKey.email() + val result = Amplify.Auth.resendUserAttributeConfirmationCode(attr) + Log.i("AuthDemo", "Code was sent again: $result."), +} catch (error: AuthException) { + Log.e("AuthDemo", "Failed to resend code.", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.resendUserAttributeConfirmationCode(AuthUserAttributeKey.email()) + .subscribe( + result -> Log.i("AuthDemo", "Code was resent: " + result.toString()), + error -> Log.e("AuthDemo", "Failed to resend code.", error) + ); +``` + + + + +```dart +Future resendVerificationCode() async { + try { + final result = await Amplify.Auth.resendUserAttributeConfirmationCode( + userAttributeKey: AuthUserAttributeKey.email, + ); + _handleCodeDelivery(result.codeDeliveryDetails); + } on AuthException catch (e) { + safePrint('Error resending code: ${e.message}'); + } +} +``` + + + +## Delete user attributes + +The `deleteUserAttributes` API allows to delete one or more user attributes. + +```ts +import { + deleteUserAttributes, + type DeleteUserAttributesInput +} from 'aws-amplify/auth'; + +async function handleDeleteUserAttributes( + keys: DeleteUserAttributesInput['userAttributeKeys'] +) { + try { + await deleteUserAttributes({ + userAttributeKeys: ['custom:my_custom_attribute', ...keys] + }); + } catch (error) { + console.log(error); + } +} +``` + + +## Next Steps + +- [Learn how to set up password change and recovery](/[platform]/build-a-backend/auth/manage-users/manage-passwords/) +- [Learn how to set up custom attributes](/[platform]/build-a-backend/auth/concepts/user-attributes#custom-attributes) + +--- + +--- +title: "Listen to auth events" +section: "build-a-backend/auth/connect-your-frontend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-16T15:59:30.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/listen-to-auth-events/" +--- + +Amplify Auth emits events during authentication flows, which enables you to react to user flows in real time and trigger custom business logic. For example, you may want to capture data, synchronize your app's state, and personalize the user's experience. You can listen to and respond to events across the Auth lifecycle such as sign-in and sign-out. + + +## Expose hub events triggered in response to auth actions + +You can use Amplify Hub with its built in Amplify Auth events to subscribe a listener using a publish-subscribe pattern and capture events between different parts of your application. The Amplify Auth category publishes in the `auth` channel when auth events such as `signedIn` or `signedOut` happen independent from your app code. + +You can review the Amplify Hub guide to [learn more](/gen1/react/build-a-backend/utilities/hub/). + +> **Warning:** Channels are logical group names that help you organize dispatching and listening. However, some channels are protected and cannot be used to publish custom events, and `auth` is one of these channels. Sending unexpected payloads to protected channels can have undesirable side effects such as impacting authentication flows. See the [Amplify Hub](/gen1/react/build-a-backend/utilities/hub/) guide for more protected channels. + +Here is a basic example of setting up a listener that logs an event emitted through the `auth` channel: + +```js +import { Hub } from 'aws-amplify/utils'; + +Hub.listen('auth', (data) => { + console.log(data) +}); +``` + +Once your app is set up to subscribe and listen to specific event types from the `auth` channel, the listeners will be notified asynchronously when an event occurs. This pattern allows for a one-to-many relationship where one auth event can be shared with many different listeners that have been subscribed. This lets your app react based on the event rather than proactively poll for information. + +Additionally, you can set up your listener to extract data from the event payload and execute a callback that you define. For example, you might update UI elements in your app to reflect your user's authenticated state after the `signedIn` or `signedOut` events. + +### Listen to and log auth events + +One of the most common workflows will be to log events. In this example you can see how you can listen and target specific `auth` events using a `switch` to log your own messages. + +```js +import { Hub } from 'aws-amplify/utils'; + +Hub.listen('auth', ({ payload }) => { + switch (payload.event) { + case 'signedIn': + console.log('user have been signedIn successfully.'); + break; + case 'signedOut': + console.log('user have been signedOut successfully.'); + break; + case 'tokenRefresh': + console.log('auth tokens have been refreshed.'); + break; + case 'tokenRefresh_failure': + console.log('failure while refreshing auth tokens.'); + break; + case 'signInWithRedirect': + console.log('signInWithRedirect API has successfully been resolved.'); + break; + case 'signInWithRedirect_failure': + console.log('failure while trying to resolve signInWithRedirect API.'); + break; + case 'customOAuthState': + logger.info('custom state returned from CognitoHosted UI'); + break; + } +}); +``` + +### Stop listening to events + +You can also stop listening for messages by calling the result of the `Hub.listen()` function. This may be useful if you no longer need to receive messages in your application flow. This can also help you avoid any memory leaks on low powered devices when you are sending large amounts of data through Amplify Hub on multiple channels. + +To stop listening to a certain event, you need to wrap the listener function with a variable and call it once you no longer need it: + +```js +/* start listening for messages */ +const hubListenerCancelToken = Hub.listen('auth', (data) => { + console.log('Listening for all auth events: ', data.payload.data); +}); + +/* later */ +hubListenerCancelToken(); // stop listening for messages +``` + +You now have a few use cases and examples for listening to and responding to auth events. + + +AWS Cognito Auth Plugin sends important events through Amplify Hub. You can listen to these events like the following: + +```dart +final subscription = Amplify.Hub.listen(HubChannel.Auth, (AuthHubEvent event) { + switch (event.type) { + case AuthHubEventType.signedIn: + safePrint('User is signed in.'); + break; + case AuthHubEventType.signedOut: + safePrint('User is signed out.'); + break; + case AuthHubEventType.sessionExpired: + safePrint('The session has expired.'); + break; + case AuthHubEventType.userDeleted: + safePrint('The user has been deleted.'); + break; + } +}); +``` + + +AWS Cognito Auth Plugin sends important events through Amplify Hub. You can listen to these events like the following: + +#### [Java] + +```java +Amplify.Hub.subscribe(HubChannel.AUTH, + hubEvent -> { + if (hubEvent.getName().equals(InitializationStatus.SUCCEEDED.name())) { + Log.i("AuthQuickstart", "Auth successfully initialized"); + } else if (hubEvent.getName().equals(InitializationStatus.FAILED.name())){ + Log.i("AuthQuickstart", "Auth failed to succeed"); + } else { + String eventName = hubEvent.getName(); + if (eventName.equals(SIGNED_IN.name())) { + Log.i("AuthQuickstart", "Auth just became signed in."); + } + else if (eventName.equals(SIGNED_OUT.name())) { + Log.i("AuthQuickstart", "Auth just became signed out."); + } + else if (eventName.equals(SESSION_EXPIRED.name())) { + Log.i("AuthQuickstart", "Auth session just expired."); + } + else if (eventName.equals(USER_DELETED.name())) { + Log.i("AuthQuickstart", "User has been deleted."); + } + else { + Log.w("AuthQuickstart", "Unhandled Auth Event: " + eventName); + } + } + } +); + +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Hub.subscribe(HubChannel.AUTH) { event -> + when (event.name) { + InitializationStatus.SUCCEEDED.name -> + Log.i("AuthQuickstart", "Auth successfully initialized") + InitializationStatus.FAILED.name -> + Log.i("AuthQuickstart", "Auth failed to succeed") + else -> when (event.name) { + AuthChannelEventName.SIGNED_IN.name -> + Log.i("AuthQuickstart", "Auth just became signed in") + AuthChannelEventName.SIGNED_OUT.name -> + Log.i("AuthQuickstart", "Auth just became signed out") + AuthChannelEventName.SESSION_EXPIRED.name -> + Log.i("AuthQuickstart", "Auth session just expired") + AuthChannelEventName.USER_DELETED.name -> + Log.i("AuthQuickstart", "User has been deleted") + else -> + Log.w("AuthQuickstart", "Unhandled Auth Event: ${event.name}") + } + } +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +Amplify.Hub.subscribe(HubChannel.AUTH).collect { + when (it.name) { + InitializationStatus.SUCCEEDED.name -> + Log.i("AuthQuickstart", "Auth successfully initialized") + InitializationStatus.FAILED.name -> + Log.i("AuthQuickstart", "Auth failed to succeed") + else -> when (it.name) { + AuthChannelEventName.SIGNED_IN.name -> + Log.i("AuthQuickstart", "Auth just became signed in.") + AuthChannelEventName.SIGNED_OUT.name -> + Log.i("AuthQuickstart", "Auth just became signed out.") + AuthChannelEventName.SESSION_EXPIRED.name -> + Log.i("AuthQuickstart", "Auth session just expired.") + AuthChannelEventName.USER_DELETED.name -> + Log.i("AuthQuickstart", "User has been deleted.") + else -> + Log.w("AuthQuickstart", "Unhandled Auth Event: ${it.name}") + } + } +} +``` + +#### [RxJava] + +```java +RxAmplify.Hub.on(HubChannel.AUTH) + .map(HubEvent::getName) + .subscribe(name -> { + if (name.equals(InitializationStatus.SUCCEEDED.name())) { + Log.i("AuthQuickstart", "Auth successfully initialized"); + return; + } else if (name.equals(InitializationStatus.FAILED.name())) { + Log.i("AuthQuickstart", "Auth failed to succeed"); + return; + } else { + if (name.equals(SIGNED_IN.name())) { + Log.i("AuthQuickstart", "Auth just became signed in."); + } + else if (name.equals(SIGNED_OUT.name())) { + Log.i("AuthQuickstart", "Auth just became signed out."); + } + else if (name.equals(SESSION_EXPIRED.name())) { + Log.i("AuthQuickstart", "Auth session just expired."); + } + else if (name.equals(USER_DELETED.name())) { + Log.i("AuthQuickstart", "User has been deleted."); + } + else { + Log.w("AuthQuickstart", "Unhandled Auth Event: " + hubEvent.getName()); + } + } + }); +``` + + + +AWS Cognito Auth Plugin sends important events through Amplify Hub. You can listen to these events like the following: + +#### [Listener] + +```swift +override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + + // Assumes `unsubscribeToken` is declared as an instance variable in your view + unsubscribeToken = Amplify.Hub.listen(to: .auth) { payload in + switch payload.eventName { + case HubPayload.EventName.Auth.signedIn: + print("User signed in") + // Update UI + + case HubPayload.EventName.Auth.sessionExpired: + print("Session expired") + // Re-authenticate the user + + case HubPayload.EventName.Auth.signedOut: + print("User signed out") + // Update UI + + case HubPayload.EventName.Auth.userDeleted: + print("User deleted") + // Update UI + + default: + break + } + } +} +``` + +#### [Combine] + +```swift +override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + + // Assumes `sink` is declared as an instance variable in your view controller + sink = Amplify.Hub + .publisher(for: .auth) + .sink { payload in + switch payload.eventName { + case HubPayload.EventName.Auth.signedIn: + print("User signed in") + // Update UI + + case HubPayload.EventName.Auth.sessionExpired: + print("Session expired") + // Re-authenticate the user + + case HubPayload.EventName.Auth.signedOut: + print("User signed out") + // Update UI + + case HubPayload.EventName.Auth.userDeleted: + print("User deleted") + // Update UI + + default: + break + } + } +} +``` + + + +--- + +--- +title: "Delete user account" +section: "build-a-backend/auth/connect-your-frontend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-16T15:59:30.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/delete-user-account/" +--- + +export async function getStaticPaths() { + return getCustomStaticPath(meta.platforms); +} + +Empowering users to delete their account can improve trust and transparency. You can programmatically enable self-service account deletion with Amplify Auth. + +If you have not yet created an Amplify Gen 2 app, visit the [quickstart](/[platform]/start/quickstart). + +## Allow users to delete their account + +You can quickly set up account deletion for your users with the Amplify Libraries. Invoking the `deleteUser` API to delete a user from the Auth category will also sign out your user. + +If your application uses a Cognito User Pool, which is the default configuration, this action will only delete the user from the Cognito User Pool. It will have no effect if you are federating with a Cognito Identity Pool alone. + +> **Warning:** Before invoking the `deleteUser` API, you may need to first delete associated user data that is not stored in Cognito. For example, if you are using Amplify Data to persist user data, you could follow [these instructions](https://gist.github.com/aws-amplify-ops/27954c421bd72930874d48c15c284807) to delete associated user data. This allows you to address any guidelines (such as GDPR) that require your app to delete data associated with a user who deletes their account. + +You can enable account deletion using the following method: + + +```ts +import { deleteUser } from 'aws-amplify/auth'; + +async function handleDeleteUser() { + try { + await deleteUser(); + } catch (error) { + console.log(error); + } +} +``` + + +```dart +Future deleteUser() async { + try { + await Amplify.Auth.deleteUser(); + safePrint('Delete user succeeded'); + } on AuthException catch (e) { + safePrint('Delete user failed with error: $e'); + } +} +``` + + + +#### [Java] + +```java +Amplify.Auth.deleteUser( + () -> Log.i("AuthQuickStart", "Delete user succeeded"), + error -> Log.e("AuthQuickStart", "Delete user failed with error " + error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.deleteUser( + { Log.i("AuthQuickStart", "Delete user succeeded") }, + { Log.e("AuthQuickStart", "Delete user failed with error", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + Amplify.Auth.deleteUser() + Log.i("AuthQuickStart", "Delete user succeeded") +} catch (error: AuthException) { + Log.e("AuthQuickStart", "Delete user failed with error", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.deleteUser() + .subscribe( + () -> Log.i("AuthQuickStart", "Delete user succeeded"), + error -> Log.e("AuthQuickStart", "Delete user failed with error " + error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func deleteUser() async { + do { + try await Amplify.Auth.deleteUser() + print("Successfully deleted user") + } catch let error as AuthError { + print("Delete user failed with error \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func deleteUser() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.deleteUser() + }.sink { + if case let .failure(authError) = $0 { + print("Delete user failed with error \(authError)") + } + } + receiveValue: { + print("Successfully deleted user") + } +} +``` + + + +We recommend you update your UI to let your users know that their account is deleted and test the functionality with a test user. Note that your user will be signed out of your application when they delete their account. + +--- + +--- +title: "Multi-step sign-in" +section: "build-a-backend/auth/connect-your-frontend" +platforms: ["android", "swift", "flutter", "react", "nextjs", "javascript", "react-native", "vue", "angular"] +gen: 2 +last-updated: "2024-12-09T19:54:14.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/" +--- + + +After a user has finished signup, they can proceed to sign in. Amplify Auth signin flows can be multi-step processes. The required steps are determined by the configuration provided when you define your auth resources. See the [multi-factor authentication](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/) page for more information. + +Depending on the configuration, you may need to call various APIs to finish authenticating a user's signin attempt. To identify the next step in a signin flow, inspect the `nextStep` parameter of the signin result. + +```typescript +import { + confirmSignIn, + confirmSignUp, + resetPassword, + signIn, +} from 'aws-amplify/auth'; + +const { nextStep } = await signIn({ + username: 'hello@mycompany.com', + password: 'hunter2', +}); + +if ( + nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_CODE' || + nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE' || + nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_TOTP_CODE' +) { + // collect OTP from user + await confirmSignIn({ + challengeResponse: '123456', + }); +} + +if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_MFA_SELECTION') { + // present nextStep.allowedMFATypes to user + // collect user selection + await confirmSignIn({ + challengeResponse: 'EMAIL', // 'EMAIL', 'SMS', or 'TOTP' + }); +} + +if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION') { + // present nextStep.allowedMFATypes to user + // collect user selection + await confirmSignIn({ + challengeResponse: 'EMAIL', // 'EMAIL' or 'TOTP' + }); +} + +if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_EMAIL_SETUP') { + // collect email address from user + await confirmSignIn({ + challengeResponse: 'hello@mycompany.com', + }); +} + +if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_TOTP_SETUP') { + // present nextStep.totpSetupDetails.getSetupUri() to user + // collect OTP from user + await confirmSignIn({ + challengeResponse: '123456', + }); +} + +if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_PASSWORD') { + // collect password from user + await confirmSignIn({ + challengeResponse: 'hunter2', + }); +} + +if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION') { + // present nextStep.availableChallenges to user + // collect user selection + await confirmSignIn({ + challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' + }); +} + +if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE') { + // collect custom challenge answer from user + await confirmSignIn({ + challengeResponse: 'custom-challenge-answer', + }); +} + +if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED') { + // collect new password from user + await confirmSignIn({ + challengeResponse: 'new-password', + }); +} + +if (nextStep.signInStep === 'RESET_PASSWORD') { + // initiate reset password flow + await resetPassword({ + username: 'username', + }); +} + +if (nextStep.signInStep === 'CONFIRM_SIGN_UP') { + // user was not confirmed during sign up process + // if user has confirmation code, invoke `confirmSignUp` api + // otherwise, invoke `resendSignUpCode` to resend the code + await confirmSignUp({ + username: 'username', + confirmationCode: '123456', + }); +} + +if (nextStep.signInStep === 'DONE') { + // signin complete +} +``` + +## Confirm sign-in with SMS MFA + +If the next step is `CONFIRM_SIGN_IN_WITH_SMS_CODE`, Amplify Auth has sent the user a random code over SMS and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. + + + +The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial phone number of the SMS recipient, which can be used to prompt the user on where to look for the code. + + + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONFIRM_SIGN_IN_WITH_SMS_CODE': { + const { codeDeliveryDetails } = result.nextStep; + // OTP has been delivered to user via SMS + // Inspect codeDeliveryDetails for additional delivery information + console.log( + `A confirmation code has been sent to ${codeDeliveryDetails?.destination}`, + ); + console.log( + `Please check your ${codeDeliveryDetails?.deliveryMedium} for the code.`, + ); + break; + } + } +} + +async function confirmMfaCode(mfaCode: string) { + const result = await confirmSignIn({ challengeResponse: mfaCode }); + + return handleSignInResult(result); +} + +``` + +## Confirm sign-in with TOTP MFA + +If the next step is `CONFIRM_SIGN_IN_WITH_TOTP_CODE`, you should prompt the user to enter the TOTP code from their associated authenticator app during set up. The code is a six-digit number that changes every 30 seconds. The user must enter the code before the 30-second window expires. + +After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONFIRM_SIGN_IN_WITH_TOTP_CODE': { + // Prompt user to open their authenticator app to retrieve the code + console.log( + `Enter a one-time code from your registered authenticator app`, + ); + break; + } + } +} +// Then, pass the TOTP code to `confirmSignIn` +async function confirmTotpCode(totpCode: string) { + const result = await confirmSignIn({ challengeResponse: totpCode }); + + return handleSignInResult(result); +} + +``` + +## Confirm sign-in with Email MFA + +If the next step is `CONFIRM_SIGN_IN_WITH_EMAIL_CODE`, Amplify Auth has sent the user a random code to their email address and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. + + + +The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial email address of the recipient, which can be used to prompt the user on where to look for the code. + + + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE': { + const { codeDeliveryDetails } = result.nextStep; + // OTP has been delivered to user via Email + // Inspect codeDeliveryDetails for additional delivery information + console.log( + `A confirmation code has been sent to ${codeDeliveryDetails?.destination}`, + ); + console.log( + `Please check your ${codeDeliveryDetails?.deliveryMedium} for the code.`, + ); + break; + } + } +} + +async function confirmMfaCode(mfaCode: string) { + const result = await confirmSignIn({ challengeResponse: mfaCode }); + + return handleSignInResult(result); +} + +``` + +## Continue sign-in with MFA Selection + +If the next step is `CONTINUE_SIGN_IN_WITH_MFA_SELECTION`, the user must select the MFA method to use. Amplify Auth currently supports SMS, TOTP, and EMAIL as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. + +The MFA types which are currently supported by Amplify Auth are: + +- `SMS` +- `TOTP` +- `EMAIL` + +Once Amplify receives the users selection, you can expect to handle a follow up `nextStep` corresponding with the selected MFA type for setup: +- If `SMS` is selected, `CONFIRM_SIGN_IN_WITH_SMS_CODE` will be the next step. +- If `TOTP` is selected, `CONFIRM_SIGN_IN_WITH_TOTP_CODE` will be the next step. +- If `EMAIL` is selected, `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` will be the next step. + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONTINUE_SIGN_IN_WITH_MFA_SELECTION': { + const { allowedMFATypes } = result.nextStep; + // Present available MFA options to user + // Prompt for selection + console.log(`There are multiple MFA options available for sign in.`); + console.log(`Select an MFA type from the allowedMfaTypes list.`); + break; + } + } +} + +type MfaType = 'SMS' | 'TOTP' | 'EMAIL'; + +async function handleMfaSelection(mfaType: MfaType) { + const result = await confirmSignIn({ challengeResponse: mfaType }); + + return handleSignInResult(result); +} + +``` + +## Continue sign-in with Email Setup + +If the next step is `CONTINUE_SIGN_IN_WITH_EMAIL_SETUP`, then the user must provide an email address to complete the sign in process. Once this value has been collected from the user, call the `confirmSignIn` API to continue. + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONTINUE_SIGN_IN_WITH_EMAIL_SETUP': { + // Prompt the user to enter an email address they would like to use for MFA + break; + } + } +} + +// Then, pass the email address to `confirmSignIn` +async function confirmEmail(email: string) { + const result = await confirmSignIn({ challengeResponse: email }); + + return handleSignInResult(result); +} + +``` + +## Continue sign-in with TOTP Setup + +The `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` step signifies that the user must set up TOTP before they can sign in. The step returns an associated value of type TOTPSetupDetails which must be used to configure an authenticator app like Microsoft Authenticator or Google Authenticator. TOTPSetupDetails provides a helper method called getSetupURI which generates a URI that can be used, for example, in a button to open the user's installed authenticator app. For more advanced use cases, TOTPSetupDetails also contains a sharedSecret which can be used to either generate a QR code or be manually entered into an authenticator app. + +Once the authenticator app is set up, the user can generate a TOTP code and provide it to the library to complete the sign in process. + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONTINUE_SIGN_IN_WITH_TOTP_SETUP': { + const { totpSetupDetails } = result.nextStep; + const appName = 'my_app_name'; + const setupUri = totpSetupDetails.getSetupUri(appName); + // Open setupUri with an authenticator app + // Prompt user to enter OTP code to complete setup + break; + } + } +} + +// Then, pass the collected OTP code to `confirmSignIn` +async function confirmTotpCode(totpCode: string) { + const result = await confirmSignIn({ challengeResponse: totpCode }); + + return handleSignInResult(result); +} + +``` + +## Continue sign-in with MFA Setup Selection + +If the next step is `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION`, then the user must indicate which of the available MFA methods they would like to setup. After the user selects an MFA method to setup, your implementation must pass the selected MFA method to the `confirmSignIn` API. + +The MFA types which are currently supported by Amplify Auth for setup are: + +- `TOTP` +- `EMAIL` + +Once Amplify receives the users selection, you can expect to handle a follow up `nextStep` corresponding with the selected MFA type for setup: +- If `EMAIL` is selected, `CONTINUE_SIGN_IN_WITH_EMAIL_SETUP` will be the next step. +- If `TOTP` is selected, `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` will be the next step. + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION': { + const { allowedMFATypes } = result.nextStep; + // Present available MFA options to user + // Prompt for selection + console.log(`There are multiple MFA options available for setup.`); + console.log(`Select an MFA type from the allowedMFATypes list.`); + break; + } + } +} + +type MfaType = 'SMS' | 'TOTP' | 'EMAIL'; + +async function handleMfaSelection(mfaType: MfaType) { + const result = await confirmSignIn({ challengeResponse: mfaType }); + + return handleSignInResult(result); +} + +``` + +## Confirm sign-in with Password + +If the next step is `CONFIRM_SIGN_IN_WITH_PASSWORD`, the user must provide their password as the first factor authentication method. To handle this step, your implementation should prompt the user to enter their password. After the user enters the password, pass the value to the `confirmSignIn` API. + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONFIRM_SIGN_IN_WITH_PASSWORD': { + // Prompt user to enter their password + console.log(`Please enter your password.`); + break; + } + } +} + +async function confirmWithPassword(password: string) { + const result = await confirmSignIn({ challengeResponse: password }); + + return handleSignInResult(result); +} +``` + +## Continue sign-in with First Factor Selection + +If the next step is `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, the user must select a first factor method for authentication. After the user selects an option, your implementation should pass the selected method to the `confirmSignIn` API. + +The first factor types which are currently supported by Amplify Auth are: +- `SMS_OTP` +- `EMAIL_OTP` +- `WEB_AUTHN` +- `PASSWORD` +- `PASSWORD_SRP` + +Depending on your configuration and what factors the user has previously setup, not all options may be available. Only the available options will be presented in `availableChallenges` for selection. + +Once Amplify receives the user's selection via the `confirmSignIn` API, you can expect to handle a follow up `nextStep` corresponding with the first factor type selected: +- If `SMS_OTP` is selected, `CONFIRM_SIGN_IN_WITH_SMS_CODE` will be the next step. +- If `EMAIL_OTP` is selected, `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` will be the next step. +- If `PASSWORD` or `PASSWORD_SRP` is selected, `CONFIRM_SIGN_IN_WITH_PASSWORD` will be the next step. +- If `WEB_AUTHN` is selected, Amplify Auth will initiate the authentication ceremony on the user's device. If successful, the next step will be `DONE`. + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION': { + const { availableChallenges } = result.nextStep; + // Present available first factor options to user + // Prompt for selection + console.log( + `There are multiple first factor options available for sign in.`, + ); + console.log( + `Select a first factor type from the availableChallenges list.`, + ); + break; + } + } +} + +async function handleFirstFactorSelection(firstFactorType: string) { + const result = await confirmSignIn({ challengeResponse: firstFactorType }); + + return handleSignInResult(result); +} + +``` + +## Confirm sign-in with custom challenge + +If the next step is `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the AWS Lambda trigger you configured as part of a custom sign in flow. + +For example, your custom challenge Lambda may pass a prompt to the frontend which requires the user to enter a secret code. + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE': { + const params = result.nextStep.additionalInfo; + const hint = params.hint!; + // Prompt user to enter custom challenge response + console.log(hint); // `Enter the secret code` + break; + } + } +} + +``` + +To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the `confirmSignIn` API. + +```ts +async function confirmCustomChallenge(answer: string) { + const result = await confirmSignIn({ challengeResponse: answer }); + + return handleSignInResult(result); +} +``` + +> **Warning:** **Special Handling on `confirmSignIn`** +> +> If `failAuthentication=true` is returned by the Lambda, Cognito will invalidate the session of the request. This is represented by a `NotAuthorizedException` and requires restarting the sign-in flow by calling `signIn` again. + +## Confirm sign-in with new password + +If the next step is `CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED`, Amplify Auth requires the user choose a new password they proceeding with the sign in. + +Prompt the user for a new password and pass it to the `confirmSignIn` API. + +See the [sign-in](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/) and [manage-password](/[platform]/build-a-backend/auth/manage-users/manage-passwords/) docs for more information. + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED': { + // Prompt user to enter a new password + console.log(`Please enter a new password.`); + break; + } + } +} + +async function confirmNewPassword(newPassword: string) { + const result = await confirmSignIn({ challengeResponse: newPassword }); + + return handleSignInResult(result); +} + +``` + +## Reset password + +If the next step is `RESET_PASSWORD`, Amplify Auth requires that the user reset their password before proceeding. +Use the `resetPassword` API to guide the user through resetting their password, then call `signIn` to restart the sign-in flow. + +See the [reset password](/[platform]/build-a-backend/auth/manage-users/manage-passwords/) docs for more information. + +```ts +import { + type ResetPasswordOutput, + type SignInOutput, + resetPassword, +} from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'RESET_PASSWORD': { + const resetPasswordResult = await resetPassword({ username }); + // initiate reset password flow + await handleResetPasswordResult(resetPasswordResult); + break; + } + } +} + +async function handleResetPasswordResult( + resetPasswordResult: ResetPasswordOutput, +) { + switch (resetPasswordResult.nextStep.resetPasswordStep) { + case 'CONFIRM_RESET_PASSWORD_WITH_CODE': { + const { codeDeliveryDetails } = resetPasswordResult.nextStep; + console.log( + `A confirmation code has been sent to ${codeDeliveryDetails.destination}.`, + ); + console.log( + `Please check your ${codeDeliveryDetails.destination} for the code.`, + ); + break; + } + case 'DONE': { + console.log(`Successfully reset password.`); + break; + } + } +} + +``` + +## Confirm Signup + +If the next step is `CONFIRM_SIGN_UP`, Amplify Auth requires that the user confirm their email or phone number before proceeding. +Use the `resendSignUpCode` API to send a new sign up code to the registered email or phone number, followed by `confirmSignUp` to complete the sign up. + +See the [sign up](/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/) docs for more information. + + + +The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial phone number of the SMS recipient, which can be used to prompt the user on where to look for the code. + + + +```ts +import { + type SignInOutput, + confirmSignUp, + resendSignUpCode, +} from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONFIRM_SIGN_UP': { + // Resend sign up code to the registered user + const { destination, deliveryMedium } = await resendSignUpCode({ + username, + }); + console.log(`A confirmation code has been sent to ${destination}.`); + console.log(`Please check your ${deliveryMedium} for the code.`); + break; + } + } +} + +async function handleConfirmSignUp(username: string, confirmationCode: string) { + await confirmSignUp({ + username, + confirmationCode, + }); +} + +``` + +Once the sign up is confirmed, call `signIn` again to restart the sign-in flow. + +## Done + +The sign-in flow is complete when the next step is `DONE`, which means the user is successfully authenticated. +As a convenience, the `SignInResult` also provides the `isSignedIn` property, which will be true if the next step is `DONE`. + +```ts +import { type SignInOutput } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'DONE': { + // `result.isSignedIn` is `true` + console.log(`Sign in is complete.`); + break; + } + } +} + +``` + + + +After a user has finished signup, they can proceed to sign in. Amplify Auth signin flows can be multi step processes. The required steps are determined by the configuration you provided when you define your auth resources like described on [Manage MFA Settings](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/) page. + +Depending on the configuration, you may need to call various APIs to finish authenticating a user's signin attempt. To identify the next step in a signin flow, inspect the `nextStep` parameter in the signin result. + +> **Warning:** *New enumeration values* +> +> When Amplify adds a new enumeration value (e.g., a new enum class entry or sealed class subtype in Kotlin, or a new enum value in Swift/Dart/Kotlin), it will publish a new minor version of the Amplify Library. Plugins that switch over enumeration values should include default handlers (an else branch in Kotlin or a default statement in Swift/Dart/Kotlin) to ensure that they are not impacted by new enumeration values. + +The `Amplify.Auth.signIn` API returns a `SignInResult` object which indicates whether the sign-in flow is +complete or whether additional steps are required before the user is signed in. + +To see if additional signin steps are required, inspect the sign in result's `nextStep.signInStep` property. +- If the sign-in step is `done`, the flow is complete and the user is signed in. +- If the sign-in step is not `done`, one or more additional steps are required. These are explained in detail below. + + + +The `signInStep` property is an enum of type `AuthSignInStep`. Depending on its value, your code should take one of the actions mentioned on this page. + + + +```dart +Future signInWithCognito( + String username, + String password, +) async { + final SignInResult result = await Amplify.Auth.signIn( + username: username, + password: password, + ); + return _handleSignInResult(result); +} + +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + case AuthSignInStep.continueSignInWithMfaSelection: + // Handle select from MFA methods case + case AuthSignInStep.continueSignInWithMfaSetupSelection: + // Handle select from MFA methods available to setup + case AuthSignInStep.continueSignInWithEmailMfaSetup: + // Handle email setup case + case AuthSignInStep.confirmSignInWithOtpCode: + // Handle email MFA case + case AuthSignInStep.continueSignInWithTotpSetup: + // Handle TOTP setup case + case AuthSignInStep.confirmSignInWithTotpMfaCode: + // Handle TOTP MFA case + case AuthSignInStep.confirmSignInWithSmsMfaCode: + // Handle SMS MFA case + case AuthSignInStep.confirmSignInWithNewPassword: + // Handle new password case + case AuthSignInStep.confirmSignInWithCustomChallenge: + // Handle custom challenge case + case AuthSignInStep.resetPassword: + // Handle reset password case + case AuthSignInStep.confirmSignUp: + // Handle confirm sign up case + case AuthSignInStep.done: + safePrint('Sign in is complete'); + } +} +``` +## Confirm sign-in with SMS MFA + +If the next step is `confirmSignInWithSmsMfaCode`, Amplify Auth has sent the user a random code over SMS and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. + + + +The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial phone number of +the SMS recipient, which can be used to prompt the user on where to look for the code. + + + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + case AuthSignInStep.confirmSignInWithSmsMfaCode: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + // ... + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` + +```dart +Future confirmMfaUser(String mfaCode) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: mfaCode, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming MFA code: ${e.message}'); + } +} +``` + +## Confirm sign-in with TOTP MFA + +If the next step is `confirmSignInWithTOTPCode`, you should prompt the user to enter the TOTP code from their associated authenticator app during set up. The code is a six-digit number that changes every 30 seconds. The user must enter the code before the 30-second window expires. + +After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // Β·Β·Β· + case AuthSignInStep.confirmSignInWithTotpMfaCode: + safePrint('Enter a one-time code from your registered authenticator app'); + // Β·Β·Β· + } +} + +// Then, pass the TOTP code to `confirmSignIn` + +Future confirmTotpUser(String totpCode) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: totpCode, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming TOTP code: ${e.message}'); + } +} +``` + +## Confirm sign-in with Email MFA + +If the next step is `confirmSignInWithOtpCode`, Amplify Auth has sent the user a random code to their email address and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. + + + +The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial email address of +the recipient, which can be used to prompt the user on where to look for the code. + + + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + case AuthSignInStep.confirmSignInWithOtpCode: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + // ... + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` + +```dart +Future confirmMfaUser(String mfaCode) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: mfaCode, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming MFA code: ${e.message}'); + } +} +``` + +## Continue sign-in with MFA Selection + +If the next step is `continueSignInWithMFASelection`, the user must select the MFA method to use. Amplify Auth currently supports SMS, TOTP, and email as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. + +The MFA types which are currently supported by Amplify Auth are: + +- `MfaType.sms` +- `MfaType.totp` +- `MfaType.email` + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // Β·Β·Β· + case AuthSignInStep.continueSignInWithMfaSelection: + final allowedMfaTypes = result.nextStep.allowedMfaTypes!; + final selection = await _promptUserPreference(allowedMfaTypes); + return _handleMfaSelection(selection); + // Β·Β·Β· + } +} + +Future _promptUserPreference(Set allowedTypes) async { + // Β·Β·Β· +} + +Future _handleMfaSelection(MfaType selection) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: selection.confirmationValue, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error resending code: ${e.message}'); + } +} +``` + +## Continue sign-in with Email Setup + +If the next step is `continueSignInWithEmailMfaSetup`, then the user must provide an email address to complete the sign in process. Once this value has been collected from the user, call the `confirmSignIn` API to continue. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // Β·Β·Β· + case AuthSignInStep.continueSignInWithEmailMfaSetup: + // Prompt user to enter an email address they would like to use for MFA + // Β·Β·Β· + } +} + +// Then, pass the email address to `confirmSignIn` + +Future confirmEmailUser(String emailAddress) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: emailAddress, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming email address: ${e.message}'); + } +} +``` + +## Continue sign-in with TOTP Setup + +If the next step is `continueSignInWithTOTPSetup`, then the user must provide a TOTP code to complete the sign in process. The step returns an associated value of type `TOTPSetupDetails` which would be used for generating TOTP. `TOTPSetupDetails` provides a helper method called `getSetupURI` that can be used to generate a URI, which can be used by native password managers for TOTP association. For example. if the URI is used on Apple platforms, it will trigger the platform's native password manager to associate TOTP with the account. For more advanced use cases, `TOTPSetupDetails` also contains the `sharedSecret` that will be used to either generate a QR code or can be manually entered into an authenticator app. + +Once the authenticator app is set up, the user can generate a TOTP code and provide it to the library to complete the sign in process. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // Β·Β·Β· + case AuthSignInStep.continueSignInWithTotpSetup: + final totpSetupDetails = result.nextStep.totpSetupDetails!; + final setupUri = totpSetupDetails.getSetupUri(appName: 'MyApp'); + safePrint('Open URI to complete setup: $setupUri'); + // Β·Β·Β· + } +} + +// Then, pass the TOTP code to `confirmSignIn` + +Future confirmTotpUser(String totpCode) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: totpCode, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming TOTP code: ${e.message}'); + } +} +``` + +## Continue sign-in with MFA Setup Selection +If the next step is `continueSignInWithMfaSetupSelection`, then the user must indicate which of the available MFA methods they would like to setup. After the user selects an MFA method to setup, your implementation must pass the selected MFA method to the `confirmSignIn` API. + +The MFA types which are currently supported by Amplify Auth are: + +- `MfaType.sms` +- `MfaType.totp` +- `MfaType.email` + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // Β·Β·Β· + case AuthSignInStep.continueSignInWithMfaSetupSelection: + final allowedMfaTypes = result.nextStep.allowedMfaTypes!; + final selection = await _promptUserPreference(allowedMfaTypes); + return _handleMfaSelection(selection); + // Β·Β·Β· + } +} + +Future _promptUserPreference(Set allowedTypes) async { + // Β·Β·Β· +} + +Future _handleMfaSelection(MfaType selection) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: selection.confirmationValue, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error selecting MFA method: ${e.message}'); + } +} +``` + +## Confirm sign-in with custom challenge + +If the next step is `confirmSignInWithCustomChallenge`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the AWS Lambda trigger you configured as part of a [custom sign in flow](/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/#sign-in-a-user). + +For example, your custom challenge Lambda may pass a prompt to the frontend which requires the user to enter a secret code. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ... + case AuthSignInStep.confirmSignInWithCustomChallenge: + final parameters = result.nextStep.additionalInfo; + final hint = parameters['hint']!; + safePrint(hint); // "Enter the secret code" + // ... + } +} +``` + +To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the `confirmSignIn` API. + +```dart +Future confirmCustomChallenge(String answer) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: answer, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming custom challenge: ${e.message}'); + } +} +``` + +> **Warning:** **Special Handling on `confirmSignIn`** +> +> If `failAuthentication=true` is returned by the Lambda, Cognito will invalidate the session of the request. This is represented by a `NotAuthorizedException` and requires restarting the sign-in flow by calling `Amplify.Auth.signIn` again. + +## Confirm sign-in with new password +If the next step is `confirmSignInWithNewPassword`, Amplify Auth requires the user choose a new password they proceeding with the sign in. + +Prompt the user for a new password and pass it to the `confirmSignIn` API. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ... + case AuthSignInStep.confirmSignInWithNewPassword: + safePrint('Please enter a new password'); + // ... + } +} +``` + +```dart +Future confirmNewPassword(String newPassword) async { + try { + final result = await Amplify.Auth.confirmSignIn( + confirmationValue: newPassword, + ); + return _handleSignInResult(result); + } on AuthException catch (e) { + safePrint('Error confirming new password: ${e.message}'); + } +} +``` + +## Reset password +If the next step is `resetPassword`, Amplify Auth requires that the user reset their password before proceeding. +Use the `resetPassword` API to guide the user through resetting their password, then call `Amplify.Auth.signIn` +when that's complete to restart the sign-in flow. + +See the [reset password](/[platform]/build-a-backend/auth/manage-users/manage-passwords/) docs for more information. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ... + case AuthSignInStep.resetPassword: + final resetResult = await Amplify.Auth.resetPassword( + username: username, + ); + await _handleResetPasswordResult(resetResult); + // ... + } +} + +Future _handleResetPasswordResult(ResetPasswordResult result) async { + switch (result.nextStep.updateStep) { + case AuthResetPasswordStep.confirmResetPasswordWithCode: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + case AuthResetPasswordStep.done: + safePrint('Successfully reset password'); + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` +## Confirm Signup +If the next step is `resetPassword`, Amplify Auth requires that the user confirm their email or phone number before proceeding. +Use the `resendSignUpCode` API to send a new sign up code to the registered email or phone number, followed by `confirmSignUp` +to complete the sign up. + +See the [confirm sign up](/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/#confirm-sign-up) docs for more information. + + + +The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial phone number of +the SMS recipient, which can be used to prompt the user on where to look for the code. + + + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ... + case AuthSignInStep.confirmSignUp: + // Resend the sign up code to the registered device. + final resendResult = await Amplify.Auth.resendSignUpCode( + username: username, + ); + _handleCodeDelivery(resendResult.codeDeliveryDetails); + // ... + } +} + +void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { + safePrint( + 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' + 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', + ); +} +``` + +```dart +Future confirmSignUp({ + required String username, + required String confirmationCode, +}) async { + try { + await Amplify.Auth.confirmSignUp( + username: username, + confirmationCode: confirmationCode, + ); + } on AuthException catch (e) { + safePrint('Error confirming sign up: ${e.message}'); + } +} +``` + +Once the sign up is confirmed, call `Amplify.Auth.signIn` again to restart the sign-in flow. + +## Done + +The sign-in flow is complete when the next step is `done`, which means the user is successfully authenticated. +As a convenience, the `SignInResult` also provides the `isSignedIn` property, which will be true if the next step is `done`. + +```dart +Future _handleSignInResult(SignInResult result) async { + switch (result.nextStep.signInStep) { + // ... + case AuthSignInStep.done: + // Could also check that `result.isSignedIn` is `true` + safePrint('Sign in is complete'); + } +} +``` + + + +After a user has finished signup, they can proceed to sign in. Amplify Auth signin flows can be multi step processes. The required steps are determined by the configuration you provided when you define your auth resources like described on [Manage MFA Settings](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/) page. + +Depending on the configuration, you may need to call various APIs to finish authenticating a user's signin attempt. To identify the next step in a signin flow, inspect the `nextStep` parameter in the signin result. + +> **Warning:** *New enumeration values* +> +> When Amplify adds a new enumeration value (e.g., a new enum class entry or sealed class subtype in Kotlin, or a new enum value in Swift/Dart/Kotlin), it will publish a new minor version of the Amplify Library. Plugins that switch over enumeration values should include default handlers (an else branch in Kotlin or a default statement in Swift/Dart/Kotlin) to ensure that they are not impacted by new enumeration values. + +When called successfully, the signin APIs will return an `AuthSignInResult`. Inspect the `nextStep` property in the result to see if additional signin steps are required. +The `nextStep` property is of enum type `AuthSignInStep`. Depending on its value, your code should take one of the following actions: + +#### [Java] + +```java +try { + Amplify.Auth.signIn( + "hello@example.com", + "password", + result -> + { + AuthNextSignInStep nextStep = result.getNextStep(); + switch (nextStep.getSignInStep()) { + case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); + Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to setup + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); + // Prompt the user to enter the email address they would like to use to receive OTPs + // Then invoke `confirmSignIn` api with the email address + break; + } + case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); + Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to use + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION: { + Log.i("AuthQuickstart", "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors()); + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + break; + } + case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { + Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the SMS MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_OTP: { + Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the OTP MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_PASSWORD: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password"); + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + break; + } + case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { + Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter custom challenge answer + // Then invoke `confirmSignIn` api with the answer + break; + } + case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { + Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter a new password + // Then invoke `confirmSignIn` api with new password + break; + } + case DONE: { + Log.i("AuthQuickstart", "SignIn complete"); + // User has successfully signed in to the app + break; + } + } + }, + error -> { + if (error instanceof UserNotConfirmedException) { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.i("AuthQuickstart", "Signup confirmation required" + error); + } else if (error instanceof PasswordResetRequiredException) { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.i("AuthQuickstart", "Password reset required" + error); + } else { + Log.e("AuthQuickstart", "SignIn failed: " + error); + } + } + ); +} catch (Exception error) { + Log.e("AuthQuickstart", "Unexpected error occurred: " + error); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +try { + Amplify.Auth.signIn( + "hello@example.com", + "password", + { result -> + val nextStep = result.nextStep + when(nextStep.signInStep){ + AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE -> { + Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code") + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup") + Log.i("AuthQuickstart", "Allowed MFA types for setup ${nextStep.allowedMFATypes}") + // Prompt the user to select the MFA type they want to setup + // Then invoke `confirmSignIn` api with the MFA type + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA") + // Prompt the user to enter the email address they would like to use to receive OTPs + // Then invoke `confirmSignIn` api with the email address + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP") + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails?.sharedSecret}") + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type") + Log.i("AuthQuickstart", "Allowed MFA types ${nextStep.allowedMFATypes}") + // Prompt the user to select the MFA type they want to use + // Then invoke `confirmSignIn` api with the MFA type + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION -> { + Log.i("AuthQuickstart", "Available authentication factors for this user: ${result.nextStep.availableFactors}") + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE -> { + Log.i("AuthQuickstart", "SMS code sent to ${nextStep.codeDeliveryDetails?.destination}") + Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") + // Prompt the user to enter the SMS MFA code they received + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP -> { + Log.i("AuthQuickstart", "OTP code sent to ${nextStep.codeDeliveryDetails?.destination}") + Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") + // Prompt the user to enter the OTP MFA code they received + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD -> { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password") + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE -> { + Log.i("AuthQuickstart","Custom challenge, additional info: ${nextStep.additionalInfo}") + // Prompt the user to enter custom challenge answer + // Then invoke `confirmSignIn` api with the answer + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD -> { + Log.i("AuthQuickstart", "Sign in with new password, additional info: ${nextStep.additionalInfo}") + // Prompt the user to enter a new password + // Then invoke `confirmSignIn` api with new password + } + AuthSignInStep.DONE -> { + Log.i("AuthQuickstart", "SignIn complete") + // User has successfully signed in to the app + } + } + + } + ) { error -> + when (error) { + is UserNotConfirmedException -> { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.e("AuthQuickstart", "Signup confirmation required", error) + } + is PasswordResetRequiredException -> { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.e("AuthQuickstart", "Password reset required", error) + } + else -> { + Log.e("AuthQuickstart", "Unexpected error occurred: $error") + } + } + } +} catch (error: Exception) { + Log.e("AuthQuickstart", "Unexpected error occurred: $error") +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.signIn( + "hello@example.com", + "password" + ) + val nextStep = result.nextStep + when (nextStep.signInStep) { + AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE -> { + Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code") + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup") + Log.i("AuthQuickstart", "Allowed MFA types for setup ${nextStep.allowedMFATypes}") + // Prompt the user to select the MFA type they want to setup + // Then invoke `confirmSignIn` api with the MFA type + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA") + // Prompt the user to enter the email address they would like to use to receive OTPs + // Then invoke `confirmSignIn` api with the email address + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP") + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails?.sharedSecret}") + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type") + Log.i("AuthQuickstart", "Allowed MFA types ${nextStep.allowedMFATypes}") + // Prompt the user to select the MFA type they want to use + // Then invoke `confirmSignIn` api with the MFA type + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION -> { + Log.i("AuthQuickstart", "Available authentication factors for this user: ${result.nextStep.availableFactors}") + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE -> { + Log.i("AuthQuickstart", "SMS code sent to ${nextStep.codeDeliveryDetails?.destination}") + Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") + // Prompt the user to enter the SMS MFA code they received + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP -> { + Log.i("AuthQuickstart", "OTP code sent to ${nextStep.codeDeliveryDetails?.destination}") + Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") + // Prompt the user to enter the OTP MFA code they received + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD -> { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password") + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE -> { + Log.i("AuthQuickstart","Custom challenge, additional info: ${nextStep.additionalInfo}") + // Prompt the user to enter custom challenge answer + // Then invoke `confirmSignIn` api with the answer + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD -> { + Log.i("AuthQuickstart", "Sign in with new password, additional info: ${nextStep.additionalInfo}") + // Prompt the user to enter a new password + // Then invoke `confirmSignIn` api with new password + } + AuthSignInStep.DONE -> { + Log.i("AuthQuickstart", "SignIn complete") + // User has successfully signed in to the app + } + } +} catch (error: Exception) { + when (error) { + is UserNotConfirmedException -> { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.e("AuthQuickstart", "Signup confirmation required", error) + } + is PasswordResetRequiredException -> { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.e("AuthQuickstart", "Password reset required", error) + } + else -> { + Log.e("AuthQuickstart", "Unexpected error occurred: $error") + } + } +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.signIn("hello@example.com", "password").subscribe( + result -> + { + AuthNextSignInStep nextStep = result.getNextStep(); + switch (nextStep.getSignInStep()) { + case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); + Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to setup + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); + // Prompt the user to enter the email address they would like to use to receive OTPs + // Then invoke `confirmSignIn` api with the email address + break; + } + case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); + Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to use + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION: { + Log.i("AuthQuickstart", "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors()); + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + break; + } + case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { + Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the SMS MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_OTP: { + Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the OTP MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_PASSWORD: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password"); + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + break; + } + case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { + Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter custom challenge answer + // Then invoke `confirmSignIn` api with the answer + break; + } + case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { + Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter a new password + // Then invoke `confirmSignIn` api with new password + break; + } + case DONE: { + Log.i("AuthQuickstart", "SignIn complete"); + // User has successfully signed in to the app + break; + } + } + }, + error -> { + if (error instanceof UserNotConfirmedException) { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.i("AuthQuickstart", "Signup confirmation required" + error); + } else if (error instanceof PasswordResetRequiredException) { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.i("AuthQuickstart", "Password reset required" + error); + } else { + Log.e("AuthQuickstart", "SignIn failed: " + error); + } + } +); +``` + +## Confirm sign-in with SMS MFA + +If the next step is `CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE`, Amplify Auth has sent the user a random code over SMS, and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. + + + +**Note:** The result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial phone number of the SMS recipient. + + + +#### [Java] + +```java +try { + Amplify.Auth.confirmSignIn( + "confirmation code", + result -> { + if (result.isSignedIn()) { + Log.i("AuthQuickstart", "Confirm signIn succeeded"); + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + }, + error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) + ); +} catch (Exception error) { + Log.e("AuthQuickstart", "Unexpected error: " + error); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +try { + Amplify.Auth.confirmSignIn( + "confirmation code", + { result -> + if (result.isSignedIn) { + Log.i("AuthQuickstart","Confirm signIn succeeded") + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } + ) { error -> Log.e("AuthQuickstart", "Confirm sign in failed: $error")} +} catch (error: Exception) { + Log.e("AuthQuickstart", "Unexpected error: $error") +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.confirmSignIn( + "confirmation code" + ) + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Confirm signIn succeeded") + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}" + ) + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } +} catch (error: Exception) { + Log.e("AuthQuickstart", "Unexpected error: $error") +} +``` + +#### [RxJava] + +```java + +RxAmplify.Auth.confirmSignIn( + "confirmation code").subscribe( + result -> { + if (result.isSignedIn()) { + Log.i("AuthQuickstart", "Confirm signIn succeeded"); + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + }, + error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) + ); +``` + +## Confirm sign-in with TOTP MFA + +If the next step is `CONFIRM_SIGN_IN_WITH_TOTP_CODE`, you should prompt the user to enter the TOTP code from their associated authenticator app during set up. The code is a six-digit number that changes every 30 seconds. The user must enter the code before the 30-second window expires. + +After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. + +## Confirm sign-in with Email MFA + +If the next step is `CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE`, Amplify Auth has sent the user a random code to their email address and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. + + + +**Note:** The result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial email address of the recipient. + + + +## Confirm sign-in with OTP + +If the next step is `CONFIRM_SIGN_IN_WITH_OTP`, Amplify Auth has sent the user a random code to the medium of the user's choosing (e.g. SMS or email) and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. + + + +**Note:** The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial email address of the recipient, which can be used to prompt the user on where to look for the code. + + + +## Continue sign-in with MFA Selection + +If the next step is `CONTINUE_SIGN_IN_WITH_MFA_SELECTION`, the user must select the MFA method to use. Amplify Auth currently supports SMS, TOTP, and email as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. + +## Continue sign-in with Email Setup + +If the next step is `CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP`, then the user must provide an email address to complete the sign in process. Once this value has been collected from the user, call the `confirmSignIn` API to continue. + +## Continue sign-in with TOTP Setup + +If the next step is `CONTINUE_SIGN_IN_WITH_TOTP_SETUP`, then the user must provide a TOTP code to complete the sign in process. The step returns an associated value of type `TOTPSetupDetails` which would be used for generating TOTP. `TOTPSetupDetails` provides a helper method called `getSetupURI` that can be used to generate a URI, which can be used by native password managers for TOTP association. For example. if the URI is used on Apple platforms, it will trigger the platform's native password manager to associate TOTP with the account. For more advanced use cases, `TOTPSetupDetails` also contains the `sharedSecret` that will be used to either generate a QR code or can be manually entered into an authenticator app. + +Once the authenticator app is set up, the user can generate a TOTP code and provide it to the library to complete the sign in process. + +## Continue sign-in with MFA Setup Selection + +If the next step is `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION`, the user must select the MFA method to setup. Amplify Auth currently supports SMS, TOTP, and email as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. + +## Continue sign-in with First Factor Selection + +If the next step is `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, the user must select an authentication factor to use either because they did not specify one or because the one they chose is not supported (e.g. selecting SMS when they don't have a phone number registered to their account). Amplify Auth currently supports SMS, email, password, and webauthn as authentication factors. After the user selects an authentication method, your implementation must pass the selected authentication method to Amplify Auth using `confirmSignIn` API. + +Visit the [sign-in documentation](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#sign-in-with-passwordless-methods) to see examples on how to call the `confirmSignIn` API. + +## Confirm sign-in with custom challenge + +If the next step is `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the Lambda trigger you setup when you configured a [custom sign in flow](/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/#sign-in-a-user). To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the `confirmSignIn` API. + +#### [Java] + +```java +try { + Amplify.Auth.confirmSignIn( + "challenge answer", + result -> { + if (result.isSignedIn()) { + Log.i("AuthQuickstart", "Confirm signIn succeeded"); + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + }, + error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) + ); +} catch (Exception error) { + Log.e("AuthQuickstart", "Unexpected error: " + error); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +try { + Amplify.Auth.confirmSignIn( + "challenge answer", + { result -> + if (result.isSignedIn) { + Log.i("AuthQuickstart","Confirm signIn succeeded") + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } + ) { error -> + Log.e("AuthQuickstart", "Confirm sign in failed: $error") + } +} catch (error: Exception) { + Log.e("AuthQuickstart", "Unexpected error: $error") +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.confirmSignIn( + "challenge answer" + ) + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Confirm signIn succeeded") + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } +} catch (error: Exception) { + Log.e("AuthQuickstart", "Unexpected error: $error") +} +``` + +#### [RxJava] + +```java + +RxAmplify.Auth.confirmSignIn( + "challenge answer").subscribe( + result -> { + if (result.isSignedIn()) { + Log.i("AuthQuickstart", "Confirm signIn succeeded"); + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + }, + error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) +); +``` + +> **Warning:** **Special Handling on `confirmSignIn`** +> +> During a confirmSignIn call if `failAuthentication=true` is returned by the Lambda the session of the request gets invalidated by cognito, a NotAuthorizedException is returned and a new signIn call is expected via Amplify.Auth.signIn +> +> ```java +NotAuthorizedException{message=Failed since user is not authorized., cause=NotAuthorizedException(message=Invalid session for the user.), recoverySuggestion=Check whether the given values are correct and the user is authorized to perform the operation.} +``` + +## Confirm sign-in with new password +If you receive a `UserNotConfirmedException` while signing in, Amplify Auth requires a new password for the user before they can proceed. Prompt the user for a new password and pass it to the `confirmSignIn` API. + +#### [Java] + +```java +try { + Amplify.Auth.confirmSignIn( + "confirmation code", + result -> { + if (result.isSignedIn()) { + Log.i("AuthQuickstart", "Confirm signIn succeeded"); + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + }, + error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) + ); +} catch (Exception error) { + Log.e("AuthQuickstart", "Unexpected error: " + error); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin + try { + Amplify.Auth.confirmSignIn( + "confirmation code", + { result -> + if (result.isSignedIn) { + Log.i("AuthQuickstart","Confirm signIn succeeded") + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}") + } + } + ) { error -> + Log.e("AuthQuickstart", "Confirm sign in failed: $error") + } +} catch (error: Exception) { + Log.e("AuthQuickstart", "Unexpected error: $error") +} +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.confirmSignIn( + "confirmation code" + ) + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Confirm signIn succeeded") + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}") + } +} catch (error: Exception) { + Log.e("AuthQuickstart", "Unexpected error: $error") +} +``` + +#### [RxJava] + +```java + +RxAmplify.Auth.confirmSignIn( + "confirmation code").subscribe( + result -> { + if (result.isSignedIn()) { + Log.i("AuthQuickstart", "Confirm signIn succeeded"); + } else { + Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); + } + }, + error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) + ); +``` + +## Reset password +If you receive `PasswordResetRequiredException`, authentication flow could not proceed without resetting the password. The next step is to invoke `resetPassword` api and follow the reset password flow. + +#### [Java] + +```java +try { + Amplify.Auth.resetPassword( + "username", + result -> Log.i("AuthQuickstart", "Reset password succeeded"), + error -> Log.e("AuthQuickstart", "Reset password failed : " + error) + ); +} catch (Exception error) { + Log.e("AuthQuickstart", "Unexpected error: " + error); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +try { + Amplify.Auth.resetPassword( + "username", + { + Log.i("AuthQuickstart", "Reset password succeeded") + } + ) { error -> + Log.e("AuthQuickstart", "Reset password failed : $error") + } +} catch (error: Exception) { + Log.e("AuthQuickstart", "Unexpected error: $error") +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + Amplify.Auth.resetPassword("username") + Log.i("AuthQuickstart", "Reset password succeeded") +} catch (error: Exception) { + Log.e("AuthQuickstart", "Unexpected error: $error") +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.resetPassword( + "username").subscribe( + result -> Log.i("AuthQuickstart", "Reset password succeeded"), + error -> Log.e("AuthQuickstart", "Reset password failed : " + error) +); +``` + +## Confirm Signup + +If you receive `CONFIRM_SIGN_UP` as a next step, sign up could not proceed without confirming user information such as email or phone number. The next step is to invoke the `confirmSignUp` API and follow the confirm signup flow. + +#### [Java] + +```java + try { + Amplify.Auth.confirmSignUp( + "username", + "confirmation code", + result -> Log.i("AuthQuickstart", "Confirm signUp result completed: " + result.isSignUpComplete()), + error -> Log.e("AuthQuickstart", "An error occurred while confirming sign up: " + error) + ); +} catch (Exception error) { + Log.e("AuthQuickstart", "unexpected error: " + error); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin + try { + Amplify.Auth.confirmSignUp( + "username", + "confirmation code", + { result -> + Log.i("AuthQuickstart", "Confirm signUp result completed: ${result.isSignUpComplete}") + } + ) { error -> + Log.e("AuthQuickstart", "An error occurred while confirming sign up: $error") + } +} catch (error: Exception) { + Log.e("AuthQuickstart", "unexpected error: $error") +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.confirmSignUp( + "username", + "confirmation code" + ) + Log.i("AuthQuickstart", "Confirm signUp result completed: ${result.isSignUpComplete}") +} catch (error: Exception) { + Log.e("AuthQuickstart", "unexpected error: $error") +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.confirmSignUp( + "username", + "confirmation code").subscribe( + result -> Log.i("AuthQuickstart", "Confirm signUp result completed: " + result.isSignUpComplete()), + error -> Log.e("AuthQuickstart", "An error occurred while confirming sign up: " + error) +); +``` + +## Get Current User + +This call fetches the current logged in user and should be used after a user has been successfully signed in. +If the user is signed in, it will return the current userId and username. + + +**Note:** An empty string will be assigned to userId and/or username, if the values are not present in the accessToken. + + +#### [Java] + +```java + try { + Amplify.Auth.getCurrentUser( + result -> Log.i("AuthQuickstart", "Current user details are:" + result.toString(), + error -> Log.e("AuthQuickstart", "getCurrentUser failed with an exception: " + error) + ); + } catch (Exception error) { + Log.e("AuthQuickstart", "unexpected error: " + error); + } +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.getCurrentUser({ + Log.i("AuthQuickStart", "Current user details are: $it")},{ + Log.e("AuthQuickStart", "getCurrentUser failed with an exception: $it") +}) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.getCurrentUser() + Log.i("AuthQuickstart", "Current user details are: $result") +} catch (error: Exception) { + Log.e("AuthQuickstart", "getCurrentUser failed with an exception: $error") +} +``` + +#### [RxJava] + +```java + RxAmplify.Auth.getCurrentUser().subscribe( + result -> Log.i("AuthQuickStart getCurrentUser: " + result.toString()), + error -> Log.e("AuthQuickStart", error.toString()) + ); +``` + +## Done + +Sign In flow is complete when you get `done`. This means the user is successfully authenticated. As a convenience, the SignInResult also provides the `isSignedIn` property, which will be true if the next step is `done`. + + + +After a user has finished signup, they can proceed to sign in. Amplify Auth signin flows can be multi step processes. The required steps are determined by the configuration you provided when you define your auth resources like described on [Manage MFA Settings](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/) page. + +Depending on the configuration, you may need to call various APIs to finish authenticating a user's signin attempt. To identify the next step in a signin flow, inspect the `nextStep` parameter in the signin result. + +> **Warning:** *New enumeration values* +> +> When Amplify adds a new enumeration value (e.g., a new enum class entry or sealed class subtype in Kotlin, or a new enum value in Swift/Dart/Kotlin), it will publish a new minor version of the Amplify Library. Plugins that switch over enumeration values should include default handlers (an else branch in Kotlin or a default statement in Swift/Dart/Kotlin) to ensure that they are not impacted by new enumeration values. + +When called successfully, the signin APIs will return an `AuthSignInResult`. Inspect the `nextStep` property in the result to see if additional signin steps are required. + +```swift +func signIn(username: String, password: String) async { + do { + let signInResult = try await Amplify.Auth.signIn(username: username, password: password) + switch signInResult.nextStep { + case .confirmSignInWithSMSMFACode(let deliveryDetails, let info): + print("SMS code sent to \(deliveryDetails.destination)") + print("Additional info \(String(describing: info))") + + // Prompt the user to enter the SMSMFA code they received + // Then invoke `confirmSignIn` api with the code + + case .confirmSignInWithTOTPCode: + print("Received next step as confirm sign in with TOTP code") + + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + + case .confirmSignInWithOTP(let deliveryDetails): + print("Email code sent to \(deliveryDetails.destination)") + + // Prompt the user to enter the Email MFA code they received + // Then invoke `confirmSignIn` api with the code + + case .continueSignInWithFirstFactorSelection(let allowedFactors): + print("Received next step as continue sign in by selecting first factor") + print("Allowed factors \(allowedFactors)") + + // Prompt the user to select the first factor they want to use + // Then invoke `confirmSignIn` api with the factor + + case .confirmSignInWithPassword: + print("Received next step as confirm sign in with password") + + // Prompt the user to enter the password + // Then invoke `confirmSignIn` api with the password + + case .continueSignInWithTOTPSetup(let setUpDetails): + print("Received next step as continue sign in by setting up TOTP") + print("Shared secret that will be used to set up TOTP in the authenticator app \(setUpDetails.sharedSecret)") + + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + + case .continueSignInWithEmailMFASetup: + print("Received next step as continue sign in by setting up email MFA") + + // Prompt the user to enter the email address they wish to use for MFA + // Then invoke `confirmSignIn` api with the email address + + case .continueSignInWithMFASetupSelection(let allowedMFATypes): + print("Received next step as continue sign in by selecting MFA type to setup") + print("Allowed MFA types \(allowedMFATypes)") + + // Prompt the user to select the MFA type they want to setup + // Then invoke `confirmSignIn` api with the MFA type + + case .continueSignInWithMFASelection(let allowedMFATypes): + print("Received next step as continue sign in by selecting MFA type") + print("Allowed MFA types \(allowedMFATypes)") + + // Prompt the user to select the MFA type they want to use + // Then invoke `confirmSignIn` api with the MFA type + + case .confirmSignInWithCustomChallenge(let info): + print("Custom challenge, additional info \(String(describing: info))") + + // Prompt the user to enter custom challenge answer + // Then invoke `confirmSignIn` api with the answer + + case .confirmSignInWithNewPassword(let info): + print("New password additional info \(String(describing: info))") + + // Prompt the user to enter a new password + // Then invoke `confirmSignIn` api with new password + + case .resetPassword(let info): + print("Reset password additional info \(String(describing: info))") + + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signin flow again. + + case .confirmSignUp(let info): + print("Confirm signup additional info \(String(describing: info))") + + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + case .done: + + // Use has successfully signed in to the app + print("Signin complete") + } + } catch let error as AuthError{ + print ("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +The `nextStep` property is of enum type `AuthSignInStep`. Depending on its value, your code should take one of the following actions: + +## Confirm sign-in with SMS MFA +If the next step is `confirmSignInWithSMSMFACode`, Amplify Auth has sent the user a random code over SMS, and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. + +Note: the signin result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial phone number of the SMS recipient. + +#### [Async/Await] + +```swift +func confirmSignIn(confirmationCodeFromUser: String) async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: confirmationCodeFromUser) + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } catch let error as AuthError { + print("Confirm sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmSignIn(confirmationCodeFromUser: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: confirmationCodeFromUser) + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } +} +``` + +## Confirm sign-in with TOTP MFA + +If the next step is `confirmSignInWithTOTPCode`, you should prompt the user to enter the TOTP code from their associated authenticator app during set up. The code is a six-digit number that changes every 30 seconds. The user must enter the code before the 30-second window expires. + +After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. + +#### [Async/Await] + +```swift +func confirmSignIn(totpCode: String) async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: totpCode) + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } catch { + print("Confirm sign in failed \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmSignIn(totpCode: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: totpCode) + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } +} +``` + +## Confirm sign-in with Email MFA +If the next step is `confirmSignInWithOTP`, Amplify Auth has sent a random code to the user's email address, and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. + +> **Info:** **Note:** the sign-in result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial email address of the recipient. + +#### [Async/Await] + +```swift +func confirmSignIn(confirmationCodeFromUser: String) async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: confirmationCodeFromUser) + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } catch let error as AuthError { + print("Confirm sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmSignIn(confirmationCodeFromUser: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: confirmationCodeFromUser) + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } +} +``` + +## Continue sign-in with MFA Selection + +If the next step is `continueSignInWithMFASelection`, the user must select the MFA method to use. Amplify Auth currently supports SMS, TOTP, and email as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. + +#### [Async/Await] + +```swift +func confirmSignInWithTOTPAsMFASelection() async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: MFAType.totp.challengeResponse) + + if case .confirmSignInWithTOTPCode = signInResult.nextStep { + print("Received next step as confirm sign in with TOTP") + } + + } catch { + print("Confirm sign in failed \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmSignInWithTOTPAsMFASelection() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn( + challengeResponse: MFAType.totp.challengeResponse) + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + if case .confirmSignInWithTOTPCode = signInResult.nextStep { + print("Received next step as confirm sign in with TOTP") + } + } +} +``` + +## Continue sign-in with Email Setup +If the next step is `continueSignInWithEmailMFASetup`, then the user must provide an email address to complete the sign in process. Once this value has been collected from the user, call the `confirmSignIn` API to continue. + +```swift +// Confirm sign in with Email Setup +case .continueSignInWithEmailMFASetup: + print("Received next step as continue sign in by setting up email MFA") + + // Prompt the user to enter the email address they wish to use for MFA + // Then invoke `confirmSignIn` api with the email address +``` + +## Continue sign-in with TOTP Setup + +If the next step is `continueSignInWithTOTPSetup`, then the user must provide a TOTP code to complete the sign in process. The step returns an associated value of type `TOTPSetupDetails` which would be used for generating TOTP. `TOTPSetupDetails` provides a helper method called `getSetupURI` that can be used to generate a URI, which can be used by native password managers for TOTP association. For example. if the URI is used on Apple platforms, it will trigger the platform's native password manager to associate TOTP with the account. For more advanced use cases, `TOTPSetupDetails` also contains the `sharedSecret` that will be used to either generate a QR code or can be manually entered into an authenticator app. + +Once the authenticator app is set up, the user can generate a TOTP code and provide it to the library to complete the sign in process. + +```swift +// Confirm sign in with TOTP setup +case .continueSignInWithTOTPSetup(let setUpDetails): + + /// appName parameter will help distinguish the account in the Authenticator app + let setupURI = try setUpDetails.getSetupURI(appName: ">") + + print("TOTP Setup URI: \(setupURI)") +``` + +#### [Async/Await] + +```swift +func confirmSignInWithTOTPSetup(totpCodeFromAuthenticatorApp: String) async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: totpCodeFromAuthenticatorApp) + + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } catch { + print("Confirm sign in failed \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmSignInWithTOTPSetup(totpCodeFromAuthenticatorApp: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn( + challengeResponse: totpCodeFromAuthenticatorApp) + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } +} +``` + +## Continue sign-in with MFA Setup Selection + +If the next step is `continueSignInWithMFASetupSelection`, the user must indicate which of the available MFA methods they would like to setup. After the user selects an MFA method to setup, your implementation must pass the selected MFA method to the `confirmSignIn` API. + +#### [Async/Await] + +```swift +func continueSignInWithEmailMFASetupSelection() async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: MFAType.email.challengeResponse) + + if case .confirmSignInWithTOTPCode = signInResult.nextStep { + print("Received next step as confirm sign in with TOTP") + } + + } catch { + print("Confirm sign in failed \(error)") + } +} +``` + +#### [Combine] + +```swift +func continueSignInWithEmailMFASetupSelection() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn( + challengeResponse: MFAType.email.challengeResponse) + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + if case .confirmSignInWithTOTPCode = signInResult.nextStep { + print("Received next step as confirm sign in with TOTP") + } + } +} +``` + +## Confirm sign-in with custom challenge + +If the next step is `confirmSignInWithCustomChallenge`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the Lambda trigger you setup when you configured a [custom sign in flow](/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/#sign-in-a-user). To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the `confirmSignIn` API. + +#### [Async/Await] + +```swift +func confirmSignIn(challengeAnswerFromUser: String) async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: challengeAnswerFromUser) + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } catch let error as AuthError { + print("Confirm sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmSignIn(challengeAnswerFromUser: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: challengeAnswerFromUser) + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } +} +``` + +> **Warning:** **Special Handling on `confirmSignIn`** +> +> During a confirmSignIn call if `failAuthentication=true` is returned by the Lambda function the session of the request gets invalidated by cognito, a NotAuthorizedException is returned and a new signIn call is expected via Amplify.Auth.signIn +> +> ```swift +Exception: notAuthorized{message=Failed since user is not authorized., cause=NotAuthorizedException(message=Invalid session for the user.), recoverySuggestion=Check whether the given values are correct and the user is authorized to perform the operation.} +``` + +## Confirm sign-in with new password + +If the next step is `confirmSignInWithNewPassword`, Amplify Auth requires a new password for the user before they can proceed. Prompt the user for a new password and pass it to the `confirmSignIn` API. + +#### [Async/Await] + +```swift +func confirmSignIn(newPasswordFromUser: String) async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: newPasswordFromUser) + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } catch let error as AuthError { + print("Confirm sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmSignIn(newPasswordFromUser: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: newPasswordFromUser) + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + if signInResult.isSignedIn { + print("Confirm sign in succeeded. The user is signed in.") + } else { + print("Confirm sign in succeeded.") + print("Next step: \(signInResult.nextStep)") + // Switch on the next step to take appropriate actions. + // If `signInResult.isSignedIn` is true, the next step + // is 'done', and the user is now signed in. + } + } +} +``` + +## Reset password + +If you receive `resetPassword`, authentication flow could not proceed without resetting the password. The next step is to invoke `resetPassword` api and follow the reset password flow. + +#### [Async/Await] + +```swift +func resetPassword(username: String) async { + do { + let resetPasswordResult = try await Amplify.Auth.resetPassword(for: username) + print("Reset password succeeded.") + print("Next step: \(resetPasswordResult.nextStep)") + } catch let error as AuthError { + print("Reset password failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func resetPassword(username: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.resetPassword(for: username) + }.sink { + if case let .failure(authError) = $0 { + print("Reset password failed \(authError)") + } + } + receiveValue: { resetPasswordResult in + print("Reset password succeeded.") + print("Next step: \(resetPasswordResult.nextStep)") + } +} +``` + +## Confirm Signup + +If you receive `confirmSignUp` as a next step, sign up could not proceed without confirming user information such as email or phone number. The next step is to invoke the `confirmSignUp` API and follow the confirm signup flow. + +#### [Async/Await] + +```swift +func confirmSignUp(for username: String, with confirmationCode: String) async { + do { + let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") + } catch let error as AuthError { + print("An error occurred while confirming sign up \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignUp(for: username, confirmationCode: confirmationCode) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while confirming sign up \(authError)") + } + } + receiveValue: { _ in + print("Confirm signUp succeeded") + } +} +``` + +## Done + +Signin flow is complete when you get `done`. This means the user is successfully authenticated. As a convenience, the SignInResult also provides the `isSignedIn` property, which will be true if the next step is `done`. + + +--- + +--- +title: "Manage users" +section: "build-a-backend/auth" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-01T21:28:13.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/" +--- + + + +--- + +--- +title: "With admin actions" +section: "build-a-backend/auth/manage-users" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-02T01:41:16.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/with-admin-actions/" +--- + +Amplify Auth can be managed with the [AWS SDK's `@aws-sdk/client-cognito-identity-provider` package](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/cognito-identity-provider/). This package is intended to use server-side, and can be used within a Function. This example focuses on the `addUserToGroup` action and will be defined as a [custom mutation](/[platform]/build-a-backend/data/custom-business-logic/#step-1---define-a-custom-query-or-mutation). + +To get started, create an "ADMINS" group that will be used to authorize the mutation: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" + +export const auth = defineAuth({ + loginWith: { + email: true, + }, + // highlight-next-line + groups: ["ADMINS"] +}) +``` + +Next, create the Function resource: + +```ts title="amplify/data/add-user-to-group/resource.ts" +import { defineFunction } from "@aws-amplify/backend" + +export const addUserToGroup = defineFunction({ + name: "add-user-to-group", +}) +``` + +Then, in your auth resources, grant access for the function to perform the `addUserToGroup` action. [Learn more about granting access to auth resources](/[platform]/build-a-backend/auth/grant-access-to-auth-resources). + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" +// highlight-next-line +import { addUserToGroup } from "../data/add-user-to-group/resource" + +export const auth = defineAuth({ + loginWith: { + email: true, + }, + groups: ["ADMINS"], + // highlight-start + access: (allow) => [ + allow.resource(addUserToGroup).to(["addUserToGroup"]) + ], + // highlight-end +}) +``` + +You're now ready to define the custom mutation. Here you will use the newly-created `addUserToGroup` function resource to handle the `addUserToGroup` mutation. This mutation can only be called by a user in the "ADMINS" group. + +```ts title="amplify/data/resource.ts" +import type { ClientSchema } from "@aws-amplify/backend" +import { a, defineData } from "@aws-amplify/backend" +import { addUserToGroup } from "./resource" + +const schema = a.schema({ + addUserToGroup: a + .mutation() + .arguments({ + userId: a.string().required(), + groupName: a.string().required(), + }) + .authorization((allow) => [allow.group("ADMINS")]) + .handler(a.handler.function(addUserToGroup)) + .returns(a.json()) +}) + +export type Schema = ClientSchema + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "iam", + }, +}) +``` + +Lastly, create the function's handler using the exported client schema to type the handler function, and the generated `env` to specify the user pool ID you'd like to interact with: + +```ts title="amplify/data/add-user-to-group/handler.ts" +import type { Schema } from "../resource" +import { env } from "$amplify/env/add-user-to-group" +import { + AdminAddUserToGroupCommand, + CognitoIdentityProviderClient, +} from "@aws-sdk/client-cognito-identity-provider" + +type Handler = Schema["addUserToGroup"]["functionHandler"] +const client = new CognitoIdentityProviderClient() + +export const handler: Handler = async (event) => { + const { userId, groupName } = event.arguments + const command = new AdminAddUserToGroupCommand({ + Username: userId, + GroupName: groupName, + UserPoolId: env.AMPLIFY_AUTH_USERPOOL_ID, + }) + const response = await client.send(command) + return response +} +``` + + +In your frontend, use the generated client to call your mutation using the group name and the user's ID. + + +```ts title="src/client.ts" +import type { Schema } from "../amplify/data/resource" +import { generateClient } from "aws-amplify/data" + +const client = generateClient() + +await client.mutations.addUserToGroup({ + groupName: "ADMINS", + userId: "5468d468-4061-70ed-8870-45c766d26225", +}) +``` + + + + + + + + + + + + +--- + +--- +title: "Manage passwords" +section: "build-a-backend/auth/manage-users" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-10-14T15:02:39.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/manage-passwords/" +--- + +Amplify Auth provides a secure way for your users to change their password or recover a forgotten password. + +## Understand password default settings + +By default, your users can retrieve access to their accounts if they forgot their password by using either their phone or email. The following are the default account recovery methods used when either `phone` or `email` are used as login options. + +| Login option | User account verification channel | +| ------------------- | --------------------------------- | +| `phone` | Phone Number | +| `email` | Email | +| `email` and `phone` | Email | + +## Reset Password + +To reset a user's password, use the `resetPassword` API which will send a reset code to the destination (e.g. email or SMS) based on the user's settings. + + +```ts +import { resetPassword } from 'aws-amplify/auth'; + +const output = await resetPassword({ + username: "hello@mycompany.com" +}); + +const { nextStep } = output; +switch (nextStep.resetPasswordStep) { + case 'CONFIRM_RESET_PASSWORD_WITH_CODE': + const codeDeliveryDetails = nextStep.codeDeliveryDetails; + console.log( + `Confirmation code was sent to ${codeDeliveryDetails.deliveryMedium}` + ); + // Collect the confirmation code from the user and pass to confirmResetPassword. + break; + case 'DONE': + console.log('Successfully reset password.'); + break; +} +``` + + +```dart +Future resetPassword(String username) async { + try { + final result = await Amplify.Auth.resetPassword( + username: username, + ); + await _handleResetPasswordResult(result); + } on AuthException catch (e) { + safePrint('Error resetting password: ${e.message}'); + } +} +``` + +```dart +Future _handleResetPasswordResult(ResetPasswordResult result) async { + switch (result.nextStep.updateStep) { + case AuthResetPasswordStep.confirmResetPasswordWithCode: + final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; + _handleCodeDelivery(codeDeliveryDetails); + break; + case AuthResetPasswordStep.done: + safePrint('Successfully reset password'); + break; + } +} +``` + + + +#### [Java] + +```java +Amplify.Auth.resetPassword( + "username", + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.resetPassword("username", + { Log.i("AuthQuickstart", "Password reset OK: $it") }, + { Log.e("AuthQuickstart", "Password reset failed", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.resetPassword("username") + Log.i("AuthQuickstart", "Password reset OK: $result") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Password reset failed", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.resetPassword("username") + .subscribe( + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func resetPassword(username: String) async { + do { + let resetResult = try await Amplify.Auth.resetPassword(for: username) + switch resetResult.nextStep { + case .confirmResetPasswordWithCode(let deliveryDetails, let info): + print("Confirm reset password with code send to - \(deliveryDetails) \(String(describing: info))") + case .done: + print("Reset completed") + } + } catch let error as AuthError { + print("Reset password failed with error \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func resetPassword(username: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.resetPassword(for: username) + }.sink { + if case let .failure(authError) = $0 { + print("Reset password failed with error \(authError)") + } + } + receiveValue: { resetResult in + switch resetResult.nextStep { + case .confirmResetPasswordWithCode(let deliveryDetails, let info): + print("Confirm reset password with code send to - \(deliveryDetails) \(String(describing: info))") + case .done: + print("Reset completed") + } + } +} +``` + +Usually, resetting the password require you to verify that it is the actual user that tried to reset the password. The next step above will be `.confirmResetPasswordWithCode`. + +If you would like to display a more specific view or messaging to your users based the error that occurred, you can handle this by downcasting the `underlyingError` to `AWSCognitoAuthError`. + +```swift +if let authError = error as? AuthError, + let cognitoAuthError = authError.underlyingError as? AWSCognitoAuthError { + switch cognitoAuthError { + case .userNotFound: + print("User not found") + case .invalidParameter: + print("Invalid Parameter) + default: + break + } +} +``` + + +To complete the password reset process, invoke the `confirmResetPassword` API with the code your user received and the new password they want to set. + + +```ts +import { confirmResetPassword } from 'aws-amplify/auth'; + +await confirmResetPassword({ + username: "hello@mycompany.com", + confirmationCode: "123456", + newPassword: "hunter3", +}); +``` + + +```dart +Future confirmResetPassword({ + required String username, + required String newPassword, + required String confirmationCode, +}) async { + try { + final result = await Amplify.Auth.confirmResetPassword( + username: username, + newPassword: newPassword, + confirmationCode: confirmationCode, + ); + safePrint('Password reset complete: ${result.isPasswordReset}'); + } on AuthException catch (e) { + safePrint('Error resetting password: ${e.message}'); + } +} +``` + + + +#### [Java] + +```java +Amplify.Auth.confirmResetPassword( + "Username", + "NewPassword123", + "confirmation code you received", + () -> Log.i("AuthQuickstart", "New password confirmed"), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.confirmResetPassword("Username", "NewPassword123", "confirmation code", + { Log.i("AuthQuickstart", "New password confirmed") }, + { Log.e("AuthQuickstart", "Failed to confirm password reset", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + Amplify.Auth.confirmResetPassword("Username", "NewPassword123", "code you received") + Log.i("AuthQuickstart", "New password confirmed") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to confirm password reset", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.confirmResetPassword("Username","NewPassword123", "confirmation code") + .subscribe( + () -> Log.i("AuthQuickstart", "New password confirmed"), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func confirmResetPassword( + username: String, + newPassword: String, + confirmationCode: String +) async { + do { + try await Amplify.Auth.confirmResetPassword( + for: username, + with: newPassword, + confirmationCode: confirmationCode + ) + print("Password reset confirmed") + } catch let error as AuthError { + print("Reset password failed with error \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func confirmResetPassword( + username: String, + newPassword: String, + confirmationCode: String +) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmResetPassword( + for: username, + with: newPassword, + confirmationCode: confirmationCode + ) + }.sink { + if case let .failure(authError) = $0 { + print("Reset password failed with error \(authError)") + } + } + receiveValue: { + print("Password reset confirmed") + } +} +``` + + + +## Update password + +You can update a signed in user's password using the `updatePassword` API. + + +```ts +import { updatePassword } from 'aws-amplify/auth'; + +await updatePassword({ + oldPassword: "hunter2", + newPassword: "hunter3", +}); +``` + + +```dart +Future updatePassword({ + required String oldPassword, + required String newPassword, +}) async { + try { + await Amplify.Auth.updatePassword( + oldPassword: oldPassword, + newPassword: newPassword, + ); + } on AuthException catch (e) { + safePrint('Error updating password: ${e.message}'); + } +} +``` + + + +#### [Java] + +```java +Amplify.Auth.updatePassword( + "existingPassword", + "newPassword", + () -> Log.i("AuthQuickstart", "Updated password successfully"), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.updatePassword("existingPassword", "newPassword", + { Log.i("AuthQuickstart", "Updated password successfully") }, + { Log.e("AuthQuickstart", "Password update failed", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + Amplify.Auth.updatePassword("existingPassword", "newPassword") + Log.i("AuthQuickstart", "Updated password successfully") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Password update failed", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.updatePassword("existingPassword", "newPassword") + .subscribe( + () -> Log.i("AuthQuickstart", "Updated password successfully"), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func changePassword(oldPassword: String, newPassword: String) async { + do { + try await Amplify.Auth.update(oldPassword: oldPassword, to: newPassword) + print("Change password succeeded") + } catch let error as AuthError { + print("Change password failed with error \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func changePassword(oldPassword: String, newPassword: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.update(oldPassword: oldPassword, to: newPassword) + }.sink { + if case let .failure(authError) = $0 { + print("Change password failed with error \(authError)") + } + } + receiveValue: { + print("Change password succeeded") + } +} +``` + + + +### Override default user account verification channel + +You can always change the channel used by your authentication resources by overriding the following setting. + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from '@aws-amplify/backend'; + +export const auth = defineAuth({ + loginWith: { + email: true + }, +// highlight-start + accountRecovery: 'EMAIL_ONLY' +// highlight-end +}); +``` + +## Override default password policy + +By default your password policy is set to the following: + +- `MinLength`: 8 characters +- `requireLowercase`: true +- `requireUppercase`: true +- `requireNumbers`: true +- `requireSymbols`: true +- `tempPasswordValidity`: 3 days + +You can customize the password format acceptable by your auth resource by modifying the underlying `cfnUserPool` resource: + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; + +const backend = defineBackend({ + auth, +}); +// extract L1 CfnUserPool resources +const { cfnUserPool } = backend.auth.resources.cfnResources; +// modify cfnUserPool policies directly +cfnUserPool.policies = { + passwordPolicy: { + minimumLength: 32, + requireLowercase: true, + requireNumbers: true, + requireSymbols: true, + requireUppercase: true, + temporaryPasswordValidityDays: 20, + }, +}; +``` + +--- + +--- +title: "Manage WebAuthn credentials" +section: "build-a-backend/auth/manage-users" +platforms: ["android", "angular", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-06-24T13:29:28.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/manage-webauthn-credentials/" +--- + + +> **Warning:** WebAuthn registration and authentication are not currently supported on React Native, other passwordless features are fully supported. + + +Amplify Auth uses passkeys as the credential mechanism for WebAuthn. The following APIs allow users to register, keep track of, and delete the passkeys associated with their Cognito account. + +[Learn more about using passkeys with Amplify](/[platform]/build-a-backend/auth/concepts/passwordless/#webauthn-passkey). + +## Associate WebAuthn credentials + + +> **Warning:** Registering a passkey is supported on Android 9 (API level 28) and above. + + +Note that users must be authenticated to register a passkey. That also means users cannot create a passkey during sign up; consequently, they must have at least one other first factor authentication mechanism associated with their account to use WebAuthn. + +You can associate a passkey using the following API: + + +```ts +import { associateWebAuthnCredential} from 'aws-amplify/auth'; + +await associateWebAuthnCredential(); + +``` + + + +#### [Java] + +```java +Amplify.Auth.associateWebAuthnCredential( + activity, + () -> Log.i("AuthQuickstart", "Associated credential"), + error -> Log.e("AuthQuickstart", "Failed to associate credential", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.associateWebAuthnCredential( + activity, + { Log.i("AuthQuickstart", "Associated credential") }, + { Log.e("AuthQuickstart", "Failed to associate credential", error) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.associateWebAuthnCredential(activity) + Log.i("AuthQuickstart", "Associated credential") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to associate credential", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.associateWebAuthnCredential(activity) + .subscribe( + result -> Log.i("AuthQuickstart", "Associated credential"), + error -> Log.e("AuthQuickstart", "Failed to associate credential", error) + ); +``` + +You must supply an `Activity` instance so that Amplify can display the PassKey UI in your application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack). + + + +#### [Async/Await] + +```swift +func associateWebAuthNCredentials() async { + do { + try await Amplify.Auth.associateWebAuthnCredential() + print("WebAuthn credential was associated") + } catch { + print("Associate WebAuthn Credential failed: \(error)") + } +} +``` + +#### [Combine] + +```swift +func associateWebAuthNCredentials() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.associateWebAuthnCredential() + }.sink { + print("Associate WebAuthn Credential failed: \($0)") + } + receiveValue: { _ in + print("WebAuthn credential was associated") + } +} +``` + + + +The user will be prompted to register a passkey using their local authenticator. Amplify will then associate that passkey with Cognito. + +## List WebAuthn credentials + +You can list registered passkeys using the following API: + + +```ts +import { listWebAuthnCredentials } from 'aws-amplify/auth'; + +const result = await listWebAuthnCredentials(); + +for (const credential of result.credentials) { + console.log(`Credential ID: ${credential.credentialId}`); + console.log(`Friendly Name: ${credential.friendlyCredentialName}`); + console.log(`Relying Party ID: ${credential.relyingPartyId}`); + console.log(`Created At: ${credential.createdAt}`); +} + +``` + + + +#### [Async/Await] + +```swift +func listWebAuthNCredentials() async { + do { + let result = try await Amplify.Auth.listWebAuthnCredentials( + options: .init(pageSize: 5)) + + for credential in result.credentials { + print("Credential ID: \(credential.credentialId)") + print("Created At: \(credential.createdAt)") + print("Relying Party Id: \(credential.relyingPartyId)") + if let friendlyName = credential.friendlyName { + print("Friendly name: \(friendlyName)") + } + } + + // Fetch the next page + if let nextToken = result.nextToken { + let nextResult = try await Amplify.Auth.listWebAuthnCredentials( + options: .init( + pageSize: 5, + nextToken: nextToken)) + } + } catch { + print("Associate WebAuthn Credential failed: \(error)") + } +} +``` + +#### [Combine] + +```swift +func listWebAuthNCredentials() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.listWebAuthnCredentials( + options: .init(pageSize: 5)) + }.sink { + print("List WebAuthn Credential failed: \($0)") + } + receiveValue: { result in + for credential in result.credentials { + print("Credential ID: \(credential.credentialId)") + print("Created At: \(credential.createdAt)") + print("Relying Party Id: \(credential.relyingPartyId)") + if let friendlyName = credential.friendlyName { + print("Friendly name: \(friendlyName)") + } + } + + if let nextToken = result.nextToken { + // Fetch the next page + } + } +} +``` + + + + +#### [Java] + +```java +Amplify.Auth.listWebAuthnCredentials( + result -> result.getCredentials().forEach(credential -> { + Log.i("AuthQuickstart", "Credential ID: " + credential.getCredentialId()); + Log.i("AuthQuickstart", "Friendly Name: " + credential.getFriendlyName()); + Log.i("AuthQuickstart", "Relying Party ID: " + credential.getRelyingPartyId()); + Log.i("AuthQuickstart", "Created At: " + credential.getCreatedAt()); + }), + error -> Log.e("AuthQuickstart", "Failed to list credentials", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.listWebAuthnCredentials( + { result -> + result.credentials.forEach { credential -> + Log.i("AuthQuickstart", "Credential ID: ${credential.credentialId}") + Log.i("AuthQuickstart", "Friendly Name: ${credential.friendlyName}") + Log.i("AuthQuickstart", "Relying Party ID: ${credential.relyingPartyId}") + Log.i("AuthQuickstart", "Created At: ${credential.createdAt}") + } + }, + { error -> Log.e("AuthQuickstart", "Failed to list credentials", error) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.listWebAuthnCredentials() + result.credentials.forEach { credential -> + Log.i("AuthQuickstart", "Credential ID: ${credential.credentialId}") + Log.i("AuthQuickstart", "Friendly Name: ${credential.friendlyName}") + Log.i("AuthQuickstart", "Relying Party ID: ${credential.relyingPartyId}") + Log.i("AuthQuickstart", "Created At: ${credential.createdAt}") + } +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to list credentials", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.listWebAuthnCredentials() + .subscribe( + result -> result.getCredentials().forEach(credential -> { + Log.i("AuthQuickstart", "Credential ID: " + credential.getCredentialId()); + Log.i("AuthQuickstart", "Friendly Name: " + credential.getFriendlyName()); + Log.i("AuthQuickstart", "Relying Party ID: " + credential.getRelyingPartyId()); + Log.i("AuthQuickstart", "Created At: " + credential.getCreatedAt()); + }), + error -> Log.e("AuthQuickstart", "Failed to list credentials", error) + ); +``` + + + +## Delete WebAuthn credentials + +You can delete a passkey with the following API: + + +```ts +import { deleteWebAuthnCredential } from 'aws-amplify/auth'; + +const id = "credential-id-to-delete"; + +await deleteWebAuthnCredential({ + credentialId: id +}); +``` + + + +#### [Async/Await] + +```swift +func deleteWebAuthNCredentials(credentialId: String) async { + do { + try await Amplify.Auth.deleteWebAuthnCredential(credentialId: credentialId) + print("WebAuthn credential was deleted") + } catch { + print("Delete WebAuthn Credential failed: \(error)") + } +} +``` + +#### [Combine] + +```swift +func deleteWebAuthNCredentials(credentialId: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.deleteWebAuthnCredential(credentialId: credentialId) + }.sink { + print("Delete WebAuthn Credential failed: \($0)") + } + receiveValue: { _ in + print("WebAuthn credential was deleted") + } +} +``` + + + + +#### [Java] + +```java +Amplify.Auth.deleteWebAuthnCredential( + credentialId, + (result) -> Log.i("AuthQuickstart", "Deleted credential"), + error -> Log.e("AuthQuickstart", "Failed to delete credential", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.deleteWebAuthnCredential( + credentialId, + { Log.i("AuthQuickstart", "Deleted credential") }, + { Log.e("AuthQuickstart", "Failed to delete credential", error) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.deleteWebAuthnCredential(credentialId) + Log.i("AuthQuickstart", "Deleted credential") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to delete credential", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.deleteWebAuthnCredential(credentialId) + .subscribe( + result -> Log.i("AuthQuickstart", "Deleted credential"), + error -> Log.e("AuthQuickstart", "Failed to delete credential", error) + ); +``` + +The delete passkey API has only the required `credentialId` as input, and it does not return a value. + + + +## Practical example + +Here is a code example that uses the list and delete APIs together. In this example, the user has 3 passkeys registered. They want to list all passkeys while using a `pageSize` of 2 as well as delete the first passkey in the list. + +```ts +import { + listWebAuthnCredentials, + deleteWebAuthnCredential +} from 'aws-amplify/auth'; + +let passkeys = []; + +const result = await listWebAuthnCredentials({ pageSize: 2 }); + +passkeys.push(...result.credentials); + +const nextPage = await listWebAuthnCredentials({ + pageSize: 2, + nextToken: result.nextToken, +}); + +passkeys.push(...nextPage.credentials); + +const id = passkeys[0].credentialId; + +await deleteWebAuthnCredential({ + credentialId: id +}); +``` + + +--- + +--- +title: "Manage devices" +section: "build-a-backend/auth/manage-users" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-08-20T18:34:28.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/manage-devices/" +--- + +Amplify Auth enables you to track devices your users use for auditing, MFA, and more. Before you begin it is important to understand the terminology for device statuses: + +- **Tracked:** Every time the user signs in with a new device, the client is given the device key at the end of a successful authentication event. We use this device key to generate a salt and password verifier which is used to call the ConfirmDevice API. At this point, the device is considered to be _tracked_. Once the device is in a tracked state, you can use the Amazon Cognito console to see the time it started to be tracked, last authentication time, and other information about that device. +- **Remembered:** Remembered devices are also tracked. During user authentication, the device key and secret pair assigned to a remembered device is used to authenticate the device to verify that it is the same device that the user previously used to sign in. +- **Not Remembered:** A not-remembered device is a tracked device where Cognito has been configured to require users to "Opt-in" to remember a device, but the user has not opt-ed in to having the device remembered. This use case is used for users signing into their application from a device that they don't own. +- **Forgotten:** a forgotten device is one removed from being remembered + +> **Info:** **Note:** [device tracking and remembering](https://aws.amazon.com/blogs/mobile/tracking-and-remembering-devices-using-amazon-cognito-your-user-pools/) features are not available when using federating sign-in with external providers as devices are tracked on the upstream identity provider. These features are also not available when using Cognito's Hosted UI. + +## Remember devices + +You can remember devices using the following: + + +```ts +import { rememberDevice } from 'aws-amplify/auth'; + +await rememberDevice(); +``` + + +```dart +Future rememberCurrentDevice() async { + try { + await Amplify.Auth.rememberDevice(); + safePrint('Remember device succeeded'); + } on AuthException catch (e) { + safePrint('Remember device failed with error: $e'); + } +} +``` + + + +#### [Java] + +```java +Amplify.Auth.rememberDevice( + () -> Log.i("AuthQuickStart", "Remember device succeeded"), + error -> Log.e("AuthQuickStart", "Remember device failed with error " + error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.rememberDevice( + { Log.i("AuthQuickStart", "Remember device succeeded") }, + { Log.e("AuthQuickStart", "Remember device failed with error", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + Amplify.Auth.rememberDevice() + Log.i("AuthQuickStart", "Remember device succeeded") +} catch (error: AuthException) { + Log.e("AuthQuickStart", "Remember device failed with error", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.rememberDevice() + .subscribe( + () -> Log.i("AuthQuickStart", "Remember device succeeded"), + error -> Log.e("AuthQuickStart", "Remember device failed with error " + error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func rememberDevice() async { + do { + try await Amplify.Auth.rememberDevice() + print("Remember device succeeded") + } catch let error as AuthError { + print("Remember device failed with error \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func rememberDevice() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.rememberDevice() + }.sink { + if case let .failure(authError) = $0 { + print("Remember device failed with error \(authError)") + } + } + receiveValue: { + print("Remember device succeeded") + } +} +``` + + + +## Forget devices + +You can also forget devices but note that forgotten devices are neither remembered nor tracked. + + +```ts +import { forgetDevice } from 'aws-amplify/auth'; + +await forgetDevice(); +``` + + + +#### [Current Device] + +```dart +Future forgetCurrentDevice() async { + try { + await Amplify.Auth.forgetDevice(); + safePrint('Forget device succeeded'); + } on AuthException catch (e) { + safePrint('Forget device failed with error: $e'); + } +} +``` + +#### [Specific Device] + +```dart +// A device that was fetched via Amplify.Auth.fetchDevices() +Future forgetSpecificDevice(AuthDevice myDevice) async { + try { + await Amplify.Auth.forgetDevice(myDevice); + safePrint('Forget device succeeded'); + } on AuthException catch (e) { + safePrint('Forget device failed with error: $e'); + } +} +``` + + + + +#### [Java] + +```java +Amplify.Auth.forgetDevice( + () -> Log.i("AuthQuickStart", "Forget device succeeded"), + error -> Log.e("AuthQuickStart", "Forget device failed with error " + error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.forgetDevice( + { Log.i("AuthQuickStart", "Forget device succeeded") }, + { Log.e("AuthQuickStart", "Forget device failed with error", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + Amplify.Auth.forgetDevice() + Log.i("AuthQuickStart", "Forget device succeeded") +} catch (error: AuthException) { + Log.e("AuthQuickStart", "Forget device failed with error", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.forgetDevice() + .subscribe( + () -> Log.i("AuthQuickStart", "Forget device succeeded"), + error -> Log.e("AuthQuickStart", "Forget device failed with error " + error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func forgetDevice() async { + do { + try await Amplify.Auth.forgetDevice() + print("Forget device succeeded") + } catch let error as AuthError { + print("Forget device failed with error \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func forgetDevice() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.forgetDevice() + }.sink { + if case let .failure(authError) = $0 { + print("Forget device failed with error \(authError)") + } + } + receiveValue: { + print("Forget device succeeded") + } +} +``` + + + +## Fetch devices + +You can fetch a list of remembered devices by using the following: + + +```ts +import { fetchDevices } from 'aws-amplify/auth'; + +const output = await fetchDevices(); +``` + + +```dart +Future fetchAllDevices() async { + try { + final devices = await Amplify.Auth.fetchDevices(); + for (final device in devices) { + safePrint('Device: $device'); + } + } on AuthException catch (e) { + safePrint('Fetch devices failed with error: $e'); + } +} +``` + + + + +#### [Java] + +```java +Amplify.Auth.fetchDevices( + devices -> { + for (AuthDevice device : devices) { + Log.i("AuthQuickStart", "Device: " + device); + } + }, + error -> Log.e("AuthQuickStart", "Fetch devices failed with error: " + error.toString())); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.fetchDevices( + { devices -> + devices.forEach { Log.i("AuthQuickStart", "Device: " + it) } + }, + { Log.e("AuthQuickStart", "Fetch devices failed with error", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + Amplify.Auth.fetchDevices().forEach { device -> + Log.i("AuthQuickStart", "Device: $device") + } +} catch (error: AuthException) { + Log.e("AuthQuickStart", "Fetch devices failed with error", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.fetchDevices() + .subscribe( + device -> Log.i("AuthQuickStart", "Device: " + device); + error -> Log.e("AuthQuickStart", "Fetch devices failed with error: " + error.toString()) + ); +``` + + + + +#### [Async/Await] + +```swift +func fetchDevices() async { + do { + let fetchDeviceResult = try await Amplify.Auth.fetchDevices() + for device in fetchDeviceResult { + print(device.id) + } + } catch let error as AuthError { + print("Fetch devices failed with error \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func fetchDevices() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.fetchDevices() + }.sink { + if case let .failure(authError) = $0 { + print("Fetch devices failed with error \(authError)") + } + } + receiveValue: { fetchDeviceResult in + for device in fetchDeviceResult { + print(device.id) + } + } +} +``` + + + + +## Fetch the current device + +You can fetch the current device by using the following: + +```dart +Future fetchCurrentUserDevice() async { + try { + final device = await Amplify.Auth.fetchCurrentDevice(); + safePrint('Device: $device'); + } on AuthException catch (e) { + safePrint('Get current device failed with error: $e'); + } +} +``` + + +You can now set up devices to be remembered, forgotten, and fetched. + +--- + +--- +title: "Manage users with Amplify console" +section: "build-a-backend/auth/manage-users" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-08-06T19:20:15.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/with-amplify-console/" +--- + +The **User management** page in the Amplify console provides a user-friendly interface for managing your application's users. You can create and manage users and groups, edit user attributes, and suspend users. + +If you have not yet created an **auth** resource, visit the [Auth setup guide](/[platform]/build-a-backend/auth/set-up-auth/). + +## Access User management + +After you've deployed your auth resource, you can access the manager on Amplify Console. + +1. Log in to the [Amplify console](https://console.aws.amazon.com/amplify/home) and choose your app. +2. Select the branch you would like to access. +3. Select **Authentication** from the left navigation bar. +4. Then, select **User management**. + +### To create a user + +1. On the **User management** page, select **Users** tab. +2. Select **Create user**. +3. In the **Create user** window, for Unique identifier enter a email address, username, or phone number. For Temporary password enter a password. +4. Choose Create user. + + + +A user can be confirmed by using the [pre-built UI components](/[platform]/build-a-backend/auth/connect-your-frontend/using-the-authenticator/) and [Amplify libraries](/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/). + + + +## To create a group + +1. On the **User management** page, choose the **Groups** tab and then choose **Create group**. +2. In the **Create group** window, for **Title** enter a name for the group. +3. Choose **Create group**. + +## To add a users to a group + +1. On the **User management** page, choose the **Groups** tab. +2. Select the name of the group to add users to. +3. Choose **Add users**. +4. In the **Add users to group** window, choose how you want to search for users to add from the **Search** menu. You can choose _Email_, _Phone number_, or _Username_. +5. Add one user or multiple users to add to the group and then choose **Add users**. + +## To delete a group + +1. On the **User management** page, choose the **Groups** tab. +2. In the **Groups** section, select the name of the group to delete. +3. Choose **Delete**. +4. A confirmation window is displayed. Enter _Delete_ and choose, **Confirm deletion**. + +--- + +--- +title: "Customize auth lifecycle" +section: "build-a-backend/auth" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-01T23:11:32.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/customize-auth-lifecycle/" +--- + + + +--- + +--- +title: "Custom auth flows" +section: "build-a-backend/auth/customize-auth-lifecycle" +platforms: ["android", "flutter", "swift"] +gen: 2 +last-updated: "2024-10-31T15:49:20.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/" +--- + + +The Auth category can be configured to perform a [custom authentication flow](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html) defined by you. The following guide shows how to setup a simple passwordless authentication flow. + +## Prerequisites +An application with Amplify libraries integrated and a minimum target of any of the following: +- **iOS 13.0**, using **Xcode 14.1** or later. +- **macOS 10.15**, using **Xcode 14.1** or later. +- **tvOS 13.0**, using **Xcode 14.3** or later. +- **watchOS 9.0**, using **Xcode 14.3** or later. +- **visionOS 1.0**, using **Xcode 15** or later. (Preview support - see below for more details.) + +For a full example, please follow the [project setup walkthrough](/[platform]/start/quickstart/). + + + +visionOS support is currently in **preview** and can be used by using the latest [Amplify Release](https://github.com/aws-amplify/amplify-swift/releases). +As new Xcode and visionOS versions are released, the support will be updated with any necessary fixes on a best effort basis. + + + + + +To use Auth in a macOS project, you'll need to enable the Keychain Sharing capability. In Xcode, navigate to **your application target** > **Signing & Capabilities** > **+ Capability**, then select **Keychain Sharing.** + +This capability is required because Auth uses the Data Protection Keychain on macOS as a platform best practice. See [TN3137: macOS keychain APIs and implementations](https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains) for more information on how Keychain works on macOS and the Keychain Sharing entitlement. + +For more information on adding capabilities to your application, see [Xcode Capabilities](https://developer.apple.com/documentation/xcode/capabilities). + + + +## Configure Auth + +The custom auth flow can be [configured manually](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). + +## Sign in a user + +Implement a UI to get the username from the user. After the user enters the username you can start the sign in flow by calling the following method: + +#### [Async/Await] + +```swift +func signIn(username: String) async { + do { + let options = AWSAuthSignInOptions(authFlowType: .customWithoutSRP) + let signInResult = try await Amplify.Auth.signIn(username: username, + options: .init(pluginOptions: options)) + if case .confirmSignInWithCustomChallenge(_) = signInResult.nextStep { + // Ask the user to enter the custom challenge. + } else { + print("Sign in succeeded") + } + } catch let error as AuthError { + print("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func signIn(username: String) -> AnyCancellable { + Amplify.Publisher.create { + let options = AWSAuthSignInOptions(authFlowType: .customWithoutSRP) + try await Amplify.Auth.signIn(username: username, + options: .init(pluginOptions: options)) + }.sink { + if case let .failure(authError) = $0 { + print("Sign in failed \(authError)") + } + } + receiveValue: { result in + if case .confirmSignInWithCustomChallenge(_) = result.nextStep { + // Ask the user to enter the custom challenge. + } else { + print("Sign in succeeded") + } + } +} +``` + +Since this is a custom authentication flow with a challenge, the result of the signin process has a next step `.confirmSignInWithCustomChallenge`. Implement a UI to allow the user to enter the custom challenge. + +## Confirm sign in with custom challenge + +To get a custom challenge from the user, create an appropriate UI for the user to submit the required value, and pass that value into the `confirmSignin()` API. + +#### [Async/Await] + +```swift +func customChallenge(response: String) async { + do { + _ = try await Amplify.Auth.confirmSignIn(challengeResponse: response) + print("Confirm sign in succeeded") + } catch let error as AuthError { + print("Confirm sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func customChallenge(response: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: response) + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { _ in + print("Confirm sign in succeeded") + } +} +``` + +You will know the sign in flow is complete if you see the following in your console window: + +```console +Confirm sign in succeeded +``` + +### Lambda Trigger Setup + +AWS Amplify now supports creating functions as part of its new backend experience. For more information on the Functions and how to start with them check out [Functions documentation](/[platform]/build-a-backend/functions/). In addition, more information on available triggers can be found in the [Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html). + +### Custom Auth Flow with Secure Remote Password (SRP) + +Cognito User Pool allows to start the custom authentication flow with SRP as the first step. If you would like to use this flow, setup Define Auth Lambda trigger to handle SRP_A as the first challenge as shown below: + +```javascript +exports.handler = (event, context) => { + if (event.request.session.length == 1 && + event.request.session[0].challengeName == 'SRP_A') { + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = 'PASSWORD_VERIFIER'; + } else if (event.request.session.length == 2 && + event.request.session[1].challengeName == 'PASSWORD_VERIFIER' && + event.request.session[1].challengeResult == true) { + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = 'CUSTOM_CHALLENGE'; + } else if (event.request.session.length == 3 && + event.request.session[2].challengeName == 'CUSTOM_CHALLENGE' && + event.request.session[2].challengeResult == true) { + event.response.issueTokens = true; + event.response.failAuthentication = false; + } else { + event.response.issueTokens = false; + event.response.failAuthentication = true; + } + context.done(null, event); +}; +``` + +If your lambda is setup to start with `SRP` as the first step, make sure to initiate the signIn process with `customWithSRP` as the authentication flow: + +```swift +let options = AWSAuthSignInOptions( + authFlowType: .customWithSRP) +let signInResult = try await Amplify.Auth.signIn( + username: username, + password: password, + options: .init(pluginOptions: options)) +``` + + +The Auth category can be configured to perform a [custom authentication flow](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html) defined by you. The following guide shows how to setup a simple passwordless authentication flow. + +## Prerequisites + +* An Android application targeting at least Android SDK API level 24 with Amplify libraries integrated + * For a full example of creating Android project, please follow the [project setup walkthrough](/[platform]/start/quickstart/) + +## Configure Auth + +The custom auth flow can be [configured manually](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). + +## Register a user + +The flow as mentioned above requires a username and a valid email id as parameters to register a user. Invoke the following api to initiate a sign up flow. + +#### [Java] + +```java +AuthSignUpOptions options = AuthSignUpOptions.builder() + .userAttribute(AuthUserAttributeKey.email(), "my@email.com") + .build(); +Amplify.Auth.signUp("username", "Password123", options, + result -> Log.i("AuthQuickStart", "Result: " + result.toString()), + error -> Log.e("AuthQuickStart", "Sign up failed", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val options = AuthSignUpOptions.builder() + .userAttribute(AuthUserAttributeKey.email(), "my@email.com") + .build() +Amplify.Auth.signUp("username", "Password123", options, + { Log.i("AuthQuickStart", "Sign up succeeded: $it") }, + { Log.e ("AuthQuickStart", "Sign up failed", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val options = AuthSignUpOptions.builder() + .userAttribute(AuthUserAttributeKey.email(), "my@email.com") + .build() +try { + val result = Amplify.Auth.signUp("username", "Password123", options) + Log.i("AuthQuickStart", "Result: $result") +} catch (error: AuthException) { + Log.e("AuthQuickStart", "Sign up failed", error) +} +``` + +#### [RxJava] + + ```java +RxAmplify.Auth.signUp( + "username", + "Password123", + AuthSignUpOptions.builder().userAttribute(AuthUserAttributeKey.email(), "my@email.com").build()) + .subscribe( + result -> Log.i("AuthQuickStart", "Result: " + result.toString()), + error -> Log.e("AuthQuickStart", "Sign up failed", error) + ); +``` + +The next step in the sign up flow is to confirm the user. A confirmation code will be sent to the email id provided during sign up. Enter the confirmation code received via email in the `confirmSignUp` call. + +#### [Java] + +```java +Amplify.Auth.confirmSignUp( + "username", + "the code you received via email", + result -> Log.i("AuthQuickstart", result.isSignUpComplete() ? "Confirm signUp succeeded" : "Confirm sign up not complete"), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.confirmSignUp( + "username", "the code you received via email", + { result -> + if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Confirm signUp succeeded") + } else { + Log.i("AuthQuickstart","Confirm sign up not complete") + } + }, + { Log.e("AuthQuickstart", "Failed to confirm sign up", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val code = "code you received via email" + val result = Amplify.Auth.confirmSignUp("username", code) + if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Signup confirmed") + } else { + Log.i("AuthQuickstart", "Signup confirmation not yet complete") + } +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to confirm signup", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.confirmSignUp("username", "the code you received via email") + .subscribe( + result -> Log.i("AuthQuickstart", result.isSignUpComplete() ? "Confirm signUp succeeded" : "Confirm sign up not complete"), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + +You will know the sign up flow is complete if you see the following in your console window: + +```console +Confirm signUp succeeded +``` + +## Sign in a user + +Implement a UI to get the username from the user. After the user enters the username you can start the sign in flow by calling the following method: + +#### [Java] + +```java +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP) + .build(); +Amplify.Auth.signIn( + "username", + "password", + options, + result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP) + .build() +Amplify.Auth.signIn( + "username", + "password", + options, + { result -> + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Sign in succeeded") + } else { + Log.i("AuthQuickstart", "Sign in not complete") + } + }, + { Log.e("AuthQuickstart", "Failed to sign in", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP) + .build() +try { + val result = Amplify.Auth.signIn("username", "password", options) + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Sign in succeeded") + } else { + Log.e("AuthQuickstart", "Sign in not complete") + } +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + +#### [RxJava] + +```java +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP) + .build(); +RxAmplify.Auth.signIn("username", "password", options) + .subscribe( + result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + +Since this is a custom authentication flow with a challenge, the result of the signin process has a next step `.confirmSignInWithCustomChallenge`. Implement a UI to allow the user to enter the custom challenge. + +## Confirm sign in with custom challenge + +Get the custom challenge (`1234` in this case) from the user and pass it to the `confirmSignin()` api. + +#### [Java] + +```java +Amplify.Auth.confirmSignIn( + "confirmation", + result -> Log.i("AuthQuickstart", "Confirm sign in succeeded: " + result.toString()), + error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.confirmSignIn("confirmation", + { Log.i("AuthQuickstart", "Confirm sign in succeeded: $it") }, + { Log.e("AuthQuickstart", "Failed to confirm sign in", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.confirmSignIn("confirmation") + Log.i("AuthQuickstart", "Confirm sign in succeeded: $result") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to confirm signin", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.confirmSignIn("confirmation") + .subscribe( + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + +You will know the sign in flow is complete if you see the following in your console window: + +```console +Confirm sign in succeeded +``` + +### Lambda Trigger Setup + +AWS Amplify now supports creating functions as part of the AWS Amplify. For more information on the Functions and how to start with them check out [Functions documentation](/[platform]/build-a-backend/functions/). In addition, more information on available triggers can be found in the [Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html). + +### Custom Auth Flow with Secure Remote Password (SRP) + +Cognito User Pool allows to start the custom authentication flow with SRP as the first step. If you would like to use this flow, setup Define Auth Lambda trigger to handle SRP_A as the first challenge as shown below: + +```javascript +exports.handler = (event, context) => { + if (event.request.session.length == 1 && + event.request.session[0].challengeName == 'SRP_A') { + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = 'PASSWORD_VERIFIER'; + } else if (event.request.session.length == 2 && + event.request.session[1].challengeName == 'PASSWORD_VERIFIER' && + event.request.session[1].challengeResult == true) { + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = 'CUSTOM_CHALLENGE'; + } else if (event.request.session.length == 3 && + event.request.session[2].challengeName == 'CUSTOM_CHALLENGE' && + event.request.session[2].challengeResult == true) { + event.response.issueTokens = true; + event.response.failAuthentication = false; + } else { + event.response.issueTokens = false; + event.response.failAuthentication = true; + } + context.done(null, event); +}; +``` + +If your lambda is setup to start with `SRP` as the first step, make sure to initiate the signIn process with `customWithSRP` as the authentication flow: + +#### [Java] + +```java +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP) + .build(); +Amplify.Auth.signIn( + "username", + "password", + options, + result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP) + .build() +Amplify.Auth.signIn( + "username", + "password", + options, + { result -> + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Sign in succeeded") + } else { + Log.i("AuthQuickstart", "Sign in not complete") + } + }, + { Log.e("AuthQuickstart", "Failed to sign in", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP) + .build() +try { + val result = Amplify.Auth.signIn("username", "password", options) + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Sign in succeeded") + } else { + Log.e("AuthQuickstart", "Sign in not complete") + } +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + +#### [RxJava] + +```java +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP) + .build(); +RxAmplify.Auth.signIn("username", "password", options) + .subscribe( + result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +The Auth category can be configured to perform a [custom authentication flow](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html) defined by you. The following guide shows how to setup a simple passwordless authentication flow. + +## Prerequisites + +Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Refer to [Flutter's supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) when targeting Web, Windows, or Linux. Additional setup is required for some target platforms. Please see the [platform setup](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#platform-setup) for more details on platform specific setup. + +## Configure Auth + +The custom auth flow can be [configured manually](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). + +## Register a user + +The flow as mentioned above requires a username and a valid email id as parameters to register a user. Invoke the following api to initiate a sign up flow. + +Because authentication flows in Cognito can be switched via your configuration, it is still required that users register with a password. + +```dart +Future signUpCustomFlow() async { + try { + final userAttributes = { + AuthUserAttributeKey.email: 'email@domain.com', + AuthUserAttributeKey.phoneNumber: '+15559101234', + // additional attributes as needed + }; + final result = await Amplify.Auth.signUp( + username: 'myusername', + password: 'mysupersecurepassword', + options: SignUpOptions(userAttributes: userAttributes), + ); + safePrint('Sign up result: $result'); + } on AuthException catch (e) { + safePrint('Error signing up: ${e.message}'); + } +} +``` + +The next step in the sign up flow is to confirm the user. A confirmation code will be sent to the email id provided during sign up. Enter the confirmation code received via email in the `confirmSignUp` call. + +```dart +Future confirmUser({ + required String username, + required String confirmationCode, +}) async { + try { + final result = await Amplify.Auth.confirmSignUp( + username: username, + confirmationCode: confirmationCode, + ); + // Check if further confirmations are needed or if + // the sign up is complete. + await _handleSignUpResult(result); + } on AuthException catch (e) { + safePrint('Error confirming user: ${e.message}'); + } +} +``` + +## Sign in a user + +Implement a UI to get the username from the user. After the user enters the username you can start the sign in flow by calling the following method: + +```dart +// Create state variables for the sign in status +var isSignedIn = false; +String? challengeHint; + +Future signInCustomFlow(String username) async { + try { + final result = await Amplify.Auth.signIn(username: username); + setState(() { + isSignedIn = result.isSignedIn; + // Get the publicChallengeParameters from your Create Auth Challenge Lambda + challengeHint = result.nextStep.additionalInfo['hint']; + }); + } on AuthException catch (e) { + safePrint('Error signing in: ${e.message}'); + } +} +``` + + + +Please note that you will be prevented from successfully calling `signIn` if a +user has already signed in and a valid session is active. You must first call +`signOut` to remove the original session. + + +## Confirm sign in with custom challenge + +To get a custom challenge from the user, create an appropriate UI for the user to submit the required value, and pass that value into the `confirmSignin()` API. + +```dart +Future confirmSignIn(String generatedNumber) async { + try { + final result = await Amplify.Auth.confirmSignIn( + /// Enter the random number generated by your Create Auth Challenge trigger + confirmationValue: generatedNumber, + ); + safePrint('Sign in result: $result'); + } on AuthException catch (e) { + safePrint('Error signing in: ${e.message}'); + } +} +``` + +Once the user provides the correct response, they should be authenticated in your application. + +> **Warning:** Special Handling on ConfirmSignIn +> +> During a `confirmSignIn` call, if `failAuthentication: true` is returned by the Lambda, the session of the request gets invalidated by Cognito, and a `NotAuthorizedException` is thrown. To recover, the user must initiate a new sign in by calling `Amplify.Auth.signIn`. +> +> Exception: `NotAuthorizedException` with a message `Invalid session for the user.` + +## Custom authentication flow with password verification + +The example in this documentation demonstrates the passwordless custom authentication flow. However, it is also possible to require that users supply a valid password as part of the custom authentication flow. + +To require a valid password, you can alter the [DefineAuthChallenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html) code to handle a `PASSWORD_VERIFIER` step: + +```js +exports.handler = async (event) => { + if ( + event.request.session.length === 1 && + event.request.session[0].challengeName === 'SRP_A' + ) { + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = 'PASSWORD_VERIFIER'; + } else if ( + event.request.session.length === 2 && + event.request.session[1].challengeName === 'PASSWORD_VERIFIER' && + event.request.session[1].challengeResult === true + ) { + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = 'CUSTOM_CHALLENGE'; + } else if ( + event.request.session.length === 3 && + event.request.session[2].challengeName === 'CUSTOM_CHALLENGE' && + event.request.session[2].challengeResult === true + ) { + event.response.issueTokens = true; + event.response.failAuthentication = false; + } else { + event.response.issueTokens = false; + event.response.failAuthentication = true; + } + + return event; +}; +``` + + +--- + +--- +title: "Email customization" +section: "build-a-backend/auth/customize-auth-lifecycle" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-08-15T17:57:11.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/customize-auth-lifecycle/email-customization/" +--- + +## Customize the Verification Email + +By default, Amplify Auth resources are scaffolded with email as the default method for your users to sign in. When you users sign up they receive a verification email to confirm their ownership of the email they specified during sign-up. Emails such as the verification email can be customized with your app's brand identity. + +To get started, change the `email` attribute of `loginWith` from `true` to an object to begin customizing its default behavior: + +```diff title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" + +export const auth = defineAuth({ + loginWith: { +- email: true, ++ email: { ++ verificationEmailStyle: "CODE", ++ verificationEmailSubject: "Welcome to my app!", ++ verificationEmailBody: (createCode) => `Use this code to confirm your account: ${createCode()}`, ++ }, + }, +}) +``` + +## Customize the Invitation Email + +In some cases, you may [set up a user account on behalf of a user in the Amplify console](/[platform]/build-a-backend/auth/manage-users/with-amplify-console/). In this case, Amplify Auth will send an invitation email to the user welcoming them to your application. This email includes a brief welcome message, along with the email address they can log in with and the temporary password you've set up for them. + +If you'd like to customize that email, you can override the `userInvitation` attribute of the `email` object: + +```diff title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" + +export const auth = defineAuth({ + loginWith: { +- email: true, ++ email: { ++ // can be used in conjunction with a customized welcome email as well ++ verificationEmailStyle: "CODE", ++ verificationEmailSubject: "Welcome to my app!", ++ verificationEmailBody: (createCode) => `Use this code to confirm your account: ${createCode()}`, ++ userInvitation: { ++ emailSubject: "Welcome to my app!", ++ emailBody: (user, code) => ++ `We're happy to have you! You can now login with username ${user()} and temporary password ${code()}`, ++ }, ++ }, + }, +}) +``` + +Note that when using the `user` and `code` arguments of the `emailBody` function, `user` and `code` are **functions** which must be called. Failure to call them will result in an error when your sandbox deploys. + +--- + +--- +title: "Triggers" +section: "build-a-backend/auth/customize-auth-lifecycle" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-03T17:21:51.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/customize-auth-lifecycle/triggers/" +--- + +Amplify Auth's behavior can be customized through the use of triggers. A trigger is defined as a Function, and is a mechanism to slot some logic to execute during the authentication flow. For example, you can use triggers to [validate whether emails include an allowlisted domain](/[platform]/build-a-backend/functions/examples/email-domain-filtering), [add a user to a group upon confirmation](/[platform]/build-a-backend/functions/examples/add-user-to-group), or [create a "UserProfile" model upon account confirmation](/[platform]/build-a-backend/functions/examples/create-user-profile-record). + +Triggers translate to [Cognito user pool Lambda triggers](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html). + +> When you have a Lambda trigger assigned to your user pool, Amazon Cognito interrupts its default flow to request information from your function. Amazon Cognito generates a JSON event and passes it to your function. The event contains information about your user's request to create a user account, sign in, reset a password, or update an attribute. Your function then has an opportunity to take action, or to send the event back unmodified. + +To get started, define a function and specify the `triggers` property on your auth resource: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" + +export const auth = defineAuth({ + loginWith: { + email: true, + }, + // highlight-next-line + triggers: {} +}) +``` + +To learn more about use cases for triggers, visit the [Functions examples](/[platform]/build-a-backend/functions/examples/). + +--- + +--- +title: "Enable sign-in with web UI" +section: "build-a-backend/auth" +platforms: ["flutter", "swift", "android"] +gen: 2 +last-updated: "2025-12-16T19:28:39.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/sign-in-with-web-ui/" +--- + + +## Prerequisites +* An app set up according to the [getting started walkthrough](/[platform]/build-a-backend/auth/set-up-auth/) + +> **Warning:** When configuring social sign-in, it's important to exercise caution when designating attributes as "required." Different social identity providers have varied scopes in terms of the information they respond back to Cognito with. User pool attributes that are initially set up as "required" cannot be changed later, and may require you to migrate the users or create a new user pool. + +## Configure Auth Category + + + +This library's Cognito plugin currently supports the [Authorization Code Grant](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html) OAuth Flow. + + + +In your `auth/resource.ts` file, update the +```ts +export const auth = defineAuth({ + loginWith: { + email: true, + externalProviders: { + callbackUrls: ["myapp://callback/"], + logoutUrls: ["myapp://signout/"], + }, + }, +}); +``` + +## Update AndroidManifest.xml + +Add the following activity and queries tag to your app's `AndroidManifest.xml` file, replacing `myapp` with +your redirect URI prefix if necessary: + +```xml + + ... + + + + + + + + + ... + +``` + +## Launch Web UI Sign In + +Sweet! You're now ready to launch sign in with web UI. For now, just add this method to the `onCreate` method of MainActivity: + +#### [Java] + +```java +Amplify.Auth.signInWithWebUI( + this, + result -> Log.i("AuthQuickStart", result.toString()), + error -> Log.e("AuthQuickStart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.signInWithWebUI( + this, + { Log.i("AuthQuickStart", "Signin OK = $it") }, + { Log.e("AuthQuickStart", "Signin failed", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.signInWithWebUI(this) + Log.i("AuthQuickStart", "Signin OK: $result") +} catch (error: AuthException) { + Log.e("AuthQuickStart", "Signin failed", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.signInWithWebUI(this) + .subscribe( + result -> Log.i("AuthQuickStart", result.toString()), + error -> Log.e("AuthQuickStart", error.toString()) + ); +``` + +### Additional options during signIn + +You may pass additional parameters to the `Amplify.Auth.signInWithWebUI` which are added as query parameters in the requests to [Cognito authorization endpoint](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html). + +```kotlin +val options = AWSCognitoAuthWebUISignInOptions.builder() + .nonce("randomUUID") + .language("en") + .loginHint("username") + .prompt(AuthWebUIPrompt.LOGIN, AuthWebUIPrompt.CONSENT) + .resource("https://localhost") + .build() + +Amplify.Auth.signInWithWebUI( + this, + options, + result -> Log.i("AuthQuickStart", result.toString()), + error -> Log.e("AuthQuickStart", error.toString()) +); +``` + + +## Prerequisites + +> **Warning:** **Note:** Social sign-in (OAuth) functionality is only available in **iOS**, **macOS** and **visionOS**. +> +> When configuring social sign-in, it's important to exercise caution when designating attributes as "required." Different social identity providers have varied scopes in terms of the information they respond back to Cognito with. User pool attributes that are initially set up as "required" cannot be changed later, and may require you to migrate the users or create a new user pool. + +For a full example, please follow the [project setup walkthrough](/[platform]/start/quickstart/). + + + +To use Auth in a macOS project, you'll need to enable the Keychain Sharing capability. In Xcode, navigate to **your application target** > **Signing & Capabilities** > **+ Capability**, then select **Keychain Sharing.** + +This capability is required because Auth uses the Data Protection Keychain on macOS as a platform best practice. +See [TN3137: macOS keychain APIs and implementations](https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains) for more information on how Keychain works on macOS and the Keychain Sharing entitlement. + +For more information on adding capabilities to your application, see [Xcode Capabilities](https://developer.apple.com/documentation/xcode/capabilities). + + + +## Configure Auth Category + + + +This library's Cognito plugin currently supports the [Authorization Code Grant](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html) OAuth Flow. + + + +Update the `auth/resource.ts` file like the following to enable the sign-in and sign-out capabilities with a web ui. + +```ts +export const auth = defineAuth({ + loginWith: { + email: true, + externalProviders: { + callbackUrls: ["myapp://callback/"], + logoutUrls: ["myapp://signout/"], + }, + }, +}); +``` +## Update Info.plist + +Signin with web UI require the Amplify plugin to show up the signin UI inside a webview. After the signin process is complete it will redirect back to your app. +You have to enable this in your app's `Info.plist`. Right click Info.plist and then choose Open As > Source Code. Add the following entry in the URL scheme: + +```xml + + + + + + + + + CFBundleURLTypes + + + CFBundleURLSchemes + + myapp + + + + + + +``` + +When creating a new SwiftUI app using Xcode 13 no longer require configuration files such as the Info.plist. If you are missing this file, click on the project target, under Info, Url Types, and click '+' to add a new URL Type. Add `myapp` to the URL Schemes. You should see the Info.plist file now with the entry for CFBundleURLSchemes. + +## Launch Web UI Sign In + +You're now ready to launch sign in with web UI. The `signInWithWebUI` api require a presentationAnchor and for an iOS app it will be the main UIWindow of the app. The example code below assume that you are in a UIViewController where you can fetch the UIWindow instance by `self.view.window`. + +#### [Async/Await] + +```swift +func signInWithWebUI() async { + do { + let signInResult = try await Amplify.Auth.signInWithWebUI(presentationAnchor: self.view.window!) + if signInResult.isSignedIn { + print("Sign in succeeded") + } + } catch let error as AuthError { + print("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func signInWithWebUI() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.signInWithWebUI(presentationAnchor: self.view.window!) + }.sink { + if case let .failure(authError) = $0 { + print("Sign in failed \(authError)") + } + } + receiveValue: { signInResult in + if signInResult.isSignedIn { + print("Sign in succeeded") + } + } +} +``` + +### Prefer private session during signIn + +Starting Amplify 1.6.0, `Amplify.Auth.signInWithWebUI` automatically uses [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) internally for iOS 13.0+. For older iOS versions, it will fall back to [SFAuthenticationSession](https://developer.apple.com/documentation/safariservices/sfauthenticationsession). +This release also introduces a new `preferPrivateSession` flag to `AWSAuthWebUISignInOptions` during the sign in flow. If `preferPrivateSession` is set to `true` during sign in, the user will not see a web view displayed when they sign out. `preferPrivateSession` will set [ASWebAuthenticationSession.prefersEphemeralWebBrowserSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/3237231-prefersephemeralwebbrowsersessio) internally and the authentication session will be private if the user's preferred browser supports it. + +```swift +try await Amplify.Auth.signInWithWebUI( + presentationAnchor: self.view.window!, + options: .preferPrivateSession() +) { + ... +} +``` + +### Additional options during signIn + +You may pass additional parameters to the `Amplify.Auth.signInWithWebUI` which are added as query parameters in the requests to [Cognito authorization endpoint](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html). + +```swift +let options = AuthWebUISignInRequest.Options( + pluginOptions: AWSAuthWebUISignInOptions.init( + nonce: "randomUUID", + language: "en", + loginHint: "username", + prompt: [.login, .consent], + resource: "http://localhost")) + +let signInResult = try await Amplify.Auth.signInWithWebUI( + presentationAnchor: self.view.window!, + options: options) +``` + + +## Prerequisites + +* An app set up according to the [getting started walkthrough](/[platform]/build-a-backend/auth/set-up-auth/) + +> **Warning:** When configuring Social sign-in, it's important to exercise caution when designating attributes as "required." Different social identity providers have varied scopes in terms of the information they respond back to Cognito with. User pool attributes that are initially set up as "required" cannot be changed later, and may require you to migrate the users or create a new user pool. + +## Configure Auth Category + + + +This library's Cognito plugin currently supports the [Authorization Code Grant](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html) OAuth Flow. + + + +Update the `auth/resource.ts` file like the following to enable the sign-in and sign-out capabilities with a web ui. + +```ts +export const auth = defineAuth({ + loginWith: { + email: true, + externalProviders: { + callbackUrls: ["myapp://callback/"], + logoutUrls: ["myapp://signout/"], + }, + }, +}); +``` + +## How It Works + +Sign-in with web UI will display the sign-in UI inside a webview. After the sign-in process is complete, the sign-in UI will redirect back to your app. + +## Platform Setup + +### Web + +To use Hosted UI in your Flutter web application locally, you must run the app with the `--web-port=3000` argument (with the value being whichever port you assigned to localhost host when configuring your redirect URIs). + +### Android + +Add the following `queries` element to the `AndroidManifest.xml` file in your app's `android/app/src/main` directory, as well as the following `intent-filter` to the `MainActivity` in the same file. + +Replace `myapp` with your redirect URI scheme as necessary: + +```xml + + + + + + + ... + + + + + + + + + ... + +``` + +### macOS + +Open XCode and enable the App Sandbox capability and then select "Incoming Connections (Server)" under "Network". + +![Incoming Connections setting selected in the App Sandbox section of the runner signing and capabilities tab.](/images/project-setup/flutter/mac/xcode-entitlements.png) + +### iOS, Windows and Linux + +No specific platform configuration is required. + +## Launch Web UI Sign In + +You're now ready to launch sign in with web UI. + +```dart +Future signInWithWebUI() async { + try { + final result = await Amplify.Auth.signInWithWebUI(); + safePrint('Sign in result: $result'); + } on AuthException catch (e) { + safePrint('Error signing in: ${e.message}'); + } +} +``` + +You can also specify a provider with the `provider` attribute: + +```dart +Future signInWithWebUIProvider() async { + try { + final result = await Amplify.Auth.signInWithWebUI( + provider: AuthProvider.google, + ); + safePrint('Result: $result'); + } on AuthException catch (e) { + safePrint('Error signing in: ${e.message}'); + } +} +``` + +Amplify Flutter currently supports the following social sign-in providers: + * Google + * Facebook + * Login With Amazon + * Apple + +### Prefer private session during signIn on iOS. + +Amplify.Auth.signInWithWebUI uses [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) internally for iOS. ASWebAuthenticationSession has a property, [prefersEphemeralWebBrowserSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/3237231-prefersephemeralwebbrowsersessio) which can be used to indicate whether the session should ask the browser for a private authentication session. To set this flag to true, set `preferPrivateSession` to true using `CognitoSignInWithWebUIPluginOptions`. + +This will bypass the permissions dialog that is displayed to the end user during sign in and sign out. However, it will also prevent reuse of existing sessions from the user's browser. For example, if the user is logged into Google in their browser and try to sign in using Google in your app, they would now need to re-enter their credentials. + +```dart +Future signInWithWebUIAndPrivateSession() async { + await Amplify.Auth.signInWithWebUI( + options: const SignInWithWebUIOptions( + pluginOptions: CognitoSignInWithWebUIPluginOptions( + isPreferPrivateSession: true, + ), + ), + ); +} +``` + +### Additional options during signIn + +You may pass additional parameters to the `Amplify.Auth.signInWithWebUI` which are added as query parameters in the requests to [Cognito authorization endpoint](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html). + +```dart +Future signInWithWebUIAndOptions() async { + try { + final result = await Amplify.Auth.Amplify.Auth.signInWithWebUI( + options: SignInWithWebUIOptions(pluginOptions: CognitoSignInWithWebUIPluginOptions( + nonce: 'randomUUID', + language: 'en', + loginHint: 'username', + prompt: List.from([CognitoSignInWithWebUIPrompt.login, CognitoSignInWithWebUIPrompt.consent]), + resource: 'http://localhost' + ) + ) + ); + safePrint('Sign in result: $result'); + } on AuthException catch (e) { + safePrint('Error signing in: ${e.message}'); + } +} +``` + + +--- + +--- +title: "Uninstalling the app" +section: "build-a-backend/auth" +platforms: ["swift", "android"] +gen: 2 +last-updated: "2024-05-02T22:35:36.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/app-uninstall/" +--- + + +Some Amplify categories such as Analytics and Auth persist data to the local device. This application data is removed when a user uninstalls the application from the device. + +If the [Android Auto Backup for Apps](https://developer.android.com/guide/topics/data/autobackup) service was enabled, this service will attempt to restore application data. + +Amplify Auth uses [EncryptedSharedPreferences](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences) when persisting auth data. When an application is uninstalled, the [Android Keystore](https://developer.android.com/training/articles/keystore) keys used to create our EncryptedSharedPreferences files are deleted. Upon an application re-install, these restored files are no longer readable due to the key removal from the Android Keystore. + +Due to this limitation with EncryptedSharedPreferences, Auth information can’t be restored on an application re-install. The user will have to re-authenticate. + + + +Some Amplify categories such as Analytics and Auth persist data to the local device. Some of that data is automatically removed when a user uninstalls the app from the device. + +Amplify stores Auth information in the local [system keychain](https://developer.apple.com/documentation/security/keychain_services), which does not guarantee any particular behavior around whether data is removed when an app is uninstalled. + +Deciding on when to clear this auth information is not something that the SDK can do in a generic way, so App developers should decide when to clear the data by signing out. One strategy for accomplishing this would be to use [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) to detect whether or not the app is launching for the first time, and invoking [`Auth.signOut()`](/[platform]/build-a-backend/auth/connect-your-frontend/sign-out/) if the app has not been launched before. + + +--- + +--- +title: "Data usage policy information" +section: "build-a-backend/auth" +platforms: ["swift"] +gen: 2 +last-updated: "2024-05-02T22:35:36.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/data-usage-policy/" +--- + + +Apple requires app developers to provide the data usage policy of the app when they submit their app to the App Store. See Apple's [User privacy and data use](https://developer.apple.com/app-store/user-privacy-and-data-use/) for more details. The Amplify Library is used to interact with AWS resources under the developer’s ownership and management. The library cannot predict the usage of its APIs and it is up to the developer to provide the privacy manifest that accurately reflects the data collected by the app. Below are the different categories identified by Apple and the corresponding data type used by the Amplify Library. + +By utilizing the library, Amplify gathers API usage metrics from the AWS services accessed. This process involves adding a user agent to the request made to your AWS service. The user-agent header is included with information about the Amplify Library version, operating system name, and version. AWS collects this data to generate metrics related to our library usage. This information is not linked to the user’s identity and not used for tracking purposes as described in Apple's privacy and data use guidelines. + +Should you have any specific concerns or require additional information for the enhancement of your privacy manifest, please don't hesitate to reach out. + +## Contact info + +| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | +| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | +| **Name** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | βœ… | +| **Email Address** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | βœ… | +| **Phone Number** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | βœ… | + +## User Content + +| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | +| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | +| **Photos or Videos** | | | | | | +| | Storage | App Functionality | ❌ | ❌ | βœ… | +| | Predictions | App Functionality | ❌ | ❌ | βœ… | +| **Audio Data** | | | | | | +| | Predictions | App Functionality | ❌ | ❌ | βœ… | + +## Identifiers + +| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | +| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | +| **User ID** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| | Analytics | Analytics | βœ… | ❌ | ❌ | +| **Device ID** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| | Analytics | Analytics | βœ… | ❌ | ❌ | + +## Other Data + +| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | +| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | +| **OS Version** | | | | | | +| | All categories | Analytics | ❌ | ❌ | ❌ | +| **OS Name** | | | | | | +| | All categories | Analytics | ❌ | ❌ | ❌ | +| **Locale Info** | | | | | | +| | All categories | Analytics | ❌ | ❌ | ❌ | +| **App Version** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Min OS target of the app** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Timezone information** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Network information** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Has SIM card** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Cellular Carrier Name** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Device Model** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Device Name** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Device OS Version** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Device Height and Width** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Device Language** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **identifierForVendor** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | + +## Health and Fitness +No data is collected + +## Financial Info +No data is collected + +## Location +No data is collected + +## Sensitive Info +No data is collected + +## Contacts +No data is collected + +## Browsing History +No data is collected + +## Search History +No data is collected + +## Diagnostics +No data is collected + + +Some Amplify categories such as Analytics and Auth persist data to the local device. Some of that data is automatically removed when a user uninstalls the app from the device. + +Amplify stores Auth information in the local [system keychain](https://developer.apple.com/documentation/security/keychain_services), which does not guarantee any particular behavior around whether data is removed when an app is uninstalled. + +Deciding on when to clear this auth information is not something that the SDK can do in a generic way, so App developers should decide when to clear the data by signing out. One strategy for accomplishing this would be to use [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) to detect whether or not the app is launching for the first time, and invoking [`Auth.signOut()`](/[platform]/build-a-backend/auth/connect-your-frontend/sign-out/) if the app has not been launched before. + +--- + +--- +title: "Examples" +section: "build-a-backend/auth" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-10-18T22:15:30.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/examples/" +--- + + + +--- + +--- +title: "Microsoft Entra ID (SAML)" +section: "build-a-backend/auth/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-02-28T16:21:52.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/examples/microsoft-entra-id-saml/" +--- + +Microsoft Entra ID can be configured as a SAML provider for use with Amazon Cognito. Integrating Entra ID enables you to sign in with your existing enterprise users, and maintain profiles unique to the Amplify Auth resource for use within your Amplify app. To learn more, visit the [Azure documentation for SAML authentication with Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/architecture/auth-saml). + +> **Info:** **Note:** the following guidance showcases configuration with your [personal cloud sandbox](/[platform]/deploy-and-host/sandbox-environments/setup/). You will need to repeat the configuration steps for branch deployments after confirming functionality against your sandbox. + +## Start your personal cloud sandbox + +To get started, define your auth resource with the appropriate redirect URIs: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" + +/** + * Define and configure your auth resource + * @see https://docs.amplify.aws/gen2/build-a-backend/auth + */ +export const auth = defineAuth({ + loginWith: { + email: true, + externalProviders: { + logoutUrls: ["http://localhost:3000/come-back-soon"], + callbackUrls: ["http://localhost:3000/profile"], + }, + }, +}) +``` + +Deploy to your personal cloud sandbox with `npx ampx sandbox`. This will generate a domain you can use to configure your new Entra ID App. After deploying your changes successfully, copy the generated `domain` value from `amplify_outputs.json` + +```json title="amplify_outputs.json" +{ + "auth": { + "aws_region": "us-east-1", + "user_pool_id": "", + "user_pool_client_id": "", + "identity_pool_id": "", + "mfa_methods": [], + "standard_required_attributes": [ + "email" + ], + "username_attributes": [ + "email" + ], + "user_verification_types": [ + "email" + ], + "mfa_configuration": "OFF", + "password_policy": { + "min_length": 8, + "require_numbers": true, + "require_lowercase": true, + "require_uppercase": true, + "require_symbols": true + }, + "oauth": { + "identity_providers": [], + "redirect_sign_in_uri": [ + "http://localhost:3000/profile" + ], + "redirect_sign_out_uri": [ + "http://localhost:3000/come-back-soon" + ], + "response_type": "code", + "scopes": [ + "phone", + "email", + "openid", + "profile", + "aws.cognito.signin.user.admin" + ], + // highlight-next-line + "domain": ".auth.us-east-1.amazoncognito.com" + }, + }, + "version": "1" +} +``` + +## Set up Microsoft Entra ID + +Next, navigate to [portal.azure.com](https://portal.azure.com/), select **Entra ID**. In your default directory, or company's existing directory, under **Manage**, select **Enterprise Applications** + +![Entra ID default directory page highlighting Enterprise Applications](/images/auth/examples/microsoft-entra-id-saml/entra-id-select-enterprise-applications.png) + +Afterwards, select **New application**, then select **Create your own application**. Specify a name for the application and choose **Integrate any other application you don't find in the gallery (Non-gallery)** + +![Azure portal creating a new enterprise application for Entra ID](/images/auth/examples/microsoft-entra-id-saml/entra-id-new-enterprise-application.png) + +Now that you have created the new enterprise application you can begin to configure Single Sign-on with SAML. Select **Single sign-on** + +![Entra ID enterprise application highlighting "single sign-on"](/images/auth/examples/microsoft-entra-id-saml/entra-id-select-single-sign-on.png) + +Then select **SAML** + +![Entra ID enterprise application single sign-on setup highlighting "SAML"](/images/auth/examples/microsoft-entra-id-saml/entra-id-select-saml.png) + +You will be directed to a page to set up single sign-on with SAML, which needs a few pieces of information from your Amplify Auth resource. + +![Entra ID set up single sign-on page with a form requiring an entity ID and reply URL](/images/auth/examples/microsoft-entra-id-saml/entra-id-set-up-saml.png) + +In the **Basic SAML Configuration** step, select **Edit** and populate with the appropriate values. + +| Label | Value | +|-------|-------| +| Identifier (Entity ID) | `urn:amazon:cognito:sp:` | +| Reply URL (Assertion Consumer Service URL) | `https:///saml2/idpresponse` | +| Logout Url (Optional) | `https:///saml2/logout` | + +> **Info:** **Note:** Amazon Cognito redirect URIs for SAML providers follow the convention: +> +> ```text showLineNumbers={false} +https://.auth..amazoncognito.com/saml2/ +``` +> +> If you are using a custom domain the route remains the same: `/saml2/`. [Learn more about configuring Amazon Cognito with SAML identity providers](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-saml-idp.html) + +> **Warning:** **Warning:** there is a known limitation where upstream sign-out functionality successfully signs out of Entra ID, but fails to redirect back to the user app. This behavior is disabled by default with SAML integrations in Amplify Auth. + +Save the configuration and proceed to Step 3's **SAML Certificates** section. Copy the **App Federation Metadata Url** + +![Entra ID set up single sign-on page highlighting the app federation metadata URL](/images/auth/examples/microsoft-entra-id-saml/entra-id-copy-federation-url.png) + +## Configure your backend with Entra ID + +Now that you've configured your SAML provider with Microsoft Entra ID and copied the **App Federation Metadata Url**, configure your auth resource with the new SAML provider and paste the URL value into the `metadataContent` property: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" + +/** + * Define and configure your auth resource + * @see https://docs.amplify.aws/gen2/build-a-backend/auth + */ +export const auth = defineAuth({ + loginWith: { + email: true, + externalProviders: { + saml: { + name: "MicrosoftEntraIDSAML", + metadata: { + metadataType: "URL", + metadataContent: "", + }, + attributeMapping: { + email: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", + }, + }, + logoutUrls: ["http://localhost:3000/come-back-soon"], + callbackUrls: ["http://localhost:3000/profile"], + }, + }, +}) +``` + +User attributes can be found in Step 2's **Attributes & Claims** section, and are prefixed with a namespace by default. The example above shows mapping the default claim for the SAML user's email address, however additional attributes can be mapped. + +## Optionally upload the Cognito Signing Certificate + +In the AWS Console, navigate to your Cognito User Pool. Select the identity provider, **MicrosoftEntraIDSAML**, created after configuring Amplify Auth with the Entra ID SAML provider. Select **View signing certificate** and **download as .crt** + +![Amazon Cognito console highlighting "view signing certificate" for SAML provider](/images/auth/examples/microsoft-entra-id-saml/cognito-view-signing-certificate.png) + +Rename the file extension to `.cer` in order to upload to Azure. On the **Single sign-on** page, scroll down to **Step 3** (**SAML Certificates**), and under **Verification Certificates (optional)**, select **Edit**. + +![Entra ID single sign-on page highlighting "edit" for verification certificates](/images/auth/examples/microsoft-entra-id-saml/entra-id-edit-verification-certificate.png) + +Select **Require verification certificates** and upload the certificate. + +![Entra ID verification certificate upload pane](/images/auth/examples/microsoft-entra-id-saml/entra-id-upload-verification-certificate.png) + +Save your changes, and now requests to Entra ID from your Cognito User Pool will be verified. + +## Connect your frontend + +Now your users are ready to sign in with Microsoft Entra ID. To sign in with this custom provider, specify the provider name as the name specified in your auth resource definition: `MicrosoftEntraIDSAML` + + +```ts title="main.ts" +import { signInWithRedirect } from "aws-amplify/auth" + +signInWithRedirect({ + provider: { custom: "MicrosoftEntraIDSAML" } +}) +``` + + + +#### [Java] + +```java +Amplify.Auth.signInWithSocialWebUI( + AuthProvider.custom("MicrosoftEntraIDSAML") + this, + result -> Log.i("AuthQuickStart", result.toString()), + error -> Log.e("AuthQuickStart", error.toString()) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Auth.signInWithSocialWebUI( + AuthProvider.custom("MicrosoftEntraIDSAML"), + this, + { Log.i("AuthQuickstart", "Sign in OK: $it") }, + { Log.e("AuthQuickstart", "Sign in failed", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Auth.signInWithSocialWebUI(AuthProvider.custom("MicrosoftEntraIDSAML"), this) + Log.i("AuthQuickstart", "Sign in OK: $result") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Auth.signInWithSocialWebUI(AuthProvider.custom("MicrosoftEntraIDSAML"), this) + .subscribe( + result -> Log.i("AuthQuickstart", result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + +```dart +Future signInWithMicrosoftEntraID() async { + try { + final result = await Amplify.Auth.signInWithWebUI( + provider: AuthProvider.custom("MicrosoftEntraIDSAML"), + ); + safePrint('Sign in result: $result'); + } on AuthException catch (e) { + safePrint('Error signing in: ${e.message}'); + } +} +``` + + + +#### [Async/Await] + +```swift +func signInWithMicrosoftEntraID() async { + do { + let signInResult = try await Amplify.Auth.signInWithWebUI( + for: AuthProvider.custom("MicrosoftEntraIDSAML"), + presentationAnchor: self.view.window! + ) + if signInResult.isSignedIn { + print("Sign in succeeded") + } + } catch let error as AuthError { + print("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func signInWithMicrosoftEntraID() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.signInWithWebUI( + for: AuthProvider.custom("MicrosoftEntraIDSAML"), + presentationAnchor: self.view.window! + ) + } + .sink { completion in + if case let .failure(authError) = completion { + print("Sign in failed \(authError)") + } + } receiveValue: { signInResult in + if signInResult.isSignedIn { + print("Sign in succeeded") + } + } +} +``` + + + +--- + +--- +title: "Grant access to auth resources" +section: "build-a-backend/auth" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-07-12T17:36:34.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/grant-access-to-auth-resources/" +--- + +Amplify Auth can be defined with an `access` property, which allows other resources to interact with auth by specifying actions. + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" +import { addUserToGroup } from "../functions/add-user-to-group/resource" + +/** + * Define and configure your auth resource + * @see https://docs.amplify.aws/gen2/build-a-backend/auth + */ +export const auth = defineAuth({ + loginWith: { + email: true, + }, + access: (allow) => [ + allow.resource(addUserToGroup).to(["addUserToGroup"]) + ], +}) +``` + + + +When you grant a function access to another resource in your Amplify backend it will configure environment variables for that function to make SDK calls to the AWS services it has access to. Those environment variables are typed and available as part of the `env` object. + + + +## List of actions + +|Action Name|Description|Cognito IAM Actions| +|-|-|-| +|manageUsers | Grants CRUD access to users in the UserPool |
    • cognito-idp:AdminConfirmSignUp
    • cognito-idp:AdminCreateUser
    • cognito-idp:AdminDeleteUser
    • cognito-idp:AdminDeleteUserAttributes
    • cognito-idp:AdminDisableUser
    • cognito-idp:AdminEnableUser
    • cognito-idp:AdminGetUser
    • cognito-idp:AdminListGroupsForUser
    • cognito-idp:AdminRespondToAuthChallenge
    • cognito-idp:AdminSetUserMFAPreference
    • cognito-idp:AdminSetUserSettings
    • cognito-idp:AdminUpdateUserAttributes
    • cognito-idp:AdminUserGlobalSignOut
    | +|manageGroupMembership | Grants permission to add and remove users from groups |
    • cognito-idp:AdminAddUserToGroup
    • cognito-idp:AdminRemoveUserFromGroup
    | +|manageGroups | Grants CRUD access to groups in the UserPool |
    • cognito-idp:GetGroup
    • cognito-idp:ListGroups
    • cognito-idp:CreateGroup
    • cognito-idp:DeleteGroup
    • cognito-idp:UpdateGroup
    | +|manageUserDevices | Manages devices registered to users|
    • cognito-idp:AdminForgetDevice
    • cognito-idp:AdminGetDevice
    • cognito-idp:AdminListDevices
    • cognito-idp:AdminUpdateDeviceStatus
    | +|managePasswordRecovery | Grants permission to reset user passwords |
    • cognito-idp:AdminResetUserPassword
    • cognito-idp:AdminSetUserPassword
    | +|addUserToGroup | Grants permission to add any user to any group. |
    • cognito-idp:AdminAddUserToGroup
    +|createUser | Grants permission to create new users and send welcome messages via email or SMS. |
    • cognito-idp:AdminCreateUser
    +|deleteUser | Grants permission to delete any user |
    • cognito-idp:AdminDeleteUser
    +|deleteUserAttributes | Grants permission to delete attributes from any user |
    • cognito-idp:AdminDeleteUserAttributes
    +|disableUser | Grants permission to deactivate any user |
    • cognito-idp:AdminDisableUser
    +|enableUser | Grants permission to activate any user |
    • cognito-idp:AdminEnableUser
    +|forgetDevice | Grants permission to deregister any user's devices |
    • cognito-idp:AdminForgetDevice
    +|getDevice | Grants permission to get information about any user's devices |
    • cognito-idp:AdminGetDevice
    +|getUser | Grants permission to look up any user by user name |
    • cognito-idp:AdminGetUser
    +|listUsers | Grants permission to list users and their basic details in the UserPool |
    • cognito-idp:ListUsers
    +|listDevices | Grants permission to list any user's remembered devices |
    • cognito-idp:AdminListDevices
    +|listGroupsForUser | Grants permission to list the groups that any user belongs to |
    • cognito-idp:AdminListGroupsForUser
    +|listUsersInGroup | Grants permission to list users in the specified group |
    • cognito-idp:ListUsersInGroup
    +|removeUserFromGroup | Grants permission to remove any user from any group |
    • cognito-idp:AdminRemoveUserFromGroup
    +|resetUserPassword | Grants permission to reset any user's password |
    • cognito-idp:AdminResetUserPassword
    +|setUserMfaPreference | Grants permission to set any user's preferred MFA method |
    • cognito-idp:AdminSetUserMFAPreference
    +|setUserPassword | Grants permission to set any user's password |
    • cognito-idp:AdminSetUserPassword
    +|setUserSettings | Grants permission to set user settings for any user |
    • cognito-idp:AdminSetUserSettings
    +|updateDeviceStatus | Grants permission to update the status of any user's remembered devices |
    • cognito-idp:AdminUpdateDeviceStatus
    +|updateUserAttributes | Grants permission to updates any user's standard or custom attributes |
    • cognito-idp:AdminUpdateUserAttributes
    + +--- + +--- +title: "Modify Amplify-generated Cognito resources with CDK" +section: "build-a-backend/auth" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-12-03T11:13:25.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/modify-resources-with-cdk/" +--- + +Amplify Auth provides sensible defaults for the underlying Amazon Cognito resource definitions. You can customize your authentication resource to enable it to behave exactly as needed for your use cases by modifying it directly using [AWS Cloud Development Kit (CDK)](https://aws.amazon.com/cdk/) + +## Override Cognito UserPool password policies + +You can override the password policy by using the L1 `cfnUserPool` construct and adding a `addPropertyOverride`. + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; + +const backend = defineBackend({ + auth, +}); +// extract L1 CfnUserPool resources +const { cfnUserPool } = backend.auth.resources.cfnResources; +// modify cfnUserPool policies directly +cfnUserPool.policies = { + passwordPolicy: { + minimumLength: 10, + requireLowercase: true, + requireNumbers: true, + requireSymbols: true, + requireUppercase: true, + temporaryPasswordValidityDays: 20, + }, +}; +``` + + +## Override Cognito UserPool to enable passwordless sign-in methods + +> **Info:** **Recommended approach:** Passwordless authentication can now be configured directly in `defineAuth` without requiring CDK overrides. [Learn how to configure passwordless authentication](/[platform]/build-a-backend/auth/concepts/passwordless/). + +For advanced use cases, you can still modify the underlying Cognito user pool resource to enable sign in with passwordless methods using CDK overrides. [Learn more about passwordless sign-in methods](/[platform]/build-a-backend/auth/concepts/passwordless/). + +You can also read more about how passwordless authentication flows are implemented in the [Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow-methods.html). + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend" +import { auth } from "./auth/resource" + +const backend = defineBackend({ + auth, +}) + +const { cfnResources } = backend.auth.resources; +const { cfnUserPool, cfnUserPoolClient } = cfnResources; + +// Specify which authentication factors you want to allow with USER_AUTH +cfnUserPool.addPropertyOverride( + 'Policies.SignInPolicy.AllowedFirstAuthFactors', + ['PASSWORD', 'WEB_AUTHN', 'EMAIL_OTP', 'SMS_OTP'] +); + +// The USER_AUTH flow is used for passwordless sign in +cfnUserPoolClient.explicitAuthFlows = [ + 'ALLOW_REFRESH_TOKEN_AUTH', + 'ALLOW_USER_AUTH' +]; + +/* Needed for WebAuthn */ +// The WebAuthnRelyingPartyID is the domain of your relying party, e.g. "example.domain.com" +cfnUserPool.addPropertyOverride('WebAuthnRelyingPartyID', ''); +cfnUserPool.addPropertyOverride('WebAuthnUserVerification', 'preferred'); +``` + + +--- + +--- +title: "Moving to production" +section: "build-a-backend/auth" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-10-10T22:55:38.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/moving-to-production/" +--- + +Amplify Auth provisions [Amazon Cognito](https://aws.amazon.com/cognito/) resources that are provisioned with limited capabilities for sending email and SMS messages. In its default state, it is not intended to handle production workloads, but is sufficient for developing your application and associated business logic. + +## Email + +Cognito provides a [default email functionality](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html#user-pool-email-default) that limits how many emails can be sent in one day. When considering production workloads, Cognito can be configured to send emails using [Amazon Simple Email Service (Amazon SES)](https://aws.amazon.com/ses/). + +> **Warning:** All new AWS accounts default to a "sandbox" status with Amazon SES. This comes with the primary caveat that you can only send mail **to** verified email addresses and domains + +To get started with Amazon SES in production, you must first [request production access](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). Once you submit your request the submission cannot be modified, however you will receive a response from AWS within 24 hours. + +After you have configured your account for production access and have verified your _sender_ email, you can configure your Cognito user pool to send emails using the verified sender: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" + +/** + * Define and configure your auth resource + * @see https://docs.amplify.aws/react/build-a-backend/auth + */ +export const auth = defineAuth({ + loginWith: { + email: true, + }, + senders: { + email: { + // configure using the email registered and verified in Amazon SES + fromEmail: "registrations@example.com", + }, + }, +}) +``` + +Now when emails are sent on new user sign-ups, password resets, etc., the sending account will be your verified email! To customize further, you can change the display name of the sender, or optionally apply a custom address for your users to reply. + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" + +/** + * Define and configure your auth resource + * @see https://docs.amplify.aws/react/build-a-backend/auth + */ +export const auth = defineAuth({ + loginWith: { + email: true, + }, + senders: { + email: { + fromEmail: "registrations@example.com", + // highlight-start + fromName: "MyApp", + replyTo: "inquiries@example.com" + // highlight-end + }, + }, +}) +``` + +## SMS + +In order to send SMS authentication codes, you must [request an origination number](https://docs.aws.amazon.com/pinpoint/latest/userguide/settings-request-number.html). Authentication codes will be sent from the origination number. If your AWS account is in the SMS sandbox, you must also add a destination phone number, which can be done by going to the [Amazon Pinpoint Console](https://console.aws.amazon.com/pinpoint/), selecting SMS and voice in the navigation pane, and selecting Add phone number in the Destination phone numbers tab. To check if your AWS account is in the SMS sandbox, go to the [SNS console](https://console.aws.amazon.com/sns/), select the Text messaging (SMS) tab from the navigation pane, and check the status under the Account information section. + +--- + +--- +title: "Advanced workflows" +section: "build-a-backend/auth" +platforms: ["javascript", "react-native", "flutter", "swift", "android", "angular", "nextjs", "react", "vue"] +gen: 2 +last-updated: "2025-11-19T19:55:05.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/advanced-workflows/" +--- + + +## Identity Pool Federation + +With identity federation, you don't need to create custom sign-in code or manage your own user identities. Instead, users of your app can sign in using a well-known external identity +provider (IdP), such as Login with Amazon, Facebook, Google, or any other OpenID Connect (OIDC)-compatible IdP. They can receive an authentication token, and then exchange that token for +temporary security credentials in AWS that map to an IAM role with permissions to use the resources in your AWS account. Using an IdP helps you keep your AWS account secure because you +don't have to embed and distribute long-term security credentials with your application. + +Imagine that you are creating a mobile app that accesses AWS resources, such as a game that runs on a mobile device and stores player and score information using Amazon S3 and DynamoDB. + +When you write such an app, you make requests to AWS services that must be signed with an AWS access key. However, we strongly recommend that you do not embed or distribute long-term +AWS credentials with apps that a user downloads to a device, even in an encrypted store. Instead, build your app so that it requests temporary AWS security credentials dynamically when +needed using identity federation. The supplied temporary credentials map to an AWS role that has only the permissions needed to perform the tasks required by the mobile app. + +You can use `federateToIdentityPool` to get AWS credentials directly from Cognito Federated Identities and not use User Pool federation. If you logged in with `Auth.signIn` you **cannot** +call `federateToIdentityPool` as Amplify will perform this federation automatically for you in the background. In general, you should only call `Auth.federatedSignIn()` when using OAuth flows. + +You can use the escape hatch API `federateToIdentityPool` with a valid token from other social providers. + +```dart +final cognitoPlugin = + Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); +const googleIdToken = 'idToken'; +final session = await cognitoPlugin.federateToIdentityPool( + token: googleIdToken, + provider: AuthProvider.google, +); +``` + + + +Note that when federated, APIs such as `Auth.getCurrentUser` will throw an error as the user is not authenticated with User Pools. + + + +### Retrieve Session + +After federated login, you can retrieve the session using the `Auth.fetchAuthSession` API. + +### Token Refresh + + + +Automatic authentication token refresh is NOT supported when federated. + + + +By default, Amplify will **NOT** automatically refresh the tokens from the federated providers. You will need to handle the token refresh logic and provide the new token to the `federateToIdentityPool` API. + +### Clear Session + +You can clear the federated session using the `clearFederationToIdentityPool` API. + +```dart +final cognitoPlugin = + Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); +await cognitoPlugin.clearFederationToIdentityPool(); +``` + + + +`clearFederationToIdentityPool` will only clear the session from the local cache; the developer needs to handle signing out from the federated identity provider. + + + +### Provide Custom Identity ID + +You can provide a custom identity ID to the `federateToIdentityPool` API. This is useful when you want to use the same identity ID across multiple sessions. + +```dart +final cognitoPlugin = + Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); +const googleIdToken = 'idToken'; +const identityId = 'us-west-2:b4cd4809-7ab1-42e1-b044-07dab9eaa768'; +final session = await cognitoPlugin.federateToIdentityPool( + token: googleIdToken, + provider: AuthProvider.google, + options: FederateToIdentityPoolOptions( + developerProvidedIdentityId: identityId, + ), +); +``` + + +## Subscribing Events + +You can take specific actions when users sign-in or sign-out by subscribing to authentication events in your app. Please see our [Hub Module Developer Guide](/[platform]/build-a-backend/auth/connect-your-frontend/listen-to-auth-events/) for more information. + +## Identity Pool Federation + +Imagine that you are creating a mobile app that accesses AWS resources, such as a game that runs on a mobile device and stores player and score information using Amazon S3 and DynamoDB. + +When you write such an app, you make requests to AWS services that must be signed with an AWS access key. However, we strongly recommend that you do not embed or distribute long-term AWS credentials with apps that a user downloads to a device, even in an encrypted store. Instead, build your app so that it requests temporary AWS security credentials dynamically when needed using web identity federation. The supplied temporary credentials map to an AWS role that has only the permissions needed to perform the tasks required by the mobile app. + +With web identity federation, you don't need to create custom sign-in code or manage your own user identities. Instead, users of your app can sign in using a well-known external identity provider (IdP), such as Login with Amazon, Facebook, Google, or any other OpenID Connect (OIDC)-compatible IdP. They can receive an authentication token, and then exchange that token for temporary security credentials in AWS that map to an IAM role with permissions to use the resources in your AWS account. Using an IdP helps you keep your AWS account secure because you don't have to embed and distribute long-term security credentials with your application. + +You can use `federateToIdentityPool` to get AWS credentials directly from Cognito Federated Identities and not use User Pool federation. If you logged in with `Auth.signIn` you **cannot** call `federateToIdentityPool` as Amplify will perform this federation automatically for you in the background. In general, you should only call `Auth.federatedSignIn()` when using OAuth flows. + +You can use the escape hatch API `federateToIdentityPool` with a valid token from other social providers. + +#### [Java] + +```java +if (Amplify.Auth.getPlugin("awsCognitoAuthPlugin") instanceof AWSCognitoAuthPlugin) { + AWSCognitoAuthPlugin plugin = (AWSCognitoAuthPlugin) Amplify.Auth.getPlugin("awsCognitoAuthPlugin"); + plugin.federateToIdentityPool( + "YOUR_TOKEN", + AuthProvider.facebook(), + result -> { + Log.i("AuthQuickstart", "Successful federation to Identity Pool."); + // use result.getCredentials() + }, + e -> { + Log.e("AuthQuickstart", "Failed to federate to Identity Pool.", e) + } + ); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +(Amplify.Auth.getPlugin("awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin)?.let { plugin -> + plugin.federateToIdentityPool( + "YOUR_TOKEN", + AuthProvider.facebook(), + { + Log.i("AuthQuickstart", "Successful federation to Identity Pool.") + // use "it.credentials" + }, + { + Log.e("AuthQuickstart", "Failed to federate to Identity Pool.", it) + } + ) +} +``` + + +Note that when federated, APIs such as Auth.getCurrentUser will throw an error as the user is not authenticated with User Pools. + + +### Retrieve Session + +After federated login, you can retrieve the session using the `Auth.fetchAuthSession` API. + +### Token Refresh + + +Automatic authentication token refresh is NOT supported when federated. + + +By default, Amplify will **NOT** automatically refresh the tokens from the federated providers. You will need to handle the token refresh logic and provide the new token to the `federateToIdentityPool` API. + +### Clear Session + +You can clear the federated session using the `clearFederationToIdentityPool` API. + +#### [Java] + +```java +if (Amplify.Auth.getPlugin("awsCognitoAuthPlugin") instanceof AWSCognitoAuthPlugin) { + AWSCognitoAuthPlugin plugin = (AWSCognitoAuthPlugin) Amplify.Auth.getPlugin("awsCognitoAuthPlugin"); + plugin.clearFederationToIdentityPool( + () -> Log.i("AuthQuickstart", "Federation cleared successfully."), + e -> Log.e("AuthQuickstart", "Failed to clear federation.", e) + ); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +(Amplify.Auth.getPlugin("awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin)?.let { plugin -> + plugin.clearFederationToIdentityPool( + { Log.i("AuthQuickstart", "Federation cleared successfully.") }, + { Log.e("AuthQuickstart", "Failed to clear federation.", it) } + ) +} +``` + + +`clearFederationToIdentityPool` will only clear the session from the local cache, the developer needs to handle signing out from the federated provider. + + +### Provide Custom Identity Id + +You can provide a custom identity id to the `federateToIdentityPool` API. This is useful when you want to use the same identity id across multiple devices. + +#### [Java] + +```java +FederateToIdentityPoolOptions options = FederateToIdentityPoolOptions.builder() + .developerProvidedIdentityId("YOUR_CUSTOM_IDENTITY_ID") + .build(); + +if (Amplify.Auth.getPlugin("awsCognitoAuthPlugin") instanceof AWSCognitoAuthPlugin) { + AWSCognitoAuthPlugin plugin = (AWSCognitoAuthPlugin) Amplify.Auth.getPlugin("awsCognitoAuthPlugin"); + plugin.federateToIdentityPool( + "YOUR_TOKEN", + AuthProvider.facebook(), + options, + result -> { + Log.i("AuthQuickstart", "Successful federation to Identity Pool."); + // use result.getCredentials() + }, + e -> { + Log.e("AuthQuickstart", "Failed to federate to Identity Pool.", e) + } + ); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +val options = FederateToIdentityPoolOptions.builder() + .developerProvidedIdentityId("YOUR_CUSTOM_IDENTITY_ID") + .build() + +(Amplify.Auth.getPlugin("awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin)?.let { plugin -> + plugin.federateToIdentityPool( + "YOUR_TOKEN", + AuthProvider.facebook(), + options, + { + Log.i("AuthQuickstart", "Successful federation to Identity Pool.") + // use "it.credentials" + }, + { + Log.e("AuthQuickstart", "Failed to federate to Identity Pool.", it) + } + ) +} +``` + + + +## Subscribing Events + +You can take specific actions when users sign-in or sign-out by subscribing authentication events in your app. Please see our [Hub Module Developer Guide](/[platform]/build-a-backend/auth/connect-your-frontend/listen-to-auth-events/) for more information. + +## Identity Pool Federation + +Imagine that you are creating a mobile app that accesses AWS resources, such as a game that runs on a mobile device and stores player and score information using Amazon S3 and DynamoDB. + +When you write such an app, you make requests to AWS services that must be signed with an AWS access key. However, we strongly recommend that you do not embed or distribute long-term AWS credentials with apps that a user downloads to a device, even in an encrypted store. Instead, build your app so that it requests temporary AWS security credentials dynamically when needed using web identity federation. The supplied temporary credentials map to an AWS role that has only the permissions needed to perform the tasks required by the mobile app. + +With web identity federation, you don't need to create custom sign-in code or manage your own user identities. Instead, users of your app can sign in using a well-known external identity provider (IdP), such as Login with Amazon, Facebook, Google, or any other OpenID Connect (OIDC)-compatible IdP. They can receive an authentication token, and then exchange that token for temporary security credentials in AWS that map to an IAM role with permissions to use the resources in your AWS account. Using an IdP helps you keep your AWS account secure, because you don't have to embed and distribute long-term security credentials with your application. + +You can use `federateToIdentityPool` to get AWS credentials directly from Cognito Federated Identities and not use User Pool federation. If you have logged in with `Auth.signIn` you **can not** call `federateToIdentityPool` as Amplify will perform this federation automatically for you in the background. In general, you should only call `Auth.federateToIdentityPool` when using OAuth flows. + +You can use the escape hatch API `federateToIdentityPool` with a valid token from other social providers. + +```swift +func federateToIdentityPools() async throws { + guard let authCognitoPlugin = try Amplify.Auth.getPlugin( + for: "awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin else { + fatalError("Unable to get the Auth plugin") + } + do { + let result = try await authCognitoPlugin.federateToIdentityPool( + withProviderToken: "YOUR_TOKEN", for: .facebook) + print("Federation successful with result: \(result)") + } catch { + print("Failed to federate to identity pools with error: \(error)") + } +} +``` + + +Note that when federated, API's such as Auth.getCurrentUser() will throw an error as the user is not authenticated with User Pools. + + +### Retrieve Session + +After federated login, you can retrieve session using the `Auth.fetchAuthSession` API. + +### Token Refresh + + +NOTE: Automatic authentication token refresh is NOT supported when federated. + + +By default, Amplify will **NOT** automatically refresh the tokens from the federated providers. You will need to handle the token refresh logic and provide the new token to the `federateToIdentityPool` API. + +### Clear Session + +You can clear the federated session using the `clearFederationToIdentityPool` API. + +```swift +func clearFederationToIdentityPools() async throws { + guard let authCognitoPlugin = try Amplify.Auth.getPlugin( + for: "awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin else { + fatalError("Unable to get the Auth plugin") + } + do { + try await authCognitoPlugin.clearFederationToIdentityPool() + print("Federation cleared successfully") + } catch { + print("Clear federation failed with error: \(error)") + } +} +``` + + +clearFederationToIdentityPool will only clear the session from local cache, developer need to handle signing out from the federated provider. + + +### Provide Custom Identity Id + +You can provide a custom identity id to the `federateToIdentityPool` API. This is useful when you want to use the same identity id across multiple devices. + +```swift +func federateToIdentityPoolsUsingCustomIdentityId() async throws { + guard let authCognitoPlugin = try Amplify.Auth.getPlugin( + for: "awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin else { + fatalError("Unable to get the Auth plugin") + } + do { + let identityId = "YOUR_CUSTOM_IDENTITY_ID" + let result = try await authCognitoPlugin.federateToIdentityPool( + withProviderToken: "YOUR_TOKEN", + for: .facebook, + options: .init(developerProvidedIdentityID: identityId)) + print("Federation successful with result: \(result)") + } catch { + print("Failed to federate to identity pools with error: \(error)") + } +} +``` + +## Keychain Sharing + +### Migrating to a Shared Keychain + +To use a shared keychain: + +1. In Xcode, go to Project Settings β†’ Your Target β†’ Signing & Capabilities +2. Select +Capability +3. Add Keychain Sharing capability +4. Add a keychain group +5. Repeat for all apps for which you want to share auth state, adding the same keychain group for all of them + +To move to the shared keychain using this new keychain access group, specify the `accessGroup` parameter when instantiating the `AWSCognitoAuthPlugin`. If a user is currently signed in, they will be signed out when first using the access group: + +```swift +let accessGroup = AccessGroup(name: "\(teamID)com.example.sharedItems") +let secureStoragePreferences = AWSCognitoSecureStoragePreferences( + accessGroup: accessGroup) +try Amplify.add( + plugin: AWSCognitoAuthPlugin( + secureStoragePreferences: secureStoragePreferences)) +try Amplify.configure() +``` + +If you would prefer the user session to be migrated (which will allow the user to continue to be signed in), then specify the `migrateKeychainItemsOfUserSession` boolean in the AccessGroup to be true like so: + +```swift +let accessGroup = AccessGroup( + name: "\(teamID)com.example.sharedItems", + migrateKeychainItemsOfUserSession: true) +let secureStoragePreferences = AWSCognitoSecureStoragePreferences( + accessGroup: accessGroup) +try Amplify.add( + plugin: AWSCognitoAuthPlugin( + secureStoragePreferences: secureStoragePreferences)) +try Amplify.configure() +``` + +Sign in a user with any sign-in method within one app that uses this access group. After reloading another app that uses this access group, the user will be signed in. Likewise, signing out of one app will sign out the other app after reloading it. + +### Migrating to another Shared Keychain + +To move to a different access group, update the name parameter of the AccessGroup to be the new access group. Set `migrateKeychainItemsOfUserSession` to `true` to migrate an existing user session under the previously used access group. + +### Migrating from a Shared Keychain + +If you'd like to stop sharing state between this app and other apps, you can set the access group to be `AccessGroup.none` or `AccessGroup.none(migrateKeychainItemsOfUserSession: true)` if you'd like the session to be migrated. + +### Retrieving Team ID + +First, ensure your Info.plist has the `AppIdentifierPrefix` key: + +```xml title="Info.plist" + + + + + AppIdentifierPrefix + $(AppIdentifierPrefix) + + +``` + +Then, you can retrieve the team ID from your Info.plist: + +```swift +guard let teamID = Bundle.main.infoDictionary?["AppIdentifierPrefix"] as? String else { + fatalError("AppIdentifierPrefix key not found in Info.plist") +} +``` + + +## Subscribing to Events + +You can take specific actions when users sign-in or sign-out by subscribing to authentication events in your app. Please see our [Hub Module Developer Guide](/[platform]/build-a-backend/auth/connect-your-frontend/listen-to-auth-events/) for more information. + +## Identity Pool Federation + +You can alternatively create your own custom credentials provider to get AWS credentials directly from Cognito Federated Identities and not use User Pool federation. You must supply the custom credentials provider to Amplify via the `Amplify.configure` method call. Below, you can see sample code of how such a custom provider can be built to achieve the use case. + +```js +import { Amplify } from 'aws-amplify'; +import { + fetchAuthSession, + CredentialsAndIdentityIdProvider, + CredentialsAndIdentityId, + GetCredentialsOptions, + AuthTokens, +} from 'aws-amplify/auth'; + +// Note: This example requires installing `@aws-sdk/client-cognito-identity` to obtain Cognito credentials +// npm add @aws-sdk/client-cognito-identity +import { CognitoIdentity } from '@aws-sdk/client-cognito-identity'; + +// You can make use of the sdk to get identityId and credentials +const cognitoidentity = new CognitoIdentity({ + region: '', +}); + +// Note: The custom provider class must implement CredentialsAndIdentityIdProvider +class CustomCredentialsProvider implements CredentialsAndIdentityIdProvider { + + // Example class member that holds the login information + federatedLogin?: { + domain: string, + token: string + }; + + // Custom method to load the federated login information + loadFederatedLogin(login?: typeof this.federatedLogin) { + // You may also persist this by caching if needed + this.federatedLogin = login; + } + + async getCredentialsAndIdentityId( + getCredentialsOptions: GetCredentialsOptions + ): Promise { + try { + + // You can add in some validation to check if the token is available before proceeding + // You can also refresh the token if it's expired before proceeding + + const getIdResult = await cognitoidentity.getId({ + // Get the identityPoolId from config + IdentityPoolId: '', + Logins: { [this.federatedLogin.domain]: this.federatedLogin.token }, + }); + + const cognitoCredentialsResult = await cognitoidentity.getCredentialsForIdentity({ + IdentityId: getIdResult.IdentityId, + Logins: { [this.federatedLogin.domain]: this.federatedLogin.token }, + }); + + const credentials: CredentialsAndIdentityId = { + credentials: { + accessKeyId: cognitoCredentialsResult.Credentials?.AccessKeyId, + secretAccessKey: cognitoCredentialsResult.Credentials?.SecretKey, + sessionToken: cognitoCredentialsResult.Credentials?.SessionToken, + expiration: cognitoCredentialsResult.Credentials?.Expiration, + }, + identityId: getIdResult.IdentityId, + }; + return credentials; + } catch (e) { + console.log('Error getting credentials: ', e); + } + } + // Implement this to clear any cached credentials and identityId. This can be called when signing out of the federation service. + clearCredentialsAndIdentityId(): void {} +} + +// Create an instance of your custom provider +const customCredentialsProvider = new CustomCredentialsProvider(); +Amplify.configure(awsconfig, { + Auth: { + // Supply the custom credentials provider to Amplify + credentialsProvider: customCredentialsProvider + }, +}); + +``` + +Now that the custom credentials provider is built and supplied to `Amplify.configure`, let's look at how you can use the custom credentials provider to finish federation into Cognito identity pool. + + +### Facebook Sign-in (React Native - Expo) + +```javascript +import Expo from 'expo'; +import React from 'react'; +import { fetchAuthSession } from 'aws-amplify/auth'; + +const App = () => { + const signIn = async () => { + const { type, token, expires } = + await Expo.Facebook.logInWithReadPermissionsAsync( + 'YOUR_FACEBOOK_APP_ID', + { + permissions: ['public_profile'] + } + ); + if (type === 'success') { + // sign in with federated identity + try { + customCredentialsProvider.loadFederatedLogin({ + domain: 'graph.facebook.com', + token: token + }); + const fetchSessionResult = await fetchAuthSession(); // will return the credentials + console.log('fetchSessionResult: ', fetchSessionResult); + } catch (err) { + console.log(err); + } + } + }; + + // ... + + return ( + + + + ); +} +``` + +### Google sign-in (React) + +```jsx +import React, { useEffect } from 'react'; +import jwt from 'jwt-decode'; +import { + fetchAuthSession, +} from 'aws-amplify/auth'; + +const SignInWithGoogle = () => { + useEffect(() => { + // Check for an existing Google client initialization + if (!window.google?.accounts) createScript(); + }, []); + + // Load the Google client + const createScript = () => { + const script = document.createElement('script'); + script.src = 'https://accounts.google.com/gsi/client'; + script.async = true; + script.defer = true; + script.onload = initGsi; + document.body.appendChild(script); + } + + // Initialize Google client and render Google button + const initGsi = () => { + if (window.google?.accounts) { + window.google.accounts.id.initialize({ + client_id: process.env.GOOGLE_CLIENT_ID, + callback: (response: any) => { + customCredentialsProvider.loadFederatedLogin({ + domain: 'accounts.google.com', + token: response.credential, + }); + const fetchSessionResult = await fetchAuthSession(); // will return the credentials + console.log('fetchSessionResult: ', fetchSessionResult); + }, + }); + window.google.accounts.id.renderButton( + document.getElementById('googleSignInButton'), + { theme: 'outline', size: 'large' } + ); + } + } + + return ( +
    +
    + ); +} +``` + +### Federate with Auth0 + +You can use `Auth0` as one of the providers of your Cognito Identity Pool. This will allow users authenticated via Auth0 have access to your AWS resources. + +Step 1. [Follow Auth0 integration instructions for Cognito Federated Identity Pools](https://auth0.com/docs/customize/integrations/aws/amazon-cognito) + +Step 2. Login with `Auth0`, then use the id token returned to get AWS credentials from `Cognito Federated Identity Pools` using custom credentials provider you created at the start: + +```js +import { fetchAuthSession } from 'aws-amplify/auth'; + +const { idToken, domain, name, email, phoneNumber } = getFromAuth0(); // get the user credentials and info from auth0 + +async function getCognitoCredentials() { + try { + customCredentialsProvider.loadFederatedLogin({ + domain, + token: idToken + }); + const fetchSessionResult = await fetchAuthSession(); // will return the credentials + console.log('fetchSessionResult: ', fetchSessionResult); + } catch (err) { + console.log(err); + } +} +``` + + +## Lambda Triggers + +With the triggers property of defineAuth and defineFunction from the new Functions implementation, you can define [Lambda Triggers](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html) for your Cognito User Pool. These enable you to add custom functionality to your registration and authentication flows. [Check out a preSignUp hook example here.](/[platform]/build-a-backend/functions/examples/email-domain-filtering/) + +### Pre Authentication and Pre Sign-up Lambda triggers + +If you have a Pre Authentication Lambda trigger enabled, you can pass `clientMetadata` as an option for `signIn`. This metadata can be used to implement additional validations around authentication. + +```ts +import { signIn } from 'aws-amplify/auth'; + +async function handleSignIn(username: string, password: string) { + try { + await signIn({ + username, + password, + options: { + clientMetadata: {} // Optional, an object of key-value pairs which can contain any key and will be passed to your Lambda trigger as-is. + } + }); + } catch (err) { + console.log(err); + } +} +``` + +### Passing metadata to other Lambda triggers + +Many Cognito Lambda Triggers also accept unsanitized key/value pairs in the form of a `clientMetadata` attribute. This attribute can be specified for various Auth APIs which result in Cognito Lambda Trigger execution. + +These APIs include: + +- `signIn` +- `signUp` +- `confirmSignIn` +- `confirmSignUp` +- `resetPassword` +- `confirmResetPassword` +- `resendSignUpCode` +- `updateUserAttributes` +- `fetchAuthSession` + +Additionally, you can configure a `ClientMetadataProvider` which passes the `clientMetadata` to internal `fetchAuthSession` calls: + +```javascript +// Set global clientMetadata (affects all token refreshes) +import { cognitoUserPoolsTokenProvider } from 'aws-amplify/auth/cognito'; + +const clientMetadataProvider = () => Promise.resolve({ + 'app-version': '1.0.0', + 'device-type': 'mobile' +}); + +cognitoUserPoolsTokenProvider.setClientMetadataProvider(clientMetadataProvider); +``` + +Please note that some of triggers which accept a `validationData` attribute will use `clientMetadata` as the value for `validationData`. Exercise caution with using `clientMetadata` when you are relying on `validationData`. + +## Working with AWS service objects + +You can use AWS _Service Interface Objects_ to work with AWS Services in authenticated State. You can call methods on any AWS Service interface object by passing your credentials from Amplify `fetchAuthSession` to the service call constructor: + +```javascript +import { fetchAuthSession } from 'aws-amplify/auth'; +import Route53 from 'aws-sdk/clients/route53'; + +async function changeResourceRecordSets() { + try { + const { credentials } = await fetchAuthSession(); + + const route53 = new Route53({ + apiVersion: '2013-04-01', + credentials + }); + + // more code working with route53 object + //route53.changeResourceRecordSets(); + } catch (err) { + console.log(err); + } +} +``` + +> **Warning:** Note: To work with Service Interface Objects, your Amazon Cognito users' [IAM role](https://docs.aws.amazon.com/cognito/latest/developerguide/iam-roles.html) must have the appropriate permissions to call the requested services. + +## Custom Token providers + +Create a custom Auth token provider for situations where you would like provide your own tokens for a service. For example, using OIDC Auth with AppSync. You must supply the token provider to Amplify via the `Amplify.configure` method call. Below, you can see sample code of how such a custom provider can be built to achieve the use case. + +```javascript +import { Amplify } from 'aws-amplify'; +import { TokenProvider, decodeJWT } from 'aws-amplify/auth'; + +// ... + +const myTokenProvider: TokenProvider = { + async getTokens({ forceRefresh } = {}) { + if (forceRefresh) { + // try to obtain new tokens if possible + } + + const accessTokenString = ''; + const idTokenString = ''; + + return { + accessToken: decodeJWT(accessTokenString), + idToken: decodeJWT(idTokenString), + }; + }, +}; + +Amplify.configure(awsconfig, { + Auth: { + tokenProvider: myTokenProvider + } +}); + +``` +## API reference + +For the complete API documentation for Authentication module, visit our [API Reference](https://aws-amplify.github.io/amplify-js/api/modules/aws_amplify.auth.html) + + +--- + +--- +title: "Use existing Cognito resources" +section: "build-a-backend/auth" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-02-19T23:36:30.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/use-existing-cognito-resources/" +--- + +Amplify Auth can be configured to use an existing Amazon Cognito user pool and identity pool. If you are in a team setting or part of a company that has previously created auth resources, you can [configure the client library directly](#use-auth-resources-without-an-amplify-backend), or maintain references with [AWS Cloud Development Kit (AWS CDK)](https://aws.amazon.com/cdk/) in your Amplify backend. + +> **Info:** **Note:** when using existing auth resources, it may be necessary to add additional policies or permissions for your authenticated and unauthenticated IAM roles. These changes must be performed manually. + +## Use auth resources without an Amplify backend + + +You can use existing resources without an Amplify backend by configuring the client library directly. + +```ts title="src/main.ts" +import { Amplify } from "aws-amplify" + +Amplify.configure({ + Auth: { + Cognito: { + userPoolId: "", + userPoolClientId: "", + identityPoolId: "", + loginWith: { + email: true, + }, + signUpVerificationMethod: "code", + userAttributes: { + email: { + required: true, + }, + }, + allowGuestAccess: true, + passwordFormat: { + minLength: 8, + requireLowercase: true, + requireUppercase: true, + requireNumbers: true, + requireSpecialCharacters: true, + }, + }, + }, +}) +``` + + +Configuring the mobile client libraries directly is not supported, however you can manually create `amplify_outputs.json` with the following schema: + +> **Info:** **Note:** it is strongly recommended to use backend outputs to generate this file for each sandbox or branch deployment + +```json title="amplify_outputs.json" +{ + "version": "1", + "auth": { + "aws_region": "", + "user_pool_id": "", + "user_pool_client_id": "", + "identity_pool_id": "", + "username_attributes": ["email"], + "standard_required_attributes": ["email"], + "user_verification_types": ["email"], + "unauthenticated_identities_enabled": true, + "password_policy": { + "min_length": 8, + "require_lowercase": true, + "require_uppercase": true, + "require_numbers": true, + "require_symbols": true + } + } +} +``` + + +## Use auth resources with an Amplify backend + +> **Warning:** Amplify cannot modify the configuration of your referenced resources and only captures the resource configuration at the time of reference, any subsequent changes made to the referenced resources will not be automatically reflected in your Amplify backend. + +If you have created Amazon Cognito resources outside of the context of your Amplify app such as creating resources through the AWS Console or consuming resources created by a separate team, you can use `referenceAuth` to reference the existing resources. It requires a user pool, a user pool client, identity pool, and an authenticated & unauthenticated IAM role configured on your identity pool. + +```ts title="amplify/auth/resource.ts" +import { referenceAuth } from '@aws-amplify/backend'; + +export const auth = referenceAuth({ + userPoolId: 'us-east-1_xxxx', + identityPoolId: 'us-east-1:b57b7c3b-9c95-43e4-9266-xxxx', + authRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthauthenticatedU-xxxx', + unauthRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthunauthenticate-xxxx', + userPoolClientId: 'xxxx', +}); +``` + +> **Info:** IAM policies specific to your Amplify application will be appended to your authenticated and unauthenticated roles, and applications using the referenced resource will be able to create users in the Cognito user pool and identities in the Cognito identity pool. + +You can also use the [`access` property](/[platform]/build-a-backend/auth/grant-access-to-auth-resources/) to grant permissions to your auth resource from other Amplify backend resources. For example, if you have a function that needs to retrieve details about a user: + +```ts title="amplify/auth/resource.ts" +import { referenceAuth } from '@aws-amplify/backend'; +import { getUser } from "../functions/get-user/resource"; + +export const auth = referenceAuth({ + userPoolId: 'us-east-1_xxxx', + identityPoolId: 'us-east-1:b57b7c3b-9c95-43e4-9266-xxxx', + authRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthauthenticatedU-xxxx', + unauthRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthunauthenticate-xxxx', + userPoolClientId: 'xxxx', + access: (allow) => [ + allow.resource(getUser).to(["getUser"]), + ], +}); +``` + +Additionally, you can also use the `groups` property to reference groups in your user pool. This is useful if you want to work with groups in your application and provide access to resources such as storage based on group membership. + +```ts title="amplify/auth/resource.ts" +import { referenceAuth } from '@aws-amplify/backend'; +import { getUser } from "../functions/get-user/resource"; + +export const auth = referenceAuth({ + userPoolId: 'us-east-1_xxxx', + identityPoolId: 'us-east-1:b57b7c3b-9c95-43e4-9266-xxxx', + authRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthauthenticatedU-xxxx', + unauthRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthunauthenticate-xxxx', + userPoolClientId: 'xxxx', + groups: { + admin: "arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthadminGroupRole-xxxx", + }, +}); +``` + +In a team setting you may want to reference a different set of auth resources depending on the deployment context. For instance if you have a `staging` branch that should reuse resources from a separate "staging" environment compared to a `production` branch that should reuse resources from the separate "production" environment. In this case we recommend using environment variables. + +```ts title="amplify/auth/resource.ts" +import { referenceAuth } from '@aws-amplify/backend'; + +export const auth = referenceAuth({ + userPoolId: process.env.MY_USER_POOL_ID, + identityPoolId: process.env.MY_IDENTITY_POOL_ID, + authRoleArn: process.env.MY_AUTH_ROLE_ARN, + unauthRoleArn: process.env.MY_UNAUTH_ROLE_ARN, + userPoolClientId: process.env.MY_USER_POOL_CLIENT_ID, +}); +``` + +Environment variables must be configured separately on your machine for sandbox deployments and Amplify console for branch deployments. + +## Next steps + +- [Learn how to connect your frontend](/[platform]/build-a-backend/auth/connect-your-frontend/) + +--- + +--- +title: "Use AWS SDK" +section: "build-a-backend/auth" +platforms: ["swift", "android"] +gen: 2 +last-updated: "2024-09-24T23:57:23.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/auth/use-aws-sdk/" +--- + +For advanced use cases where Amplify does not provide the functionality, you can retrieve an escape hatch to access the underlying Amazon Cognito client. + + +The escape hatch provides access to the underlying `AWSCognitoIdentityProvider` instance. Import the necessary types: + +```swift +import AWSCognitoAuthPlugin +import AWSCognitoIdentityProvider +``` + +Then retrieve the escape hatch with this code: + +```swift +func getEscapeHatch() { + let client: CognitoIdentityProviderClient + + // Get the instance of AWSCognitoAuthPlugin + let plugin = try? Amplify.Auth.getPlugin(for: "awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin + + // Get the instance of CognitoIdentityProviderClient + if case .userPoolAndIdentityPool(let userPoolClient, _) = plugin?.getEscapeHatch() { + client = userPoolClient + } else if case .userPool(let userPoolClient) = plugin?.getEscapeHatch() { + client = userPoolClient + } else { + fatalError("No user pool configuration found") + } + print("Fetched escape hatch - \(String(describing: client))") +} +``` + + + +You can access the underlying `CognitoIdentityProviderClient` and `CognitoIdentityClient` as shown below + +```kotlin +implementation "aws.sdk.kotlin:cognitoidentityprovider:1.0.44" +implementation "aws.sdk.kotlin:cognitoidentity:1.0.44" +``` + +#### [Kotlin] + +```kotlin +suspend fun resendCodeUsingEscapeHatch() { + // Get the instance of AWSCognitoAuthPlugin + val cognitoAuthPlugin = Amplify.Auth.getPlugin("awsCognitoAuthPlugin") + val cognitoAuthService = cognitoAuthPlugin.escapeHatch as AWSCognitoAuthService + + // Get the instance of CognitoIdentityProviderClient + val cognitoIdentityProviderClient = cognitoAuthService.cognitoIdentityProviderClient + val request = ResendConfirmationCodeRequest { + clientId = "xxxxxxxxxxxxxxxx" + username = "user1" + } + val response = cognitoIdentityProviderClient?.resendConfirmationCode(request) +} +``` + +#### [Java] + + + +[Learn more about consuming Kotlin clients from Java using either a blocking interface or an equivalent async interface based on futures](https://github.com/awslabs/smithy-kotlin/blob/main/docs/design/kotlin-smithy-sdk.md#java-interop). + + + +```java +// Get the instance of AWSCognitoAuthPlugin +AWSCognitoAuthPlugin cognitoAuthPlugin = (AWSCognitoAuthPlugin) Amplify.Auth.getPlugin("awsCognitoAuthPlugin"); + +// Get the instance of CognitoIdentityProviderClient +CognitoIdentityProviderClient client = cognitoAuthPlugin.getEscapeHatch().getCognitoIdentityProviderClient(); +ResendConfirmationCodeRequest request = ResendConfirmationCodeRequest.Companion.invoke(dslBuilder -> { + dslBuilder.setClientId("xxxxxxxxxxxxxxxx"); + dslBuilder.setUsername("user1"); + return null; +}); + +assert client != null; +client.resendConfirmationCode(request, new Continuation() { + @NonNull + @Override + public CoroutineContext getContext() { + return GlobalScope.INSTANCE.getCoroutineContext(); + } + + @Override + public void resumeWith(@NonNull Object resultOrException) { + Log.i(TAG, "Result: " + resultOrException); + } +}); +``` + + + +--- + +--- +title: "API References" +section: "build-a-backend/auth" +platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] +gen: 2 +last-updated: "" +url: "https://docs.amplify.aws/react/build-a-backend/auth/reference/" +--- + + + +--- + +--- +title: "Data" +section: "build-a-backend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-02-21T20:06:17.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/" +--- + + + +--- + +--- +title: "Set up Amplify Data" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-11-13T16:29:27.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/set-up-data/" +--- + +In this guide, you will learn how to set up Amplify Data. This includes building a real-time API and database using TypeScript to define your data model, and securing your API with authorization rules. We will also explore using AWS Lambda to scale to custom use cases. + +Before you begin, you will need: + +- [Node.js](https://nodejs.org/) v18.16.0 or later +- [npm](https://www.npmjs.com/) v6.14.4 or later +- [git](https://git-scm.com/) v2.14.1 or later + +With Amplify Data, you can build a secure, real-time API backed by a database in minutes. After you define your data model using TypeScript, Amplify will deploy a real-time API for you. This API is powered by AWS AppSync and connected to an Amazon DynamoDB database. You can secure your API with authorization rules and scale to custom use cases with AWS Lambda. + +## Building your data backend + +If you've run `npm create amplify@latest` already, you should see an `amplify/data/resource.ts` file, which is the central location to configure your data backend. The most important element is the `schema` object, which defines your backend data models (`a.model()`) and custom queries (`a.query()`), mutations (`a.mutation()`), and subscriptions (`a.subscription()`). + +```ts title="amplify/data/resource.ts" +import { a, defineData, type ClientSchema } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a.model({ + content: a.string(), + isDone: a.boolean() + }) + .authorization(allow => [allow.publicApiKey()]) +}); + +// Used for code completion / highlighting when making requests from frontend +export type Schema = ClientSchema; + +// defines the data resource to be deployed +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { expiresInDays: 30 } + } +}); +``` + +Every `a.model()` automatically creates the following resources in the cloud: + +- a DynamoDB database table to store records +- query and mutation APIs to create, read (list/get), update, and delete records +- `createdAt` and `updatedAt` fields that help you keep track of when each record was initially created or when it was last updated +- real-time APIs to subscribe for create, update, and delete events of records + +The `allow.publicApiKey()` rule designates that anyone authenticated using an API key can create, read, update, and delete todos. + +To deploy these resources to your cloud sandbox, run the following CLI command in your terminal: + + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + + + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox --outputs-out-dir +``` + + + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox --outputs-out-dir +``` + + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox --outputs-format dart --outputs-out-dir lib +``` + + +## Connect your application code to the data backend + +Once the cloud sandbox is up and running, it will also create an `amplify_outputs.json` file, which includes the relevant connection information to your data backend, like your API endpoint URL and API key. + +To connect your frontend code to your backend, you need to: + +1. Configure the Amplify library with the Amplify client configuration file (`amplify_outputs.json`) +2. Generate a new API client from the Amplify library +3. Make an API request with end-to-end type-safety + + +First, install the Amplify client library to your project: + +```bash title="Terminal" showLineNumbers={false} +npm add aws-amplify +``` + + + +In your app's entry point, typically **main.tsx** for React apps created using Vite, make the following edits: + +```tsx title="src/main.tsx" +import { Amplify } from 'aws-amplify'; +import outputs from '../amplify_outputs.json'; + +Amplify.configure(outputs); +``` + + + +In your app's entry point, typically **main.ts** for Vue apps created using Vite, make the following edits: + +```tsx title="src/main.ts" +import { Amplify } from 'aws-amplify'; +import outputs from '../amplify_outputs.json'; + +Amplify.configure(outputs); +``` + + + +Under Gradle Scripts, open build.gradle (Module :app), add the following lines: + +```kotlin title="app/build.gradle.kts" +android { + compileOptions { + // Support for modern Java features + isCoreLibraryDesugaringEnabled = true + } +} + +dependencies { + // Amplify API dependencies + // highlight-start + implementation("com.amplifyframework:aws-api:ANDROID_VERSION") + // highlight-end + // ... other dependencies + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:ANDROID_DESUGAR_VERSION") +} +``` + +Click **Sync Now** in the notification bar above the file editor to sync these dependencies. + +Next, configure the Amplify client library with the generated `amplify_outputs.json` file to make it aware of the backend API endpoint. *Note: verify that the **amplify_outputs.json** file is present in your **res/raw/** folder. + +Create a new `MyAmplifyApp` class that inherits from `Application` with the following code: + +> **Warning:** Before calling the `Amplify.configure` function, make sure to either download the `amplify_outputs.json` file from the console, or generate it with the following command: +> +> ```bash title="Terminal" showLineNumbers={false} +npx ampx generate outputs --app-id --branch main --out-dir app/src/main/res/raw +``` +> +> Next, be sure the file you generated or downloaded is in the appropriate resource directory for your application (for example, `app/src/main/res/raw`) in your Android project. Otherwise, you will not be able to compile your application. + +```kt +package com.example.myapplication + +import android.app.Application +import android.util.Log +import com.amplifyframework.AmplifyException +import com.amplifyframework.api.aws.AWSApiPlugin +import com.amplifyframework.core.Amplify +import com.amplifyframework.core.configuration.AmplifyOutputs + +class MyAmplifyApp : Application() { + override fun onCreate() { + super.onCreate() + + try { + // Adds the API plugin that is used to issue queries and mutations + // to your backend. + Amplify.addPlugin(AWSApiPlugin()) + // Configures the client library to be aware of your backend API + // endpoint and authorization modes. + Amplify.configure(AmplifyOutputs(R.raw.amplify_outputs), applicationContext) + Log.i("Tutorial", "Initialized Amplify") + } catch (error: AmplifyException) { + Log.e("Tutorial", "Could not initialize Amplify", error) + } + } +} +``` + +This overrides the `onCreate()` to initialize Amplify when your application is launched. + +Next, configure your application to use your new custom Application class. Open **manifests** > **AndroidManifest.xml**, and add an `android:name` attribute with the value of your new class name: + +```xml + + + + + + +``` + +Build and run the application. In Logcat, you'll see a log line indicating success: + +```console title="Logcat" showLineNumbers={false} +com.example.MyAmplifyApp I/MyAmplifyApp: Initialized Amplify +``` + +Finally, let's generate the GraphQL client code for your Android application. Amplify Data uses GraphQL under the hood to make query, mutation, and subscription requests. The generated GraphQL client code helps you to author fully-typed API requests without needing to hand-author GraphQL requests and manually map them to Kotlin or Java code. + +```bash title="Terminal" showLineNumbers={false} +npx ampx generate graphql-client-code --format modelgen --model-target java --out +``` + + + +Drag and drop the **amplify_outputs.json** file from the Finder into Xcode. + +Next, add Amplify Library for Swift through Swift Package Manager. In Xcode, select **File** > **Add Packages...**. + +Then, enter the Amplify Library for Swift GitHub repo URL (https://github.com/aws-amplify/amplify-swift) into the search bar and hit **Enter**. + +Once the result is loaded, choose Up to **Next Major Version** as the **Dependency Rule**, then click **Add Package**. + +Choose which of the libraries you want added to your project. For this tutorial, select **AWSAPIPlugin** and **Amplify**, then click **Add Package**. + +Now let's add the necessary plugins into the Swift application by customizing the `init()` function of your app: + +```swift title="MyAmplifyApp" +import SwiftUI +// highlight-start +import Amplify +import AWSAPIPlugin +// highlight-end + +@main +struct MyAmplifyApp: App { + + // highlight-start + init() { + let awsApiPlugin = AWSAPIPlugin(modelRegistration: AmplifyModels()) + do { + try Amplify.add(plugin: awsApiPlugin) + try Amplify.configure(with: .amplifyOutputs) + print("Initialized Amplify"); + } catch { + // simplified error handling for the tutorial + print("Could not initialize Amplify: \(error)") + } + } + // highlight-end + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} +``` + +Finally, let's generate the GraphQL client code for your Swift application. Amplify Data uses GraphQL under the hood to make query, mutation, and subscription requests. The generated GraphQL client code helps you to author fully-typed API requests without needing to hand-author GraphQL requests and manually map them to Swift code. + +```bash title="Terminal" showLineNumbers={false} +npx ampx generate graphql-client-code --format modelgen --model-target swift --out /AmplifyModels +``` + +Drag and drop the **AmplifyModels** folder into your Xcode project to add the generated files. + + + +From your project root directory, find and modify your **pubspec.yaml** and add the Amplify plugins to the project dependencies. + +```yaml title="pubspec.yaml" +dependencies: + // highlight-start + amplify_api: ^2.0.0 + amplify_flutter: ^2.0.0 + // highlight-end + flutter: + sdk: flutter +``` + +Install the dependencies by running the following command. Depending on your development environment, you may perform this step via your IDE (or it may even be performed for you automatically). + +```bash title="Terminal" showLineNumbers={false} +flutter pub get +``` + +Now, let's generate the GraphQL client code for your Flutter application. Amplify Data uses GraphQL under the hood to make query, mutation, and subscription requests. The generated GraphQL client code helps you to author fully-typed API requests without needing to hand-author GraphQL requests and manually map them to Dart code. + +```bash title="Terminal" showLineNumbers={false} +npx ampx generate graphql-client-code --format modelgen --model-target dart --out /lib/models +``` + +Finally, let's add the necessary plugins into the Flutter application by customizing the `main()` function of the **lib/main.dart** file: + +```dart title="lib/main.dart" +// highlight-start +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +// highlight-end +import 'package:flutter/material.dart'; + +// highlight-start +import 'amplify_outputs.dart'; +import 'models/ModelProvider.dart'; +// highlight-end + +Future main() async { + // highlight-start + try { + final api = AmplifyAPI( + options: APIPluginOptions( + modelProvider: ModelProvider.instance + ) + ); + await Amplify.addPlugins([api]); + await Amplify.configure(amplifyConfig); + + safePrint('Successfully configured Amplify'); + } on Exception catch (e) { + safePrint('Error configuring Amplify: $e'); + } + // highlight-end + + runApp(const MyApp()); +} +``` + +## Write data to your backend + + +Let's first add a button to create a new todo item. To make a "create Todo" API request, generate the data client using `generateClient()` in your frontend code, and then call `.create()` operation for the Todo model. The Data client is a fully typed client that gives you in-IDE code completion. To enable this in-IDE code completion capability, pass in the `Schema` type to the `generateClient` function. + + + +```tsx title="src/TodoList.tsx" +import type { Schema } from '../amplify/data/resource' +import { generateClient } from 'aws-amplify/data' + +const client = generateClient() + +export default function TodoList() { + const createTodo = async () => { + await client.models.Todo.create({ + content: window.prompt("Todo content?"), + isDone: false + }) + } + + return
    + +
    +} +``` + + + +```html title="src/TodoList.vue" + + + +``` + + + +Run the application in local development mode with `npm run dev` and check your network tab after creating a todo. You should see a successful request to a `/graphql` endpoint. + + + +Try playing around with the code completion of `.update(...)` and `.delete(...)` to get a sense of other mutation operations. + + + + + +```ts title="todo-list.component.ts" +import type { Schema } from '../amplify/data/resource'; +import { Component } from '@angular/core'; +import { generateClient } from 'aws-amplify/data'; + +const client = generateClient(); + +@Component({ + selector: 'app-todo-list', + template: ` + + ` +}) +export class TodoListComponent { + async createTodo() { + await client.models.Todo.create({ + content: window.prompt("Todo content?"), + isDone: false + }); + } +} +``` + +Run the application in local development mode and check your network tab after creating a todo. You should see a successful request to a `/graphql` endpoint. + + + +Try playing around with the code completion of `.update(...)` and `.delete(...)` to get a sense of other mutation operations. + + + + + +In your MainActivity, add a button to create a new todo. + +```kt title="MainActivity.kt" +// imports + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MyApplicationTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + // highlight-start + Column { + Button(onClick = { + val todo = Todo.builder() + .content("My first todo") + .isDone(false) + .build() + + Amplify.API.mutate(ModelMutation.create(todo), + { Log.i("MyAmplifyApp", "Added Todo with id: ${it.data.id}")}, + { Log.e("MyAmplifyApp", "Create failed", it)}, + ) + }) { + Text(text = "Create Todo") + } + } + // highlight-end + } + } + } + } +} +``` + +Build and run your app. Then, click on "Create Todo" on the app. Your Logcat should show you that a todo was successfully added: + +```console title="Logcat" showLineNumbers={false} +com.example.MyAmplifyApp I/MyAmplifyApp: Added Todo with id: SOME_TODO_ID +``` + + + +Create a new file called `TodoViewModel.swift` and the `createTodo` function with the following code: + +```swift title="TodoViewModel.swift" +import Foundation +import Amplify + +@MainActor +class TodoViewModel: ObservableObject { + func createTodo() { + let todo = Todo( + content: "Build iOS Application", + isDone: false + ) + Task { + do { + let result = try await Amplify.API.mutate(request: .create(todo)) + switch result { + case .success(let todo): + print("Successfully created todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } catch let error as APIError { + print("Failed to create todo: ", error) + } catch { + print("Unexpected error: \(error)") + } + } + } +} + +``` + +Update `ContentView.swift` with the following code: + +```swift title="ContentView.swift" +struct ContentView: View { + + // highlight-start + // Create an observable object instance. + @StateObject var vm = TodoViewModel() + // highlight-end + + var body: some View { + // highlight-start + VStack { + Button(action: { + vm.createTodo() + }) { + HStack { + Text("Add a New Todo") + Image(systemName: "plus") + } + } + .accessibilityLabel("New Todo") + } + // highlight-end + } +} +``` + +Now if you run the application, and click on the "Add a New Todo" button, you should see a log indicating a todo was created: + +```console title="Logs" showLineNumbers={false} +Successfully created todo: Todo(id: XYZ ...) +``` + + + +In your page, let's add a floating action button that creates a new todo. + +```dart title="lib/main.dart" +// ... main() +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: MyHomePage(), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key}); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Your todos', + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () async { + final newTodo = Todo(content: "New Flutter todo", isDone: false); + final request = ModelMutations.create(newTodo); + final response = await Amplify.API.mutate(request: request).response; + if (response.hasErrors) { + safePrint('Creating Todo failed.'); + } else { + safePrint('Creating Todo successful.'); + } + }, + tooltip: 'Add todo', + child: const Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} +``` + +Now if you run the application, and click on the floating action button, you should see a log indicating a todo was created: + +```console showLineNumbers={false} +Creating Todo successful. +``` + + +## Read data from your backend + +Next, list all your todos and then refetch the todos after a todo has been added: + + +```tsx title="src/TodoList.tsx" +import { useState, useEffect } from "react"; +import type { Schema } from "../amplify/data/resource"; +import { generateClient } from "aws-amplify/data"; + +const client = generateClient(); + +export default function TodoList() { + const [todos, setTodos] = useState([]); + + const fetchTodos = async () => { + const { data: items, errors } = await client.models.Todo.list(); + setTodos(items); + }; + + useEffect(() => { + fetchTodos(); + }, []); + + const createTodo = async () => { + await client.models.Todo.create({ + content: window.prompt("Todo content?"), + isDone: false, + }); + + fetchTodos(); + } + + return ( +
    + +
      + {todos.map(({ id, content }) => ( +
    • {content}
    • + ))} +
    +
    + ); +} +``` + + + +```html title="src/TodoList.vue" + + + +``` + + + +```ts title="todo-list.component.ts" +import type { Schema } from '../amplify/data/resource'; +import { Component, OnInit } from '@angular/core'; +import { generateClient } from 'aws-amplify/data'; + +const client = generateClient(); + +@Component({ + selector: 'app-todo-list', + template: ` +
    + +
      +
    • {{ todo.content }}
    • +
    +
    + ` +}) +export class TodoListComponent implements OnInit { + todos: Schema['Todo']['type'][] = []; + + async ngOnInit() { + await this.fetchTodos(); + } + + async fetchTodos() { + const { data: items } = await client.models.Todo.list(); + this.todos = items; + } + + async createTodo() { + await client.models.Todo.create({ + content: window.prompt('Todo content?'), + isDone: false + }); + await this.fetchTodos(); + } +} +``` + + + +Start by creating a new `TodoList` @Composable that fetches the data on the initial display of the TodoList: + +```kt title="MainActivity.kt" +@Composable +fun TodoList() { + var todoList by remember { mutableStateOf(emptyList()) } + + LaunchedEffect(Unit) { + // API request to list all Todos + Amplify.API.query(ModelQuery.list(Todo::class.java), + { + todoList = it.data.items.toList() + }, + { Log.e("MyAmplifyApp", "Failed to query.", it)}) + } + + LazyColumn { + items(todoList) { todo -> + Row { + // Render your activity item here + Checkbox(checked = todo.isDone, onCheckedChange = null) + Text(text = todo.content) + } + } + } +} +``` + +If you build and rerun the application, you should see the todo that was created in the previous build. But notice how when you click on the "create Todo" button, it doesn't add any new todos to the list below until the next time your app relaunches. To solve this, let's add real-time updates to the todo list. + + +Update the `listTodos` function in the `TodoViewModel.swift` for listing to-do items: + +```swift title="TodoViewModel.swift" +@MainActor +class TodoViewModel: ObservableObject { + + // highlight-next-line + @Published var todos: [Todo] = [] + + func createTodo() { + /// ... + } + + // highlight-start + func listTodos() { + Task { + do { + let result = try await Amplify.API.query(request: .list(Todo.self)) + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + self.todos = todos.elements + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } catch let error as APIError { + print("Failed to query list of todos: ", error) + } catch { + print("Unexpected error: \(error)") + } + } + } + // highlight-end +} +``` + +Now let's update the UI code to observe the todos. + +```swift title="ContentView.swift" +import SwiftUI +import Amplify + +struct ContentView: View { + @StateObject var vm = TodoViewModel() + + var body: some View { + VStack { + // highlight-start + List(vm.todos, id: \.id) { todo in + Text(todo.content ?? "") + } + // highlight-end + // .. Add a new Todo button + } + // highlight-start + .task { + await vm.listTodos() + } + // highlight-end + } +} + +``` + + + +Start by adding a new list to track the todos and the ability to fetch the todo list when it first renders: + +```dart title="lib/main.dart" +// ...main() + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: MyHomePage(), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key}); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + List _todos = []; + + @override + void initState() { + super.initState(); + _refreshTodos(); + } + + Future _refreshTodos() async { + try { + final request = ModelQueries.list(Todo.classType); + final response = await Amplify.API.query(request: request).response; + + final todos = response.data?.items; + if (response.hasErrors) { + safePrint('errors: ${response.errors}'); + return; + } + setState(() { + safePrint(todos); + _todos = todos!.whereType().toList(); + }); + } on ApiException catch (e) { + safePrint('Query failed: $e'); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Your todos', + ), + _todos.isEmpty == true + ? const Center( + child: Text( + "The list is empty.\nAdd some items by clicking the floating action button.", + textAlign: TextAlign.center, + ), + ) + : ListView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + itemCount: _todos.length, + itemBuilder: (context, index) { + final todo = _todos[index]; + return CheckboxListTile.adaptive( + value: todo.isDone, + title: Text(todo.content!), + onChanged: (isChecked) async {}, + ); + }), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () async { + final newTodo = Todo(content: "New Flutter todo", isDone: false); + final request = ModelMutations.create(newTodo); + final response = await Amplify.API.mutate(request: request).response; + if (response.hasErrors) { + safePrint('Creating Todo failed.'); + } else { + safePrint('Creating Todo successful.'); + } + }, + tooltip: 'Add todo', + child: const Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} + +``` + + +## Subscribe to real-time updates + + +You can also use `observeQuery` to subscribe to a live feed of your backend data. Let's refactor the code to use a real-time observeQuery instead. + +```tsx title="src/App.tsx" +import type { Schema } from "../amplify/data/resource"; +import { useState, useEffect } from "react"; +import { generateClient } from "aws-amplify/data"; + +const client = generateClient(); + +export default function TodoList() { + const [todos, setTodos] = useState([]); + + useEffect(() => { + const sub = client.models.Todo.observeQuery().subscribe({ + next: ({ items }) => { + setTodos([...items]); + }, + }); + + return () => sub.unsubscribe(); + }, []); + + const createTodo = async () => { + await client.models.Todo.create({ + content: window.prompt("Todo content?"), + isDone: false, + }); + // no more manual refetchTodos required! + // - fetchTodos() + }; + + return ( +
    + +
      + {todos.map(({ id, content }) => ( +
    • {content}
    • + ))} +
    +
    + ); +} +``` + + + +You can also use `observeQuery` to subscribe to a live feed of your backend data. Let's refactor the code to use a real-time observeQuery instead. + +```html title="src/TodoList.vue" + + + +``` + + + +You can also use `observeQuery` to subscribe to a live feed of your backend data. Let's refactor the code to use a real-time observeQuery instead. + +```ts title="todo-list.component.ts" +import type { Schema } from '../../../amplify/data/resource'; +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { generateClient } from 'aws-amplify/data'; +import { Subscription } from 'rxjs'; + +const client = generateClient(); + +@Component({ + selector: 'app-todos', + standalone: true, + imports: [CommonModule], + template: ` +
    +

    My todos

    + +
      +
    • + {{ todo.content }} +
    • +
    +
    + πŸ₯³ App successfully hosted. Try creating a new todo. +
    + + Review next steps of this tutorial. + +
    +
    + `, +}) +export class TodosComponent implements OnInit { + todos: Schema['Todo']['type'][] = []; + subscription?: Subscription; + + ngOnInit(): void { + this.listTodos(); + } + + ngOnDestroy(): void { + this.subscription?.unsubscribe(); + } + + listTodos() { + try { + this.subscription = client.models.Todo.observeQuery().subscribe({ + next: ({ items, isSynced }) => { + this.todos = items; + }, + }); + } catch (error) { + console.error('error fetching todos', error); + } + } + + createTodo() { + try { + client.models.Todo.create({ + content: window.prompt('Todo content'), + }); + this.listTodos(); + } catch (error) { + console.error('error creating todos', error); + } + } +} +``` + +Now try to open your app in two browser windows and see how creating a todo in one window automatically adds the todo in the second window as well. + + + +You can also use `.onCreate`, `.onUpdate`, or `.onDelete` to subscribe to specific events. Review [Subscribe to real-time events](/[platform]/build-a-backend/data/subscribe-data) to learn more about subscribing to specific mutation events. + + + + + +To add real-time updates, you can use the subscription feature of Amplify Data. It allows to subscribe to `onCreate`, `onUpdate`, and `onDelete` events of the application. In our example, let's append the list every time a new todo is added. + +```kt title="MainActivity.kt" +@Composable +fun TodoList() { + var todoList by remember { mutableStateOf(emptyList()) } + + LaunchedEffect(Unit) { + Amplify.API.query(ModelQuery.list(Todo::class.java), + { + todoList = it.data.items.toList() + }, + { Log.e("MyAmplifyApp", "Failed to query.", it)}) + // highlight-start + Amplify.API.subscribe(ModelSubscription.onCreate(Todo::class.java), + { Log.i("ApiQuickStart", "Subscription established") }, + { Log.i("ApiQuickStart", "Todo create subscription received: ${it.data}") + todoList = todoList + it.data + }, + { Log.e("ApiQuickStart", "Subscription failed", it) }, + { Log.i("ApiQuickStart", "Subscription completed") } + + ) + // highlight-end + } + + LazyColumn { + items(todoList) { todo -> + Row { + // Render your activity item here + Checkbox(checked = todo.isDone, onCheckedChange = null) + Text(text = todo.content) + } + } + } +} +``` +Now call `TodoList()` from the `onCreate()` function: + +```kt title="MainActivity.kt" +setContent { + MyAmplifyAppTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + Authenticator { state -> + Column { + Text( + text = "Hello ${state.user.username}!", + ) + .... + //highlight-next-line + TodoList() +``` + + + +To add real-time updates, you can use the subscription feature of Amplify Data. It allows to subscribe to `onCreate`, `onUpdate`, and `onDelete` events of the application. In our example, let's append the list every time a new todo is added. + +First, add a private variable to store the subscription. Then create the subscription on the `init()` initializer, and add the `subscribe()` and `cancel()` functions. + +```swift title="TodoViewModel.swift" +@MainActor +class TodoViewModel: ObservableObject { + @Published var todos: [Todo] = [] + + // highlight-start + private var subscription: AmplifyAsyncThrowingSequence> + + init() { + self.subscription = Amplify.API.subscribe(request: .subscription(of: Todo.self, type: .onCreate)) + } + + func subscribe() { + Task { + do { + for try await subscriptionEvent in subscription { + handleSubscriptionEvent(subscriptionEvent) + } + } catch { + print("Subscription has terminated with \(error)") + } + } + } + + private func handleSubscriptionEvent(_ subscriptionEvent: GraphQLSubscriptionEvent) { + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + print("Subscription connect state is \(subscriptionConnectionState)") + case .data(let result): + switch result { + case .success(let createdTodo): + print("Successfully got todo from subscription: \(createdTodo)") + todos.append(createdTodo) + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } + } + + func cancel() { + self.subscription.cancel() + } + // highlight-end + + func createTodo() { + /// ... + } + + func listTodos() { + /// ... + } +} +``` + +Then in `ContentView.swift`, when the view appears, call `vm.subscribe()`. On disappear, cancel the subscription. + +```swift title="ContentView.swift" +struct ContentView: View { + @StateObject var vm = TodoViewModel() + + var body: some View { + VStack { + // ... + } + // highlight-start + .onDisappear { + vm.cancel() + } + .task { + vm.listTodos() + vm.subscribe() + } + // highlight-end + } +} +``` + +Now if you rerun your app, a new todo should be appended to the list every time you create a new todo. + + + +To add real-time updates, you can use the subscription feature of Amplify Data. It allows to subscribe to `onCreate`, `onUpdate`, and `onDelete` events of the application. In our example, let's append the list every time a new todo is added. + +When the page renders, subscribe to `onCreate` events and then unsubscribe when the Widget is disposed. + +```dart title="lib/main.dart" +// ...main() +// ...MyApp +// ...MyHomePage + +class _MyHomePageState extends State { + List _todos = []; + // highlight-next-line + StreamSubscription>? subscription; + + @override + void initState() { + super.initState(); + _refreshTodos(); + // highlight-next-line + _subscribe(); + } + + // highlight-start + @override + void dispose() { + _unsubscribe(); + super.dispose(); + } + // highlight-end + + // highlight-start + void _subscribe() { + final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); + final Stream> operation = Amplify.API.subscribe( + subscriptionRequest, + onEstablished: () => safePrint('Subscription established'), + ); + subscription = operation.listen( + (event) { + safePrint('Subscription event data received: ${event.data}'); + setState(() { + _todos.add(event.data!); + }); + }, + onError: (Object e) => safePrint('Error in subscription stream: $e'), + ); + } + // highlight-end + + // highlight-start + void _unsubscribe() { + subscription?.cancel(); + subscription = null; + } + // highlight-end + + // ..._refreshTodos() + // ...build() +} +``` + + +## Conclusion + +Success! You've learned how to create your first real-time API and database with Amplify Data. + +### Next steps + +There's so much more to discover with Amplify Data. Learn more about: + +- [How to model your database table and their access patterns](/[platform]/build-a-backend/data/data-modeling) +- [Secure your API with fine-grained authorization rules](/[platform]/build-a-backend/data/customize-authz) +- [Create relationships between different database model](/[platform]/build-a-backend/data/data-modeling/relationships) +- [Add custom business logic](/[platform]/build-a-backend/data/custom-business-logic) + +--- + +--- +title: "Connect your app code to API" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-07T16:31:16.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/connect-to-API/" +--- + +In this guide, you will connect your application code to the backend API using the Amplify Libraries. Before you begin, you will need: + +- Your cloud sandbox with an Amplify Data resource up and running (`npx ampx sandbox`) +- A frontend application set up with the Amplify library installed +- [npm installed](https://docs.npmjs.com/getting-started) + +## Configure the Amplify Library + +When you deploy you're iterating on your backend (`npx ampx sandbox`), an **amplify_outputs.json** file is generated for you. This file contains your API's endpoint information and auth configurations. Add the following code to your app's entrypoint to initialize and configure the Amplify client library: + + +```ts +import { Amplify } from 'aws-amplify'; +import outputs from '../amplify_outputs.json'; + +Amplify.configure(outputs); +``` + +## Generate the Amplify Data client + +Once the Amplify library is configured, you can generate a "Data client" for your frontend code to make fully-typed API requests to your backend. + +> **Info:** **If you're using Amplify with a JavaScript-only frontend (i.e. not TypeScript), then you can still get a fully-typed data fetching experience by annotating the generated client with a JSDoc comment**. Select the **JavaScript** in the code block below to see how. + +To generate a new Data client, use the following code: + +#### [TypeScript] +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +// Now you should be able to make CRUDL operations with the +// Data client +const fetchTodos = async () => { + const { data: todos, errors } = await client.models.Todo.list(); +}; +``` + +#### [JavaScript] +```js +import { generateClient } from 'aws-amplify/data'; + +/** + * @type {import('aws-amplify/data').Client} + */ +const client = generateClient(); + +// Now you should be able to make CRUDL operations with the +// Data client +const fetchTodos = async () => { + const { data: todos, errors } = await client.models.Todo.list(); +}; +``` + + + +## Configure authorization mode + +The **Authorization Mode** determines how a request should be authorized with the backend. By default, Amplify Data uses the "userPool" authorization which uses the signed-in user credentials to sign an API request. If you use a `allow.publicApiKey()` authorization rules for your data models, you need to use "apiKey" as an authorization mode. Review [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz) to learn more about which authorization modes to choose for which type of request. A **Default Authorization Mode** is provided as part of the **amplify_outputs.json** that is generated upon a successful deployment. + + +You can generate different Data clients with different authorization modes or pass in the authorization mode at the request time. + +### Set authorization mode on a per-client basis + +To apply the same authorization mode on all requests from a Data client, specify the `authMode` parameter on the `generateClient` function. + +#### [API Key] + +Use "API Key" as your authorization mode when if defined the `allow.publicApiKey()` authorization rule. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient({ + authMode: 'apiKey', +}); +``` + +#### [Amazon Cognito user pool] + +Use "userPool" as your authorization mode when using Amazon Cognito user pool-based authorization rules, such as `allow.authenticated()`, `allow.owner()`, `allow.ownerDefinedIn()`, `allow.groupsDefinedIn()`, or `allow.groups()`. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient({ + authMode: 'userPool', +}); +``` + +#### [AWS IAM (including Amazon Cognito identity pool roles)] + +Use "identityPool" as your authorization mode when using Amazon Cognito identity pool-based authorization rules, such as `allow.guest()` or `allow.authenticated('identityPool')`. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient({ + authMode: 'identityPool', +}); +``` + +#### [OpenID Connect (OIDC)] +Use "oidc" as your authorization mode when connecting applications to a trusted identity provider. Private, owner, and group authorization can be configured with an OIDC authorization mode. Review the [OIDC authorization docs](/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider) to learn more. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient({ + authMode: 'oidc', +}); +``` + +#### [Lambda Authorizer] + +Use "Lambda Authorizer" when using your own custom authorization logic via `allow.custom()`. Review [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz) to learn more about how to implement your authorization protocol. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const getAuthToken = () => 'myAuthToken'; +const lambdaAuthToken = getAuthToken(); + +const client = generateClient({ + authMode: 'lambda', + authToken: lambdaAuthToken, +}); +``` + + + +### Set authorization mode on the request-level + +You can also specify the authorization mode on each individual API request. This is useful if your application typically only uses one authorization mode with a small number of exceptions. + +#### [API Key] + + +```ts +const { data: todos, errors } = await client.models.Todo.list({ + authMode: 'apiKey', +}); +``` + + + +```kt +val query = ModelQuery.list(Todo::class.java) as AppSyncGraphQLRequest> +val apiKeyQuery = query + .newBuilder() + .authorizationType(AuthorizationType.API_KEY) + .build>() +Amplify.API.query(apiKeyQuery, + { Log.i("MyAmplifyApp", "Queried with API key ${it.data}")}, + { Log.e("MyAmplifyApp", "Error querying with API Key")}) +``` + + + +```swift +let result = try await Amplify.API.query( + request: .list( + Todo.self, + authMode: .apiKey)) +``` + + + +```dart +final apiKeyRequest = ModelQueries.list(Todo.classType, authorizationMode: APIAuthorizationType.apiKey); +final apiKeyResponse = await Amplify.API.query(request: apiKeyRequest).response; +``` + + +#### [Amazon Cognito user pool] + + +```ts +const { data: todos, errors } = await client.models.Todo.list({ + authMode: 'userPool', +}); +``` + + + +```kt +val query = ModelQuery.list(Todo::class.java) as AppSyncGraphQLRequest> +val userPoolQuery = query + .newBuilder() + .authorizationType(AuthorizationType.AMAZON_COGNITO_USER_POOLS) + .build>() +Amplify.API.query(userPoolQuery, + { Log.i("MyAmplifyApp", "Queried with Cognito user pool ${it.data}")}, + { Log.e("MyAmplifyApp", "Error querying with Cognito user pool")}) +``` + + + +```swift +let result = try await Amplify.API.query( + request: .list( + Todo.self, + authMode: .amazonCognitoUserPools)) +``` + + + +```dart +final userPoolRequest = ModelQueries.list(Todo.classType, authorizationMode: APIAuthorizationType.userPools); +final userPoolResponse = await Amplify.API.query(request: userPoolRequest).response; +``` + + +#### [AWS IAM (including Amazon Cognito identity pool roles)] + + +```ts +const { data: todos, errors } = await client.models.Todo.list({ + authMode: 'identityPool', +}); +``` + + + +```kt +val query = ModelQuery.list(Todo::class.java) as AppSyncGraphQLRequest> +val iamQuery = query + .newBuilder() + .authorizationType(AuthorizationType.AWS_IAM) + .build>() +Amplify.API.query(iamQuery, + { Log.i("MyAmplifyApp", "Queried with AWS IAM ${it.data}")}, + { Log.e("MyAmplifyApp", "Error querying with AWS IAM")}) +``` + + + +```swift +let result = try await Amplify.API.query( + request: .list( + Todo.self, + authMode: .awsIAM)) +``` + + + +```dart +final iamRequest = ModelQueries.list(Todo.classType, authorizationMode: APIAuthorizationType.iam); +final iamResponse = await Amplify.API.query(request: iamRequest).response; +``` + + +#### [OpenID Connect (OIDC)] + + +```ts +const { data: todos, errors } = await client.models.Todo.list({ + authMode: 'oidc', +}); +``` + + + +```kt +val query = ModelQuery.list(Todo::class.java) as AppSyncGraphQLRequest> +val oidcQuery = query + .newBuilder() + .authorizationType(AuthorizationType.OPENID_CONNECT) + .build>() +Amplify.API.query(oidcQuery, + { Log.i("MyAmplifyApp", "Queried with OIDC authorization mode ${it.data}")}, + { Log.e("MyAmplifyApp", "Error querying with OIDC authorization mode")}) +``` + + + +```swift +let result = try await Amplify.API.query( + request: .list( + Todo.self, + authMode: .openIDConnect)) +``` + + + +```dart +final oidcRequest = ModelQueries.list(Todo.classType, authorizationMode: APIAuthorizationType.oidc); +final oidcResponse = await Amplify.API.query(request: oidcRequest).response; +``` + + +#### [Lambda Authorizer] + +You can implement your own custom API authorization logic using a AWS Lambda function. Review [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz) to learn more about how to implement your authorization protocol with AWS Lambda. + + +```ts +const getAuthToken = () => 'myAuthToken'; +const lambdaAuthToken = getAuthToken(); + +const { data: todos, errors } = await client.models.Todo.list({ + authMode: 'lambda', + authToken: lambdaAuthToken, +}); +``` + + + +```kt +val query = ModelQuery.list(Todo::class.java) as AppSyncGraphQLRequest> +val lambdaQuery = query + .newBuilder() + .authorizationType(AuthorizationType.AWS_LAMBDA) + .build>() +Amplify.API.query(lambdaQuery, + { Log.i("MyAmplifyApp", "Queried with AWS Lambda authorizer ${it.data}")}, + { Log.e("MyAmplifyApp", "Error querying with AWS Lambda authorizer")}) +``` + + + +```swift +let result = try await Amplify.API.query( + request: .list( + Todo.self, + authMode: .function)) +``` + + + +```dart +final lambdaRequest = ModelQueries.list(Todo.classType, authorizationMode: APIAuthorizationType.function); +final lambdaResponse = await Amplify.API.query(request: lambdaRequest).response; +``` + + +## Set custom request headers + +When working with the Amplify Data endpoint, you may need to set request headers for authorization purposes or to pass additional metadata from your frontend to the backend API. + + +This is done by specifying a `headers` parameter into the configuration. You can define headers either on a per Data client-level or on a per-request level: + +#### [Custom headers per Data client] + +```ts +import type { Schema } from '../amplify/data/resource'; +import { generateClient } from 'aws-amplify/data'; + +const client = generateClient({ + headers: { + 'My-Custom-Header': 'my value', + }, +}); +``` + +#### [Custom headers per request] + +```ts +// same way for all CRUDL: .create, .get, .update, .delete, .list, .observeQuery +const { data: blog, errors } = await client.models.Blog.get( + { id: 'myBlogId' }, + { + headers: { + 'My-Custom-Header': 'my value', + }, + } +); +``` + +The examples above show you how to set static headers but you can also programmatically set headers by specifying an async function for `headers`: + +#### [Custom headers per Data client] + +```ts +import type { Schema } from '../amplify/data/resource'; +import { generateClient } from 'aws-amplify/data'; + +const client = generateClient({ + headers: async (requestOptions) => { + console.log(requestOptions); + /* The request options allow you to customize your headers based on the request options such + as http method, headers, request URI, and query string. These options are typically used + to create a request signature. + { + method: '...', + headers: { }, + uri: '/', + queryString: "" + } + */ + return { + 'My-Custom-Header': 'my value', + }; + }, +}); +``` + +#### [Custom headers per request] + +```ts +// same way for all CRUDL: .create, .get, .update, .delete, .list, .observeQuery +const res = await client.models.Blog.get( + { id: 'myBlogId' }, + { + headers: async (requestOptions) => { + console.log(requestOptions); + /* The request options allow you to customize your headers based on the request options such + as http method, headers, request URI, and query string. These options are typically used + to create a request signature. + { + method: '...', + headers: { }, + uri: '/', + queryString: "" + } + */ + return { + 'My-Custom-Header': 'my value', + }; + }, + } +); +``` + + + + +To specify your own headers, use the `configureClient()` configuration option on the `AWSApiPlugin`'s builder. Specify the name of one of the configured APIs in your **amplify_outputs.json**. Apply customizations to the underlying OkHttp instance by providing a lambda expression as below. + +#### [Java] + +```java +AWSApiPlugin plugin = AWSApiPlugin.builder() + .configureClient(AWSApiPlugin.DEFAULT_GRAPHQL_API, okHttpBuilder -> { + okHttpBuilder.addInterceptor(chain -> { + Request originalRequest = chain.request(); + Request updatedRequest = originalRequest.newBuilder() + .addHeader("customHeader", "someValue") + .build(); + return chain.proceed(updatedRequest); + }); + }) + .build(); +Amplify.addPlugin(plugin); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val plugin = AWSApiPlugin.builder() + .configureClient(AWSApiPlugin.DEFAULT_GRAPHQL_API) { okHttpBuilder -> + okHttpBuilder.addInterceptor { chain -> + val originalRequest = chain.request() + val updatedRequest = originalRequest.newBuilder() + .addHeader("customHeader", "someValue") + .build() + chain.proceed(updatedRequest) + } + } + .build() +Amplify.addPlugin(plugin) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val plugin = AWSApiPlugin.builder() + .configureClient(AWSApiPlugin.DEFAULT_GRAPHQL_API) { okHttpBuilder -> + okHttpBuilder.addInterceptor { chain -> + val originalRequest = chain.request() + val updatedRequest = originalRequest.newBuilder() + .addHeader("customHeader", "someValue") + .build() + chain.proceed(updatedRequest) + } + } + .build() +Amplify.addPlugin(plugin) +``` + +#### [RxJava] + +```java +AWSApiPlugin plugin = AWSApiPlugin.builder() + .configureClient(AWSApiPlugin.DEFAULT_GRAPHQL_API, okHttpBuilder -> { + okHttpBuilder.addInterceptor(chain -> { + Request originalRequest = chain.request(); + Request updatedRequest = originalRequest.newBuilder() + .addHeader("customHeader", "someValue") + .build(); + return chain.proceed(updatedRequest); + }); + }) + .build(); +RxAmplify.addPlugin(plugin); +``` + + + + +To include custom headers in your outgoing requests, add an `URLRequestInterceptor` to the `AWSAPIPlugin`. + +```swift +import Amplify +import AWSAPIPlugin + +struct CustomInterceptor: URLRequestInterceptor { + func intercept(_ request: URLRequest) throws -> URLRequest { + var request = request + request.setValue("headerValue", forHTTPHeaderField: "headerKey") + return request + } +} +let apiPlugin = AWSAPIPlugin(modelRegistration: AmplifyModels()) +try apiPlugin.add(interceptor: CustomInterceptor(), for: AWSAPIPlugin.defaultGraphQLAPI) +try Amplify.add(plugin: apiPlugin) +try Amplify.configure(with: .amplifyOutputs) + +``` + + + +The simplest option for GraphQL requests is to use the `headers` property of a `GraphQLRequest`. + +```dart +Future queryWithCustomHeaders() async { + final operation = Amplify.API.query( + request: GraphQLRequest( + document: graphQLDocumentString, + headers: {'customHeader': 'someValue'}, + ), + ); + final response = await operation.response; + final data = response.data; + safePrint('data: $data'); +} +``` + +Another option is to use the `baseHttpClient` property of the API plugin which can customize headers or otherwise alter HTTP functionality for all HTTP calls. + +```dart +// First create a custom HTTP client implementation to extend HTTP functionality. +class MyHttpRequestInterceptor extends AWSBaseHttpClient { + @override + Future transformRequest( + AWSBaseHttpRequest request, + ) async { + request.headers.putIfAbsent('customHeader', () => 'someValue'); + return request; + } +} + +// Then you can pass an instance of this client to `baseHttpClient` when you configure Amplify. +await Amplify.addPlugins([ + AmplifyAPI(baseHttpClient: MyHttpRequestInterceptor()), +]); +``` + + + +## Use an additional Data endpoint + +If you have an additional Data endpoint that you're managing with a different Amplify project or through other means, this section will show you how to utilize that endpoint in your frontend code. + +This is done by specifying the `endpoint` parameter on the `generateClient` function. + +```ts +import { generateClient } from 'aws-amplify/data'; + +const client = generateClient({ + endpoint: 'https://my-other-endpoint.com/graphql', +}); +``` + +If this Data endpoint shares its authorization configuration (for example, both endpoints share the same user pool and/or identity pool as the one in your `amplify_outputs.json` file), you can specify the `authMode` parameter on `generateClient`. + +```ts +const client = generateClient({ + endpoint: 'https://my-other-endpoint.com/graphql', + authMode: 'userPool', +}); +``` + +If the endpoint uses API Key authorization, you can pass in the `apiKey` parameter on `generateClient`. + +```ts +const client = generateClient({ + endpoint: 'https://my-other-endpoint.com/graphql', + authMode: 'apiKey', + apiKey: 'my-api-key', +}); +``` + +If the endpoint uses a different authorization configuration, you can manually pass in the authorization header using the instructions in the [Set custom request headers](#set-custom-request-headers) section. + + +--- + +--- +title: "Create, update, and delete application data" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-10-15T16:14:40.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/mutate-data/" +--- + +In this guide, you will learn how to create, update, and delete your data using Amplify Libraries' Data client. + +Before you begin, you will need: + +- An [application connected to the API](/[platform]/build-a-backend/data/connect-to-API/) + +## Create an item + + +You can create an item by first generating the Data client with your backend Data schema. Then you can add an item: + +```ts +import { generateClient } from 'aws-amplify/data'; +import { type Schema } from '../amplify/data/resource' + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create({ + content: "My new todo", + isDone: true, +}) +``` + + + +**Note:** You do not need to specify `createdAt` or `updatedAt` fields because Amplify automatically populates these fields for you. + + + + + +You can run a GraphQL mutation with `Amplify.API.mutate` to create an item. + +#### [Async/Await] + +Make sure you have the following imports at the top of your file: + +```swift +import Amplify +``` + +```swift +func createTodo() async { + // Retrieve your Todo using Amplify.API.query + var todo = Todo(name: "my first todo", description: "todo description") + todo.description = "created description" + do { + let result = try await Amplify.API.mutate(request: .create(todo)) + switch result { + case .success(let todo): + print("Successfully created todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } catch let error as APIError { + print("Failed to create todo: ", error) + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +You can run a GraphQL mutation with `Amplify.API.mutate`. Make sure you have the following imports at the top of your file: + +```swift +import Amplify +import Combine +``` + +```swift +func createTodo() -> AnyCancellable { + // Retrieve your Todo using Amplify.API.query + var todo = Todo(name: "my first todo", description: "todo description") + todo.description = "created description" + let todoCreated = todo + let sink = Amplify.Publisher.create { + try await Amplify.API.mutate(request: .create(todoCreated)) + } + .sink { + if case let .failure(error) = $0 { + print("Got failed event with error \(error)") + } + } + receiveValue: { result in + switch result { + case .success(let todo): + print("Successfully created todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } + return sink +} +``` + + + + +Now that the client is set up, you can run a GraphQL mutation with `Amplify.API.mutate` to create your data. + +#### [Java] + +```java +Todo todo = Todo.builder() + .name("My todo") + .build(); + +Amplify.API.mutate(ModelMutation.create(todo), + response -> Log.i("MyAmplifyApp", "Todo with id: " + response.getData().getId()), + error -> Log.e("MyAmplifyApp", "Create failed", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val todo = Todo.builder() + .name("My todo") + .build() + +Amplify.API.mutate(ModelMutation.create(todo), + { Log.i("MyAmplifyApp", "Todo with id: ${it.data.id}") } + { Log.e("MyAmplifyApp", "Create failed", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val todo = Todo.builder() + .name("My todo") + .build() +try { + val response = Amplify.API.mutate(ModelMutation.create(todo)) + Log.i("MyAmplifyApp", "Todo with id: ${response.data.id}") +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "Create failed", error) +} +``` + +#### [RxJava] + +```java +Todo todo = Todo.builder() + .name("My todo") + .build(); + +RxAmplify.API.mutate(ModelMutation.create(todo)) + .subscribe( + response -> Log.i("MyAmplifyApp", "Todo with id: " + response.getData().getId()), + error -> Log.e("MyAmplifyApp", "Create failed", error) + ); +``` + + + + +You can run a GraphQL mutation with `Amplify.API.mutate` to create your data. + +```dart +Future createTodo() async { + try { + final todo = Todo(name: 'my first todo', description: 'todo description'); + final request = ModelMutations.create(todo); + final response = await Amplify.API.mutate(request: request).response; + + final createdTodo = response.data; + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + } on ApiException catch (e) { + safePrint('Mutation failed: $e'); + } +} +``` + + +## Update an item + + +To update the item, use the `update` function: + +```ts +import { generateClient } from 'aws-amplify/data'; +import { type Schema } from '../amplify/data/resource'; + +const client = generateClient(); + +const todo = { + id: 'some_id', + content: 'Updated content', +}; + +const { data: updatedTodo, errors } = await client.models.Todo.update(todo); +``` + + + +**Notes:** + +- You do not need to specify the `updatedAt` field. Amplify will automatically populate this field for you. +- If you specify _extra_ input fields not expected by the API, this query will fail. You can see this in the `errors` field returned by the query. With Amplify Data, errors are not thrown like exceptions. Instead, any errors are captured and returned as part of the query result in the `errors` field. + + + + + +To update data, replace the request with `.update`. + +```swift +try await Amplify.API.mutate(request: .update(todo)) +``` + + + +To update data, use `ModelMutation.update(todo)` instead. + + + +To update the `Todo` with a new name: + +```dart +Future updateTodo(Todo originalTodo) async { + final todoWithNewName = originalTodo.copyWith(name: 'new name'); + + final request = ModelMutations.update(todoWithNewName); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Response: $response'); +} +``` + + +## Delete an item + + +You can then delete the Todo by using the delete mutation. To specify which item to delete, you only need to provide the `id` of that item: + +```js +import { generateClient } from 'aws-amplify/data'; +import { type Schema } from '../amplify/data/resource' + +const client = generateClient(); + +const toBeDeletedTodo = { + id: '123123213' +} + +const { data: deletedTodo, errors } = await client.models.Todo.delete(toBeDeletedTodo) +``` + + + +**Note:** When deleting items in many-to-many relationships, the join table records must be deleted before deleting the associated records. For example, for a many-to-many relationship between Posts and Tags, delete the PostTags join record before deleting a Post or Tag. Review [Many-to-many relationships](/[platform]/build-a-backend/data/data-modeling/relationships/) for more details. + + + + + +Each API request uses an authorization mode. If you get unauthorized errors, you may need to update your authorization mode. To override the default authorization mode defined in your **amplify/data/resource.ts** file, pass an `authMode` property to the request or the client. The following examples show how you can mutate data with a custom authorization mode: + +```ts +import { generateClient } from 'aws-amplify/data'; +import { type Schema } from '../amplify/data/resource'; + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + isDone: true, + }, + { + authMode: 'apiKey', + } +); +``` + + + + + +To delete data, replace the request with `.delete`. + +```swift +try await Amplify.API.mutate(request: .delete(todo)) +``` + + + +To delete data, use `ModelMutation.delete(todo)`. + + + +To delete the `Todo`: + +```dart +Future deleteTodo(Todo todoToDelete) async { + final request = ModelMutations.delete(todoToDelete); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Response: $response'); +} +``` + +Or you can delete by ID, which is ideal if you do not have the instance in memory yet: + +```dart +Future deleteTodoById(Todo todoToDelete) async { + final request = ModelMutations.deleteById( + Todo.classType, + TodoModelIdentifier(id: '8e0dd2fc-2f4a-4dc4-b47f-2052eda10775'), + ); + final response = await Amplify.API.mutate(request: request).response; + safePrint('Response: $response'); +} +``` + + + +## Cancel create, update, and delete requests + +You can cancel any mutation API request by calling `.cancel` on the mutation request promise that's returned by `.create(...)`, `.update(...)`, or `.delete(...)`. + +```ts +const promise = client.models.Todo.create({ content: 'New Todo' }); +// ^ Note: we're not awaiting the request, we're returning the promise + +try { + await promise; +} catch (error) { + console.log(error); + // If the error is because the request was cancelled you can confirm here. + if (client.isCancelError(error)) { + console.log(error.message); // "my message for cancellation" + // handle user cancellation logic + } +} + +//... + +// To cancel the above request +client.cancel(promise, 'my message for cancellation'); +``` + +You need to ensure that the promise returned from `.create()`, `.update()`, and `.delete()` has not been modified. Typically, async functions wrap the promise being returned into another promise. For example, the following will **not** work: + +```ts +async function makeAPICall() { + return client.models.Todo.create({ content: 'New Todo' }); +} +const promise = makeAPICall(); + +// The following will NOT cancel the request. +client.cancel(promise, 'my error message'); +``` + + +## Conclusion + +Congratulations! You have finished the **Create, update, and delete application data** guide. In this guide, you created, updated, and deleted your app data. + +### Next steps + +Our recommended next steps include using the API to query data and subscribe to real-time events to look for mutations in your data. Some resources that will help with this work include: + +- [Read application data](/[platform]/build-a-backend/data/query-data/) +- [Subscribe to real-time events](/[platform]/build-a-backend/data/subscribe-data/) + +--- + +--- +title: "Read application data" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-07-23T09:15:04.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/query-data/" +--- + + +You can read application data using the Amplify Data client. In this guide, we will review the difference between reading data and getting data, how to filter query results to get just the data you need, and how to paginate results to make your data more manageable. We will also show you how to cancel these requests when needed. + +Before you begin, you will need: + +- An [application connected to the API](/[platform]/build-a-backend/data/connect-to-API/) +- Data already created to view + +## List and get your data + +Queries are used to read data through the API and include the `list` and `get` operations. Amplify Data automatically creates `list` and `get` queries for any `a.model()` type in your schema. The `list` query retrieves multiple items, such as Todo items, without needing to specific an identifier for a particular record. This is best suited for getting an overview or summary of items, or for enhancing the `list` operation to filter the items by specific criteria. When you want to query a single entry by an identifier, you would use `get` to retrieve a specific Todo item. + + + +**Note:** The cost structure of your underlying data source can impact the cost to run some queries. For example, the `list` operation uses Amazon DynamoDB "scan operations," which can use more read request units than the `get` operation. You will want to review the associated costs for these operations for your data source. In our example, we are using DynamoDB. You can learn more about how DynamoDB costs are calculated by visiting [Amazon DynamoDB pricing](https://aws.amazon.com/dynamodb/pricing/). + + + +You can list items by first generating the Data client with your backend Data schema. Then you can list items of your desired model: + +```ts +import { generateClient } from 'aws-amplify/data'; +import { type Schema } from '@/amplify/data/resource'; + +const client = generateClient(); + +// list items +const { data: todos, errors } = await client.models.Todo.list(); + +// get a specific item +const { data: todo, errors } = await client.models.Todo.get({ + id: '...', +}); +``` + + + +Each API request uses an authorization mode. If you get unauthorized errors, you may need to update your authorization mode. To override the default authorization mode defined in your **amplify/data/resource.ts** file, pass an `authMode` property to the request or the client. The following examples show how you can mutate data with a custom authorization mode: + +```ts +import { generateClient } from 'aws-amplify/data'; +import { type Schema } from '@/amplify/data/resource'; + +const client = generateClient(); + +const { errors, data: todos } = await client.models.Todo.list({ + authMode: 'apiKey', +}); +``` + + + +## Filter list queries + +As your data grows, you will need to paginate your list queries. Fortunately, this is already built in to Amplify Data. + +```ts +import { generateClient } from 'aws-amplify/data'; +import { type Schema } from '@/amplify/data/resource'; + +const client = generateClient(); + +const { data: todos, errors } = await client.models.Todo.list({ + filter: { + content: { + beginsWith: 'hello' + } + } +}); +``` + +### Compound filters + +You can combine filters with `and`, `or`, and `not` Boolean logic. Observe that `filter` is recursive in respect to those fields. So if, for example, you wanted to filter for `priority` values of 1 _or_ 2, you would do this: + +```ts +import { generateClient } from 'aws-amplify/data'; +import { type Schema } from '@/amplify/data/resource'; + +const client = generateClient(); + +const { data: todos, errors } = await client.models.Todo.list({ + filter: { + or: [ + { + priority: { eq: '1' } + }, + { + priority: { eq: '2' } + } + ] + } +}); +``` + +Note that querying for `priority` of 1 and 2 would return no results, because this is Boolean logic instead of natural language. + +## Paginate list queries + +To paginate your list query results, make a subsequent list query request with the `nextToken` and `limit` input variable set. The `limit` variable limits how many results are returned. The response will include a `nextToken` you can use to request the next page of data. A `nextToken` is a very long string that represents the cursor to the starting item of the next query made with these filters. + +```ts +import { generateClient } from 'aws-amplify/data'; +import { type Schema } from '@/amplify/data/resource'; + +const client = generateClient(); + +const { + data: todos, + nextToken, // Repeat this API call with the nextToken until the returned nextToken is `null` + errors +} = await client.models.Todo.list({ + limit: 100, // default value is 100 + nextToken: 'eyJ2ZXJzaW9uejE1a2...' // previous nextToken +}); +``` + + +If you're building a React application, you can use the `usePagination` hook in Amplify UI to help with managing the pagination user experience. + +```js +import * as React from 'react'; +import { Pagination } from '@aws-amplify/ui-react'; + +export const PaginationHasMorePagesExample = () => { + const [pageTokens, setPageTokens] = React.useState([null]); + const [currentPageIndex, setCurrentPageIndex] = React.useState(1); + const [hasMorePages, setHasMorePages] = React.useState(true); + + const handleNextPage = async () => { + if (hasMorePages && currentPageIndex === pageTokens.length) { + const { data: todos, nextToken } = await client.models.Todo.list({ + nextToken: pageTokens[pageTokens.length - 1] + }); + + if (!nextToken) { + setHasMorePages(false); + } + + setPageTokens([...pageTokens, nextToken]); + } + + setCurrentPageIndex(currentPageIndex + 1); + }; + + return ( + setCurrentPageIndex(currentPageIndex - 1)} + onChange={(pageIndex) => setCurrentPageIndex(pageIndex)} + /> + ); +}; +``` + + + + +**Limitations:** + +- There is no API to get a total page count at this time. Note that scanning all items is a [potentially expensive operation](https://github.com/aws-amplify/amplify-js/issues/2901). +- You [cannot query by `page` number](https://github.com/aws-amplify/amplify-cli/issues/5086); you have to query by `nextToken`. + + + +## Fetch only the data you need with custom selection set + +A business domain model may contain many models with numerous fields. However, apps typically only need subsets of the data or fields to meet the requirements of different components or screens. It is necessary to have a mechanism to retrieve subsets of models and their relationships. This mechanism would help optimize data usage for screens and components by only transferring needed data. Having this capability would improve the app's data efficiency, latency, and the end user's perceived performance. + +A **custom selection set** allows consumers to specify, on a per-call basis, the fields the consumer wants to retrieve; this is possible for all operations that return data (CRUDL + `observeQuery`). The desired fields are specified in a strongly typed way (discoverable through IntelliSense) with a "dot notation". + +```ts +// same way for all CRUDL: .create, .get, .update, .delete, .list, .observeQuery +const { data: blogWithSubsetOfData, errors } = await client.models.Blog.get( + { id: blog.id }, + { + selectionSet: ['author.email', 'posts.*'], + } +); +``` + +## TypeScript type helpers for Amplify Data + +When using TypeScript, you frequently need to specify data model types for type generics. + + +For instance, with React's `useState`, you provide a type in TypeScript to ensure type-safety in your component code using the state. Use the `Schema["MODEL_NAME"]["type"]` pattern to get TypeScript types for the shapes of data models returned from the backend API. + +```ts +import { type Schema } from '@/amplify/data/resource'; + +type Post = Schema['Post']['type']; + +const [posts, setPosts] = useState([]); +``` + + + +```ts +import { type Schema } from '../../../amplify/data/resource'; + +type Post = Schema['Post']['type']; +``` + + +You can combine the `Schema["MODEL_NAME"]["type"]` type with the `SelectionSet` helper type to describe the return type of API requests using the `selectionSet` parameter: + + +```ts +import type { SelectionSet } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; + +const selectionSet = ['content', 'blog.author.*', 'comments.*'] as const; +type PostWithComments = SelectionSet; + +// ... +const [posts, setPosts] = useState([]); + +const fetchPosts = async () => { + const { data: postsWithComments } = await client.models.Post.list({ + selectionSet, + }); + setPosts(postsWithComments); +} +``` + + + +```ts + + + +``` + + + +```ts +import type { Schema } from '../../../amplify/data/resource'; +import { Component, OnInit } from '@angular/core'; +import { generateClient, type SelectionSet } from 'aws-amplify/data'; +import { CommonModule } from '@angular/common'; + +const client = generateClient(); + +const selectionSet = ['content', 'blog.author.*', 'comments.*'] as const; + +type PostWithComments = SelectionSet< + Schema['Post']['type'], + typeof selectionSet +>; + +@Component({ + selector: 'app-todos', + standalone: true, + imports: [CommonModule], + templateUrl: './todos.component.html', + styleUrls: ['./todos.component.css'], +}) +export class TodosComponent implements OnInit { + posts: PostWithComments[] = []; + + constructor() {} + + ngOnInit(): void { + this.fetchPosts(); + } + + async fetchPosts(): Promise { + const { data: postsWithComments } = await client.models.Post.list({ + selectionSet, + }); + this.posts = postsWithComments; + } +} +``` + + +## Cancel read requests + +You can cancel any query API request by calling `.cancel` on the query request promise that's returned by `.list(...)` or `.get(...)`. + +```javascript +const promise = client.models.Todo.list(); +// ^ Note: we're not awaiting the request, we're returning the promise + +try { + await promise; +} catch (error) { + console.log(error); + // If the error is because the request was cancelled you can confirm here. + if (client.isCancelError(error)) { + console.log(error.message); // "my message for cancellation" + // handle user cancellation logic + } +} +... + +// To cancel the above request +client.cancel(promise, "my message for cancellation"); +``` + +You need to ensure that the promise returned from `.list()` or `.get()` has not been modified. Typically, async functions wrap the promise being returned into another promise. For example, the following will **not** work: + +```javascript +async function makeAPICall() { + return client.models.Todo.list(); +} +const promise = makeAPICall(); + +// The following will NOT cancel the request. +client.cancel(promise, 'my error message'); +``` + +## Conclusion + +Congratulations! You have finished the **Read application data** guide. In this guide, you learned how to read your data through `get` and `list` queries. + +### Next steps + +Our recommended next steps include subscribing to real-time events to look for mutations in your data and continuing to build out and customize your information architecture for your data. Some resources that will help with this work include: + +- [Subscribe to real-time events](/[platform]/build-a-backend/data/subscribe-data/) +- [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz/) +- [Customize your data model](/[platform]/build-a-backend/data/data-modeling/) +- [Add custom business logic](/[platform]/build-a-backend/data/custom-business-logic/) + + + +## Query by Id + +Now that you were able to make a mutation, take the `Id` that was printed out and use it in your query to retrieve data. + +#### [Async/Await] + +```swift +func getTodo() async { + do { + let result = try await Amplify.API.query( + request: .get(Todo.self, byId: "9FCF5DD5-1D65-4A82-BE76-42CB438607A0") + ) + + switch result { + case .success(let todo): + guard let todo = todo else { + print("Could not find todo") + return + } + print("Successfully retrieved todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } catch let error as APIError { + print("Failed to query todo: ", error) + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func getTodo() -> AnyCancellable { + let sink = Amplify.Publisher.create { + try await Amplify.API.query( + request: .get(Todo.self, byId: "9FCF5DD5-1D65-4A82-BE76-42CB438607A0") + ) + } + .sink { + if case let .failure(error) = $0 { + print("Got failed event with error \(error)") + } + } + receiveValue: { result in + switch result { + case .success(let todo): + guard let todo = todo else { + print("Could not find todo") + return + } + print("Successfully retrieved todo: \(todo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } + return sink +} +``` + +## List Query + +You can get the list of items using `.list` with optional parameters `limit` and `where` to specify the page size and condition. By default, the page size is 1000. + +#### [Async/Await] + +```swift +func listTodos() async { + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) + do { + let result = try await Amplify.API.query(request: request) + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } catch let error as APIError { + print("Failed to query list of todos: ", error) + } catch { + print("Unexpected error: \(error)") + } +} +``` + +#### [Combine] + +```swift +func listTodos() -> AnyCancellable { + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) + let sink = Amplify.Publisher.create { + try await Amplify.API.query(request: request) + } + .sink { + if case let .failure(error) = $0 { + print("Got failed event with error \(error)") + } + } + receiveValue: { result in + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } + return sink +} +``` + +### List subsequent pages of items + +If you are using SwiftUI and have SwiftUI imported in the same code file, you will need to import the class `Amplify.List` to resolve name collision with `SwiftUI.List`: + +```swift +import SwiftUI +import Amplify +import class Amplify.List +``` + +For large data sets, you'll need to paginate through the results. After receiving the first page of results, you can check if there is a subsequent page and obtain the next page. + +```swift +var todos: [Todo] = [] +var currentPage: List? + +func listFirstPage() async { + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) + do { + let result = try await Amplify.API.query(request: request) + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + self.currentPage = todos + self.todos.append(contentsOf: todos) + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } catch let error as APIError { + print("Failed to query list of todos: ", error) + } catch { + print("Unexpected error: \(error)") + } +} + +func listNextPage() async { + if let current = self.currentPage, current.hasNextPage() { + do { + let todos = try await current.getNextPage() + self.todos.append(contentsOf: todos) + self.currentPage = todos + } catch { + print("Failed to get next page \(error)") + } + } +} +``` + +## List all pages + +If you want to get all pages, retrieve the subsequent page when you have successfully retrieved the first or next page. + +1. Update the above method `listFirstPage()` to `listAllPages()` +2. Call `listNextPageRecursively()` in the success block of the query in `listAllPages()` +2. Update the `listNextPage()` to `listNextPageRecursively()` +3. Call `listNextPageRecursively()` in the success block of the query in `listNextPageRecursively()` + +The completed changes should look like this: + +```swift +var todos: [Todo] = [] +var currentPage: List? + +func listAllPages() async { // 1. Updated from `listFirstPage()` + let todo = Todo.keys + let predicate = todo.name == "my first todo" && todo.description == "todo description" + let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) + do { + let result = try await Amplify.API.query(request: request) + switch result { + case .success(let todos): + print("Successfully retrieved list of todos: \(todos)") + self.currentPage = todos + self.todos.append(contentsOf: todos) + await self.listNextPageRecursively() // 2. Added + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } catch let error as APIError { + print("Failed to query list of todos: ", error) + } catch { + print("Unexpected error: \(error)") + } +} + +func listNextPageRecursively() async { // 3. Updated from `listNextPage()` + if let current = currentPage, current.hasNextPage() { + do { + let todos = try await current.getNextPage() + self.todos.append(contentsOf: todos) + self.currentPage = todos + await self.listNextPageRecursively() // 4. Added + } catch { + print("Failed to get next page \(error)") + } + } +} +``` + + + +## Query item + +Now that you were able to make a mutation, take the `Id` that was printed out and use it in your query to retrieve data. + +#### [Java] + +```java +private void getTodo(String id) { + Amplify.API.query( + ModelQuery.get(Todo.class, id), + response -> Log.i("MyAmplifyApp", ((Todo) response.getData()).getName()), + error -> Log.e("MyAmplifyApp", error.toString(), error) + ); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +private fun getTodo(id: String) { + Amplify.API.query(ModelQuery.get(Todo::class.java, id), + { Log.i("MyAmplifyApp", "Query results = ${(it.data as Todo).name}") }, + { Log.e("MyAmplifyApp", "Query failed", it) } + ); +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +suspend fun getTodo(id: String) { + try { + val response = Amplify.API.query(ModelQuery.get(Todo::class.java, id)) + Log.i("MyAmplifyApp", response.data.name) + } catch (error: ApiException) { + Log.e("MyAmplifyApp", "Query failed", error) + } +} +``` + +#### [RxJava] + +```java +private void getTodo(String id) { + RxAmplify.API.query(ModelQuery.get(Todo.class, id)) + .subscribe( + response -> Log.i("MyAmplifyApp", ((Todo) response.getData()).getName()), + error -> Log.e("MyAmplifyApp", error.toString(), error) + ); +} +``` + +## List items + +You can get the list of items that match a condition that you specify in `Amplify.API.query`: + +#### [Java] + +```java +Amplify.API.query( + ModelQuery.list(Todo.class, Todo.NAME.contains("first")), + response -> { + for (Todo todo : response.getData()) { + Log.i("MyAmplifyApp", todo.getName()); + } + }, + error -> Log.e("MyAmplifyApp", "Query failure", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.API.query( + ModelQuery.list(Todo::class.java, Todo.NAME.contains("first")), + { response -> + response.data.forEach { todo -> + Log.i("MyAmplifyApp", todo.name) + } + }, + { Log.e("MyAmplifyApp", "Query failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + Amplify.API + .query(ModelQuery.list(Todo::class.java, Todo.NAME.contains("first"))) + .response.data + .items.forEach { todo -> Log.i("MyAmplifyApp", todo.name) } +} catch (error: ApiException) { + Log.e("MyAmplifyApp", "Query failure", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.API.query(ModelQuery.list(Todo.class, Todo.NAME.contains("first")) + .subscribe( + response -> { + for (Todo todo : response.getData()) { + Log.i("MyAmplifyApp", todo.getName()); + } + }, + error -> Log.e("MyAmplifyApp", "Query failure", error) + )); +``` + +> **Note**: This approach will only return up to the first 1,000 items. To change this limit or make requests for additional results beyond this limit, use *pagination* as discussed below. + +## List subsequent pages of items + +A list query only returns the first 1,000 items by default, so for large data sets, you'll need to paginate through the results. After receiving a page of results, you can obtain a `GraphQLRequest` for requesting the next page, if there are more results available. The page size is configurable as well, as in the example below. + +#### [Java] + +```java +public void queryFirstPage() { + query(ModelQuery.list(Todo.class, ModelPagination.limit(1_000))); +} + +private static void query(GraphQLRequest> request) { + Amplify.API.query( + request, + response -> { + if (response.hasData()) { + for (Todo todo : response.getData()) { + Log.d("MyAmplifyApp", todo.getName()); + } + if (response.getData().hasNextResult()) { + query(response.getData().getRequestForNextResult()); + } + } + }, + failure -> Log.e("MyAmplifyApp", "Query failed.", failure) + ); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +fun queryFirstPage() { + query(ModelQuery.list(Todo::class.java, ModelPagination.limit(1_000))) +} + +fun query(request: GraphQLRequest>) { + Amplify.API.query(request, + { response -> + if (response.hasData()) { + response.data.items.forEach { todo -> + Log.d("MyAmplifyApp", todo.name) + } + if (response.data.hasNextResult()) { + query(response.data.requestForNextResult) + } + } + }, + { Log.e("MyAmplifyApp", "Query failed", it) } + ) +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +suspend fun queryFirstPage() { + query(ModelQuery.list(Todo::class.java, + ModelPagination.firstPage().withLimit(1_000))) +} + +suspend fun query(request: GraphQLRequest>) { + try { + val response = Amplify.API.query(request) + response.data.items.forEach { todo -> + Log.d("MyAmplifyApp", todo.name) + } + if (response.data.hasNextResult()) { + query(response.data.requestForNextResult) + } + } catch (error: ApiException) { + Log.e("MyAmplifyApp", "Query failed.", error) + } +} +``` + +#### [RxJava] + +```java +BehaviorSubject>> subject = + BehaviorSubject.createDefault(ModelQuery.list(Todo.class, ModelPagination.limit(1_000))); +subject.concatMap(request -> RxAmplify.API.query(request).toObservable()) + .doOnNext(response -> { + if (response.hasErrors()) { + subject.onError(new Exception(String.format("Query failed: %s", response.getErrors()))); + } else if (!response.hasData()) { + subject.onError(new Exception("Empty response from AppSync.")); + } else if(response.getData().hasNextResult()) { + subject.onNext(response.getData().getRequestForNextResult()); + } else { + subject.onComplete(); + } + }) + .concatMapIterable(GraphQLResponse::getData) + .subscribe( + todo -> Log.d(TAG, "Todo: " + todo), + error -> Log.e(TAG, "Error: " + error) + ); +``` + + + + +## Query item + +Now that you were able to make a mutation, take the `id` from the created `Todo` instance and use it to retrieve data. + +```dart +Future queryItem(Todo queriedTodo) async { + try { + final request = ModelQueries.get( + Todo.classType, + queriedTodo.modelIdentifier, + ); + final response = await Amplify.API.query(request: request).response; + final todo = response.data; + if (todo == null) { + safePrint('errors: ${response.errors}'); + } + return todo; + } on ApiException catch (e) { + safePrint('Query failed: $e'); + return null; + } +} +``` + +## List items + +You can get the list of items in `Amplify.API.query`: + +```dart +Future> queryListItems() async { + try { + final request = ModelQueries.list(Todo.classType); + final response = await Amplify.API.query(request: request).response; + + final todos = response.data?.items; + if (todos == null) { + safePrint('errors: ${response.errors}'); + return const []; + } + return todos; + } on ApiException catch (e) { + safePrint('Query failed: $e'); + return const []; + } +} +``` + +### List subsequent pages of items + +For large data sets, you'll need to paginate through the results. After receiving the first page of results, you can check if there is a subsequent page and obtain the next page. + +```dart +const limit = 100; + +Future> queryPaginatedListItems() async { + final firstRequest = ModelQueries.list(Todo.classType, limit: limit); + final firstResult = await Amplify.API.query(request: firstRequest).response; + final firstPageData = firstResult.data; + + // Indicates there are > 100 todos and you can get the request for the next set. + if (firstPageData?.hasNextResult ?? false) { + final secondRequest = firstPageData!.requestForNextResult; + final secondResult = + await Amplify.API.query(request: secondRequest!).response; + return secondResult.data?.items ?? []; + } else { + return firstPageData?.items ?? []; + } +} +``` + +## Query Predicates + +Models also support the use of query predicates for comparison. These are accessible from the Model's attributes, for example `Blog["attribute"]["operator"]`. + +Supported operators: +- `eq` - equal +- `ne` - not equal +- `gt` - greater than +- `ge` - greater than or equal +- `lt` - less than +- `le` - less than or equal +- `beginsWith` - Matches models where the given field begins with the provided value. +- `between` - Matches models where the given field is between the provided start and end values. +- `contains` - Matches models where the given field contains the provided value. + +### Basic Equality Operator + +Query for equality on a model's attribute. + +```dart +const blogTitle = 'Test Blog 1'; +final queryPredicate = Blog.NAME.eq(blogTitle); + +final request = ModelQueries.list( + Blog.classType, + where: queryPredicate, +); +final response = await Amplify.API.query(request: request).response; +final blogFromResponse = response.data?.items.first; +``` + +### Fetch by Parent ID + +Get all Posts by parent ID + +```dart +final blogId = blog.id; + +final request = ModelQueries.list( + Post.classType, + where: Post.BLOG.eq(blogId), +); +final response = await Amplify.API.query(request: request).response; +final data = response.data?.items ?? []; +``` + +### Less than + +Return Posts with a rating less than 5. + +```dart +const rating = 5; + +final request = ModelQueries.list( + Post.classType, + where: Post.RATING.lt(rating), +); +final response = await Amplify.API.query(request: request).response; + +final data = response.data?.items ?? []; +``` + + +--- + +--- +title: "Subscribe to real-time events" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-11-13T19:00:27.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/subscribe-data/" +--- + + +In this guide, we will outline the benefits of enabling real-time data integrations and how to set up and filter these subscriptions. We will also cover how to unsubscribe from subscriptions. + +Before you begin, you will need: + +- An [application connected to the API](/[platform]/build-a-backend/data/connect-to-API/) +- Data already created to modify + + + +## Set up a real-time list query + +The recommended way to fetch a list of data is to use `observeQuery` to get a real-time list of your app data at all times. You can integrate `observeQuery` with React's `useState` and `useEffect` hooks in the following way: + +```ts +import { useState, useEffect } from 'react'; +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; + +type Todo = Schema['Todo']['type']; + +const client = generateClient(); + +export default function MyComponent() { + const [todos, setTodos] = useState([]); + + useEffect(() => { + const sub = client.models.Todo.observeQuery().subscribe({ + next: ({ items, isSynced }) => { + setTodos([...items]); + }, + }); + return () => sub.unsubscribe(); + }, []); + + return ( +
      + {todos.map((todo) => ( +
    • {todo.content}
    • + ))} +
    + ); +} +``` + +`observeQuery` fetches and paginates through all of your available data in the cloud. While data is syncing from the cloud, snapshots will contain all of the items synced so far and an `isSynced` status of `false`. When the sync process is complete, a snapshot will be emitted with all the records in the local store and an `isSynced` status of `true`. + + + +If you don't see all of the real-time events and model fields you expect to see, here are a few things to look for. + +#### Authorization + +The model's [authorization rules](/[platform]/build-a-backend/data/customize-authz/) must grant the appropriate rights to the user. + +| Operation | Authorization | +| -- | -- | +| `onCreate` | `read` OR `listen` | +| `onUpdate` | `read` OR `listen` | +| `onDelete` | `read` OR `listen` | +| `observeQuery` | `read` OR (`listen` AND `list`) | + +If the authorization rules are correct, also ensure the session is authenticated as expected. + +#### Selection Set Parity + +All of the fields you expect to see in a real-time update must be present in the selection set of the **mutation** that triggers it. A mutation essentially "provides" the fields via its selection set that the corresponding subscription can then select from. + +One way to address this is to use a common selection set variable for both operations. For example: + +```ts +// Defining your selection set `as const` ensures the types +// propagate through to the response objects. +const selectionSet = ['title', 'author', 'posts.*'] as const; + +const sub = client.models.Blog.observeQuery( + filter: { id: { eq: 'blog-id' } }, + selectionSet: [...selectionSet] +).subscribe({ + next(data) { + handle(data.items) + } +}); + +// The update uses the same selection set, ensuring all the +// required fields are provided to the subscriber. +const { data } = await client.models.Blog.update({ + id: 'blog-id', + name: 'Updated Name' +}, { + selectionSet: [...selectionSet] +}); +``` + +This works well if all subscriptions to `Blog` require the same subset of fields. If multiple subscriptions are involved with various selection sets, you must ensure that all `Blog` mutations contain the superset of fields from all subscriptions. + +Alternatively, you can skip the custom selection sets entirely. The internally generated selection set for any given model is identical across operations by default. The trade-off is that the default selection sets exclude related models. So, when related models are required, you would need to either lazy load them or construct a query to fetch them separately. + +#### Related Model Mutations + +Mutations do not trigger real-time updates for *related* models. This is true even when the subscription includes a related model in the selection set. For example, if we're subscribed to a particular `Blog` and wish to see updates when a `Post` is added or changed, it's tempting to create a subscribe on `Blog` and assume it "just works": + +```ts +// Notice how we're fetching a few `Blog` details, but mostly using +// the selection set to grab all the related posts. +const selectionSet = ['title', 'author', 'posts.*'] as const; + +const sub = client.models.Blog.observeQuery( + filter: { id: { eq: 'blog-id' } }, + selectionSet: [...selectionSet] +).subscribe({ + next(data) { + handle(data.items) + } +}); +``` + +But, mutations on `Post` records won't trigger an real-time event for the related `Blog`. If you need `Blog` updates when a `Post` is added, you must manually "touch" the relevant `Blog` record. + +```ts +async function addPostToBlog( + post: Schema['Post']['createType'], + blog: Schema['Blog']['type'] +) { + // Create the post first. + await client.models.Post.create({ + ...post, + blogId: blog.id + }); + + // "Touch" the blog, notifying subscribers to re-render. + await client.models.Blog.update({ + id: blog.id + }, { + // Remember to include the selection set if the subscription + // is looking for related-model fields! + selectionSet: [...selectionSet] + }); +} +``` + + + +## Set up a real-time event subscription + +Subscriptions is a feature that allows the server to send data to its clients when a specific event happens. For example, you can subscribe to an event when a new record is created, updated, or deleted through the API. Subscriptions are automatically available for any `a.model()` in your Amplify Data schema. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; + +const client = generateClient(); + +// Subscribe to creation of Todo +const createSub = client.models.Todo.onCreate().subscribe({ + next: (data) => console.log(data), + error: (error) => console.warn(error), +}); + +// Subscribe to update of Todo +const updateSub = client.models.Todo.onUpdate().subscribe({ + next: (data) => console.log(data), + error: (error) => console.warn(error), +}); + +// Subscribe to deletion of Todo +const deleteSub = client.models.Todo.onDelete().subscribe({ + next: (data) => console.log(data), + error: (error) => console.warn(error), +}); + +// Stop receiving data updates from the subscription +createSub.unsubscribe(); +updateSub.unsubscribe(); +deleteSub.unsubscribe(); +``` + +## Set up server-side subscription filters + +Subscriptions take an optional `filter` argument to define service-side subscription filters: + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; + +const client = generateClient(); + +const sub = client.models.Todo.onCreate({ + filter: { + content: { + contains: 'groceries', + }, + }, +}).subscribe({ + next: (data) => console.log(data), + error: (error) => console.warn(error), +}); +``` + +If you want to get all subscription events, don't specify any `filter` parameters. + + + +**Limitations:** + +- Specifying an empty object `{}` as a filter is **not** recommended. Using `{}` as a filter might cause inconsistent behavior based on your data model's authorization rules. +- If you're using dynamic group authorization and you authorize based on a single group per record, subscriptions are only supported if the user is part of five or fewer user groups. +- Additionally, if you authorize by using an array of groups (`groups: [String]`), + - subscriptions are only supported if the user is part of 20 or fewer groups + - you can only authorize 20 or fewer user groups per record + + + +### Subscription connection status updates + +Now that your application is set up and using subscriptions, you may want to know when the subscription is finally established, or reflect to your users when the subscription isn't healthy. You can monitor the connection state for changes through the `Hub` local eventing system. + +```ts +import { CONNECTION_STATE_CHANGE, ConnectionState } from 'aws-amplify/data'; +import { Hub } from 'aws-amplify/utils'; + +Hub.listen('api', (data: any) => { + const { payload } = data; + if (payload.event === CONNECTION_STATE_CHANGE) { + const connectionState = payload.data.connectionState as ConnectionState; + console.log(connectionState); + } +}); +``` + +#### Subscription connection states + +- **`Connected`** - Connected and working with no issues. +- **`ConnectedPendingDisconnect`** - The connection has no active subscriptions and is disconnecting. +- **`ConnectedPendingKeepAlive`** - The connection is open, but has missed expected keep-alive messages. +- **`ConnectedPendingNetwork`** - The connection is open, but the network connection has been disrupted. When the network recovers, the connection will continue serving traffic. +- **`Connecting`** - Attempting to connect. +- **`ConnectionDisrupted`** - The connection is disrupted and the network is available. +- **`ConnectionDisruptedPendingNetwork`** - The connection is disrupted and the network connection is unavailable. +- **`Disconnected`** - Connection has no active subscriptions and is disconnecting. + + + +Connections between your application and backend subscriptions can be interrupted for various reasons, including network outages or the device entering sleep mode. Your subscriptions will automatically reconnect when it becomes possible to do so. + +While offline, your application will miss messages and will not automatically catch up when reconnected. Depending on your use case, you may want to take action for your app to catch up when it comes back online. + +```js +import { generateClient, CONNECTION_STATE_CHANGE, ConnectionState } from 'aws-amplify/data' +import { Hub } from 'aws-amplify/utils' +import { Schema } from '../amplify/data/resource'; + +const client = generateClient() + +const fetchRecentData = () => { + const { data: allTodos } = await client.models.Todo.list(); +} + +let priorConnectionState: ConnectionState; + +Hub.listen("api", (data: any) => { + const { payload } = data; + if ( + payload.event === CONNECTION_STATE_CHANGE + ) { + + if (priorConnectionState === ConnectionState.Connecting && payload.data.connectionState === ConnectionState.Connected) { + fetchRecentData(); + } + priorConnectionState = payload.data.connectionState; + } +}); + +const createSub = client.models.Todo.onCreate().subscribe({ + next: payload => // Process incoming messages +}); + +const updateSub = client.models.Todo.onUpdate().subscribe({ + next: payload => // Process incoming messages +}); + +const deleteSub = client.models.Todo.onDelete().subscribe({ + next: payload => // Process incoming messages +}); + +const cleanupSubscriptions = () => { + createSub.unsubscribe(); + updateSub.unsubscribe(); + deleteSub.unsubscribe(); +} +``` + + + +## Unsubscribe from a subscription + +You can also unsubscribe from events by using subscriptions by implementing the following: + +```ts +// Stop receiving data updates from the subscription +sub.unsubscribe(); +``` + +## Conclusion + +Congratulations! You have finished the **Subscribe to real-time events** guide. In this guide, you set up subscriptions for real-time events and learned how to filter and cancel these subscriptions when needed. + +### Next steps + +Our recommended next steps include continuing to build out and customize your information architecture for your data. Some resources that will help with this work include: + +- [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz/) +- [Customize your data model](/[platform]/build-a-backend/data/data-modeling/) +- [Add custom business logic](/[platform]/build-a-backend/data/custom-business-logic/) + + + +Subscribe to mutations for creating real-time clients. + +Because the lifetime of the subscription will last longer than the lifetime of a single function, you can create an instance variable at the top of your class: + +#### [Async/Await] + +```swift +var subscription: AmplifyAsyncThrowingSequence> +``` + +#### [Combine] + +```swift +var subscription: AnyCancellable? +``` + +To listen to creation updates, you can use the following code sample: + +#### [Async/Await] + +```swift +func createSubscription() { + subscription = Amplify.API.subscribe(request: .subscription(of: Todo.self, type: .onCreate)) + Task { + do { + for try await subscriptionEvent in subscription { + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + print("Subscription connect state is \(subscriptionConnectionState)") + case .data(let result): + switch result { + case .success(let createdTodo): + print("Successfully got todo from subscription: \(createdTodo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } + } + } catch { + print("Subscription has terminated with \(error)") + } + } +} +``` + +#### [Combine] + +```swift +func createSubscription() { + let sequence = Amplify.API.subscribe(request: .subscription(of: Todo.self, type: .onCreate)) + subscription = Amplify.Publisher.create(sequence) + .sink { + if case let .failure(apiError) = $0 { + print("Subscription has terminated with \(apiError)") + } else { + print("Subscription has been closed successfully") + } + } + receiveValue: { result in + switch result { + case .connection(let subscriptionConnectionState): + print("Subscription connect state is \(subscriptionConnectionState)") + case .data(let result): + switch result { + case .success(let createdTodo): + print("Successfully got todo from subscription: \(createdTodo)") + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } + } +} +``` + +## Unsubscribing from updates + +### Async/Await + +To unsubscribe from updates, you can call `cancel()` on the subscription. + +```swift +func cancelSubscription() { + // Cancel the subscription listener when you're finished with it + subscription?.cancel() +} +``` + +### Combine + +Calling `cancel()` on the sequence will disconnect the subscription from the backend. Any downstream subscribers will also be cancelled. + +```swift +let sequence = Amplify.API.subscribe(...) +let subscription = Amplify.Publisher.create(sequence) +let allUpdates = subscription.sink(...) +let filteredUpdates = subscription.filter{...}.sink(...) +sequence.cancel() // sequence is now disconnected + // allUpdates and filteredUpdates will no longer receive data +``` + +Similarly, calling `cancel()` on the Combine subscriber (e.g., the `AnyCancellable` returned from `sink()`) will cause the underlying sequence to cancel. This will cause all attached subscribers to stop receiving updates. + +```swift +allUpdates.cancel() // sequence is disconnected + // filteredUpdates will no longer receive data +``` + + + +Subscribe to mutations for creating real-time clients: + +#### [Java] + +```java +ApiOperation subscription = Amplify.API.subscribe( + ModelSubscription.onCreate(Todo.class), + onEstablished -> Log.i("ApiQuickStart", "Subscription established"), + onCreated -> Log.i("ApiQuickStart", "Todo create subscription received: " + ((Todo) onCreated.getData()).getName()), + onFailure -> Log.e("ApiQuickStart", "Subscription failed", onFailure), + () -> Log.i("ApiQuickStart", "Subscription completed") +); + +// Cancel the subscription listener when you're finished with it +subscription.cancel(); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val subscription = Amplify.API.subscribe( + ModelSubscription.onCreate(Todo::class.java), + { Log.i("ApiQuickStart", "Subscription established") }, + { Log.i("ApiQuickStart", "Todo create subscription received: ${(it.data as Todo).name}") }, + { Log.e("ApiQuickStart", "Subscription failed", it) }, + { Log.i("ApiQuickStart", "Subscription completed") } +) + +// Cancel the subscription listener when you're finished with it +subscription.cancel(); +``` + +#### [Kotlin - Coroutines] + +```kotlin +val job = activityScope.launch { + try { + Amplify.API.subscribe(ModelSubscription.onCreate(Todo::class.java)) + .catch { Log.e("ApiQuickStart", "Error on subscription", it) } + .collect { Log.i("ApiQuickStart", "Todo created! ${it.data.name}") } + } catch (notEstablished: ApiException) { + Log.e("ApiQuickStart", "Subscription not established", it) + } +} + +// When done with subscription +job.cancel() +``` + +#### [RxJava] + +```java +RxSubscriptionOperation> subscription = + RxAmplify.API.subscribe(request); + +subscription + .observeConnectionState() + .subscribe(connectionStateEvent -> Log.i("ApiQuickStart", String.valueOf(connectionStateEvent))); + +subscription + .observeSubscriptionData() + .subscribe( + data -> Log.i("ApiQuickStart", data), + exception -> Log.e("ApiQuickStart", "Subscription failed.", exception), + () -> Log.i("ApiQuickStart", "Subscription completed.") + ); + +// Cancel the subscription listener when you're finished with it +subscription.cancel(); +``` + + + + +Subscribe to mutations for creating real-time clients. + +## Setup subscription with callbacks + +When creating subscriptions, a [`Stream`](https://api.dart.dev/dart-async/Stream-class.html) object will be returned to you. This `Stream` will continue producing events until either the subscription encounters an error or you cancel the subscription. In the case of need for limiting the amount of data that is omitted, you can take advantage of the Stream's helper functions such as `take`. The cancellation occurs when the defined amount of event has occurred: + +```dart +Stream> subscribe() { + final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); + final Stream> operation = Amplify.API + .subscribe( + subscriptionRequest, + onEstablished: () => safePrint('Subscription established'), + ) + // Listens to only 5 elements + .take(5) + .handleError( + (Object error) { + safePrint('Error in subscription stream: $error'); + }, + ); + return operation; +} +``` + +Alternatively, you can call [`Stream.listen`](https://api.dart.dev/dart-async/Stream/listen.html) to create a [`StreamSubscription`](https://api.dart.dev/dart-async/StreamSubscription-class.html) object which can be programmatically canceled. + +```dart +// Be sure to import this +import 'dart:async'; + +... + +StreamSubscription>? subscription; + +void subscribe() { + final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); + final Stream> operation = Amplify.API.subscribe( + subscriptionRequest, + onEstablished: () => safePrint('Subscription established'), + ); + subscription = operation.listen( + (event) { + safePrint('Subscription event data received: ${event.data}'); + }, + onError: (Object e) => safePrint('Error in subscription stream: $e'), + ); +} + +void unsubscribe() { + subscription?.cancel(); + subscription = null; +} +``` + +In addition to an `onCreate` subscription, you can also call `.onUpdate()` or `.onDelete()`. + +```dart +final onUpdateSubscriptionRequest = ModelSubscriptions.onUpdate(Todo.classType); +// or +final onDeleteSubscriptionRequest = ModelSubscriptions.onDelete(Todo.classType); +``` + +## Subscription connection status + +Now that you set up the application and are using subscriptions, you may want to know when the subscription is closed, or reflect to your users when the subscription isn’t healthy. You can monitor the subscription status for changes via `Amplify.Hub` + +```dart +Amplify.Hub.listen( + HubChannel.Api, + (ApiHubEvent event) { + if (event is SubscriptionHubEvent) { + safePrint(event.status); + } + }, +); +``` + +### SubscriptionStatus + +- **`connected`** - Connected and working with no issues +- **`connecting`** - Attempting to connect (both initial connection and reconnection) +- **`pendingDisconnect`** - Connection has no active subscriptions and is shutting down +- **`disconnected`** - Connection has no active subscriptions and is disconnected +- **`failed`** - Connection had a failure and has been disconnected + +## Automated Reconnection + +Under the hood, we will attempt to maintain a healthy web socket connection through network changes. For example, if a device’s connection changes from Wi-Fi to 5g network, the plugin will attempt to reconnect using the new network. + +Likewise, when disconnected from the internet unexpectedly, the subscription will attempt to reconnect using an exponential retry/back off strategy. By default, we will make 8 recovery attempts over about 50 seconds. If we cannot make a successful connection, then the web socket will be closed. You can customize this strategy when configuring the API plugin through `RetryOptions`. + +```dart +Future _configureAmplify() async { + final apiPlugin = AmplifyAPI( + options: APIPluginOptions( + modelProvider: ModelProvider.instance, + // Optional config + subscriptionOptions: const GraphQLSubscriptionOptions( + retryOptions: RetryOptions(maxAttempts: 10), + ), + ) + ); + await Amplify.addPlugin(apiPlugin); + + try { + await Amplify.configure(outputs); + } on AmplifyAlreadyConfiguredException { + safePrint( + "Tried to reconfigure Amplify; this can occur when your app restarts on Android."); + } +} +``` + + + +**Important**: While offline, your application will miss messages and will not automatically catch up when reconnection happens. Depending on your use case, you may want to take action to catch up when your app comes back online. The following example solves this problem by retrieving all data on reconnection. + + + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_api/amplify_api.dart'; +import './models/ModelProvider.dart'; // <--- Update import to reflect your project +import 'dart:async'; + +// ... + +List allTodos = []; +SubscriptionStatus prevSubscriptionStatus = SubscriptionStatus.disconnected; +StreamSubscription>? subscription; + +/// ... + +// Init listeners +Amplify.Hub.listen( + HubChannel.Api, + (ApiHubEvent event) { + if (event is SubscriptionHubEvent) { + if (prevSubscriptionStatus == SubscriptionStatus.connecting && + event.status == SubscriptionStatus.connected) { + getTodos(); // refetch todos + } + prevSubscriptionStatus = event.status; + } + }, +); + +subscribe(); + +/// ... + +Future getTodos() async { + try { + final request = ModelQueries.list(Todo.classType); + final response = await Amplify.API.query(request: request).response; + + final todos = response.data?.items ?? []; + if (response.errors.isNotEmpty) { + safePrint('errors: ${response.errors}'); + } + + setState(() { + allTodos = todos; + }); + } on ApiException catch (e) { + safePrint('Query failed: $e'); + return; + } +} + +void subscribe() { + final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); + final Stream> operation = Amplify.API.subscribe( + subscriptionRequest, + onEstablished: () => safePrint('Subscription established'), + ); + subscription = operation.listen( + (event) { + setState(() { + allTodos.add(event.data); + }); + }, + onError: (Object e) => safePrint('Error in subscription stream: $e'), + ); +} + +``` + + +--- + +--- +title: "Customize your data model" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-07-12T17:02:33.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/" +--- + +## Data modeling capabilities + +Every data model is defined as part of a data schema (`a.schema()`). You can enhance your data model with various fields, customize their identifiers, apply authorization rules, or model relationships. Every data model (`a.model()`) automatically provides create, read, update, and delete API operations as well as real-time subscription events. Below is a quick tour of the many functionalities you can add to your data model: + +```ts +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a + .schema({ + Customer: a + .model({ + customerId: a.id().required(), + // fields can be of various scalar types, + // such as string, boolean, float, integers etc. + name: a.string(), + // fields can be of custom types + location: a.customType({ + // fields can be required or optional + lat: a.float().required(), + long: a.float().required(), + }), + // fields can be enums + engagementStage: a.enum(["PROSPECT", "INTERESTED", "PURCHASED"]), + collectionId: a.id(), + collection: a.belongsTo("Collection", "collectionId") + // Use custom identifiers. By default, it uses an `id: a.id()` field + }) + .identifier(["customerId"]), + Collection: a + .model({ + customers: a.hasMany("Customer", "collectionId"), // setup relationships between types + tags: a.string().array(), // fields can be arrays + representativeId: a.id().required(), + // customize secondary indexes to optimize your query performance + }) + .secondaryIndexes((index) => [index("representativeId")]), + }) + .authorization((allow) => [allow.publicApiKey()]); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +## Gen 1 schema support + +If you are coming from Gen 1, you can continue to use the GraphQL Schema Definition Language (SDL) for defining your schema. However, we strongly recommend you use the TypeScript-first schema builder experience in your project as it provides type safety and is the recommended way of working with Amplify going forward. + + + +**Note:** Some features available in Gen 1 GraphQL SDL are not available in Gen 2. See the [feature matrix](/[platform]/start/migrate-to-gen2/#gen-1-vs-gen-2-feature-matrix) for features supported in Gen 2. + + + +```ts title="amplify/data/resource.ts" +import { defineData } from '@aws-amplify/backend'; + +const schema = /* GraphQL */` + type Todo @model @auth(rules: [{ allow: owner }]) { + content: String + isDone: Boolean + } +`; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +--- + +--- +title: "Add fields to data model" +section: "build-a-backend/data/data-modeling" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-04-07T19:23:58.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/add-fields/" +--- + +Amplify Data supports all AWS AppSync scalar types as field types. The following scalar types are available: + +|Field type|Description|TypeScript validation|GraphQL Scalar Type| +|-|-|-|-| +|`a.id()`|A unique identifier for an object. This scalar is serialized like a String but isn't meant to be human-readable. If not specified on create operations, a UUID will be generated.|`string`|ID| +|`a.string()`|A UTF-8 character sequence.|`string`|String| +|`a.integer()`|An integer value between -(2^31) and 2^31-1.|`number` but rounded to closest integer value upon query/mutation|Int| +|`a.float()`|An IEEE 754 floating point value.|`number`|Float| +|`a.boolean()`|A Boolean value, either true or false.|`boolean`|Boolean| +|`a.date()`|An extended ISO 8601 date string in the format `YYYY-MM-DD`.|`string`|AWSDate| +|`a.time()`|An extended ISO 8601 time string in the format `hh:mm:ss.sss`.|`string`|AWSTime| +|`a.datetime()`|An extended ISO 8601 date and time string in the format `YYYY-MM-DDThh:mm:ss.sssZ`.|`string`|AWSDateTime| +|`a.timestamp()`|An integer value representing the number of seconds before or after 1970-01-01-T00:00Z.|`number`|AWSTimestamp| +|`a.email()`|An email address in the format local-part@domain-part as defined by RFC 822.|`string` with local-part and domain-part type enforcement|AWSEmail| +|`a.json()`|A JSON string. Any valid JSON construct is automatically parsed and loaded in the resolver code as maps, lists, or scalar values, rather than as the literal input strings. Unquoted strings or otherwise invalid JSON result in a validation error.|`any`|AWSJSON| +|`a.phone()`|A phone number. This value is stored as a string. Phone numbers can contain either spaces or hyphens to separate digit groups. Phone numbers without a country code are assumed to be US/North American numbers adhering to the North American Numbering Plan.|`string` validation only happening service-side|AWSPhone| +|`a.url()`|A URL as defined by RFC 1738. For example, https://www.amazon.com/dp/B000NZW3KC/ or mailto:example@example.com. URLs must contain a schema (http, mailto) and can't contain two forward slashes (//) in the path part.|`string` but with type enforcement on the schema part|AWSURL| +|`a.ipAddress()`|A valid IPv4 or IPv6 address. IPv4 addresses are expected in quad-dotted notation (123.12.34.56). IPv6 addresses are expected in non-bracketed, colon-separated format (1a2b:3c4b:1234:4567). You can include an optional CIDR suffix (123.45.67.89/16) to indicate subnet mask.|`string` with type enforcement for IPv4 and IPv6 pattern|AWSIPAddress| + +## Specify a custom field type + +Sometimes, the built-in types do not meet the needs of your application. In those cases, you can specify custom types. You can either define the custom types inline or explicitly define the custom type in the schema. + +**Inline definition:** The "location" field will become a new non-model type that uses PascalCase, a naming convention in which the first letter of each word in a compound word is capitalized. If there are conflicts with another schema-level definition (model, custom type, enum), you will receive a Type error with a warning that you need to sift the value out as a separate item and use a "ref". + +```ts +a.schema({ + Post: a.model({ + location: a.customType({ + lat: a.float(), + long: a.float(), + }), + content: a.string(), + }), +}).authorization((allow) => allow.publicApiKey()); +``` + +**Explicit definition:** Specify the "Location" as `a.customType()` in your schema. To use the custom type, reference it through `a.ref()` in the respective field definitions. + +```ts +a.schema({ + Location: a.customType({ + lat: a.float(), + long: a.float(), + }), + + Post: a.model({ + location: a.ref('Location'), + content: a.string(), + }), + + User: a.model({ + lastKnownLocation: a.ref('Location'), + }), +}).authorization((allow) => allow.publicApiKey()); +``` + + +To set or read the location field on the client side, you can expand a nested object and the type system will auto-infer the allowed values. + +```ts +const { data: newPost, errors } = await client.models.Post.create({ + location: { + lat: 48.837006, + long: 8.28245, + }, +}); + +console.log(newPost?.location?.lat, newPost?.location?.long); +``` + + + +To set or read the location field on the client side, you can create a Post object with the location values. + +```swift +let post = Post( + location: .init( + lat: 48.837006, + long: 8.28245)) +let createdPost = try await Amplify.API.mutate(request: .create(post)).get() +print("\(createdPost)") +``` + + +## Specify an enum field type + +Enum has a similar developer experience as custom types: short-hand and long-form approaches. + +Short-hand approach + +```ts +a.schema({ + Post: a.model({ + privacySetting: a.enum(['PRIVATE', 'FRIENDS_ONLY', 'PUBLIC']), + content: a.string(), + }), +}).authorization((allow) => allow.publicApiKey()); +``` + +Long-form approach + +```ts +a.schema({ + PrivacySetting: a.enum([ + 'PRIVATE', + 'FRIENDS_ONLY', + 'PUBLIC' + ]), + + Post: a.model({ + content: a.string(), + privacySetting: a.ref('PrivacySetting'), + }), + + Video: a.model({ + privacySetting: a.ref('PrivacySetting'), + }), +}).authorization((allow) => allow.publicApiKey()); +``` + + +When creating a new item client-side, the enums are also type-enforced: +```ts +client.models.Post.create({ + content: 'hello', + // WORKS - value auto-completed + privacySetting: 'PRIVATE', + + // DOES NOT WORK - TYPE ERROR + privacySetting: 'NOT_PUBLIC', +}); +``` + + + +Creating a new item client-side with the enum value: + +```swift +let post = Post( + content: "hello", + privacySetting: .private) +let createdPost = try await Amplify.API.mutate(request: .create(post)).get() +``` + + + +### List enum values client-side + +You can list available enum values client-side using the `client.enums..values()` API. For example, this allows you to display the available enum values within a dropdown UI. + +```ts +const availableSettings = client.enums.PrivacySetting.values() +// availableSettings returns ["PRIVATE", "FRIENDS_ONLY", "PUBLIC"] +``` + + +## Mark fields as required + +By default, fields are optional. To mark a field as required, use the `.required()` modifier. + +```ts +const schema = a.schema({ + Todo: a.model({ + content: a.string().required(), + }), +}).authorization((allow) => allow.publicApiKey()); +``` + +## Mark fields as arrays + +Any field can be modified to be an array using the `.array()` modifier. + +```ts +const schema = a.schema({ + Todo: a.model({ + content: a.string().required(), + notes: a.string().array(), + }), +}).authorization((allow) => allow.publicApiKey()); +``` + +## Assign default values for fields + +You can use the `.default(...)` modifier to specify a default value for optional [scalar type fields](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html). The `.default(...)` modifier is not available for custom types, arrays, or relationships. + +```ts +const schema = a.schema({ + Todo: a.model({ + content: a.string().default('My new Todo'), + }), +}).authorization((allow) => allow.publicApiKey()); +``` + +**Note:** The `.default(...)` modifier can't be applied to required fields. + + +--- + +--- +title: "Modeling relationships" +section: "build-a-backend/data/data-modeling" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-10T00:17:11.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/relationships/" +--- + +When modeling application data, you often need to establish relationships between different data models. In Amplify Data, you can create one-to-many, one-to-one, and many-to-many relationships in your Data schema. On the client-side, Amplify Data allows you to lazy or eager load of related data. + + + + ## Types of relationships + +|Relationship|Code|Description|Example| +|-|-|-|-| +|one to many|`a.hasMany(...)` & `a.belongsTo(...)`|Creates a one-to-many relationship between two models.|A **Team** has many **Members**. A **Member** belongs to a **Team**.| +|one to one|`a.hasOne(...)` & `a.belongsTo(...)`|Creates a one-to-one relationship between two models.|A **Customer** has one **Cart**. A **Cart** belongs to one **Customer**.| +|many to many| Two `a.hasMany(...)` & `a.belongsTo(...)` on join tables|Create two one-to-many relationships between the related models in a join table.|A **Post** has many **Tags**. A **Tag** has many **Posts**.| + +## Model one-to-many relationships + +Create a one-to-many relationship between two models using the `hasMany()` and `belongsTo()` method. In the example below, a Team has many Members and a Member belongs to exactly one Team. + +1. Create a **reference field** called `teamId` on the **Member** model. This reference field's type **MUST** match the type of **Team**'s identifier. In this case, it's an auto-generated `id: a.id().required()` field. +2. Add a **relationship field** called `team` that references the `teamId` field. This allows you to query for the team information from the **Member** model. +3. Add a **relationship field** called `members` that references the `teamId` field on the **Member** model. + +```typescript +const schema = a.schema({ + Member: a.model({ + name: a.string().required(), + // 1. Create a reference field + teamId: a.id(), + // 2. Create a belongsTo relationship with the reference field + team: a.belongsTo('Team', 'teamId'), + }), + + Team: a.model({ + mantra: a.string().required(), + // 3. Create a hasMany relationship with the reference field + // from the `Member`s model. + members: a.hasMany('Member', 'teamId'), + }), +}).authorization((allow) => allow.publicApiKey()); +``` + +### Create a "Has Many" relationship between records + + +```ts +const { data: team } = await client.models.Team.create({ + mantra: 'Go Frontend!', +}); + +const { data: member } = await client.models.Member.create({ + name: "Tim", + teamId: team.id, +}); +``` + + + +```kt +val team = Team.builder() + .mantra("Go Frontend!") + .build() + +Amplify.API.mutate(ModelMutation.create(team), + { + Log.i("MyAmplifyApp", "Added team with id: ${it.data.id}") + val member = Member.builder() + .name("Tim") + .team(it.data) + .build() + + Amplify.API.mutate(ModelMutation.create(member), + { Log.i("MyAmplifyApp", "Added Member with id: ${it.data.id}")}, + { Log.e("MyAmplifyApp", "Create failed", it)}, + ) + }, { + Log.e("MyAmplifyApp", "Create failed", it) + }) +``` + + + +```swift +do { + let team = Team(mantra: "Go Frontend!") + let createdTeam = try await Amplify.API.mutate(request: .create(team)).get() + + let member = Member( + name: "Tim", + team: createdTeam) // Directly pass in the team instance + let createdMember = try await Amplify.API.mutate(request: .create(member)) +} catch { + print("Create team or member failed", error) +} +``` + + + +```dart +final team = Team(mantra: "Go Frontend!"); +final teamRequest = ModelMutations.create(team); +final teamResponse = await Amplify.API.mutate(request: teamRequest).response; + +final member = Member(name: "Tim", team: teamResponse.data); +final memberRequest = ModelMutations.create(member); +final memberResponse = await Amplify.API.mutate(request: memberRequest).response; +``` + + +### Update a "Has Many" relationship between records + + +```ts +const { data: newTeam } = await client.models.Team.create({ + mantra: 'Go Fullstack', +}); + +await client.models.Member.update({ + id: "MY_MEMBER_ID", + teamId: newTeam.id, +}); +``` + + + +```kt +val newTeam = Team.builder() + .mantra("Go Fullstack!") + .build() + +Amplify.API.mutate(ModelMutation.create(newTeam), + { + Log.i("MyAmplifyApp", "Added team with id: ${it.data.id}") + + val updatingMember = existingMember.copyOfBuilder().team(it.data).build() + + Amplify.API.mutate(ModelMutation.update(updatingMember), + { Log.i("MyAmplifyApp", "Updated Member with id: ${it.data.id}")}, + { Log.e("MyAmplifyApp", "Create failed", it)}, + ) + }, { + Log.e("MyAmplifyApp", "Create failed", it) + }) +``` + + + +```swift +do { + let newTeam = Team(mantra: "Go Fullstack!") + let createdNewTeam = try await Amplify.API.mutate(request: .create(newTeam)).get() + + existingMember.setTeam(createdNewTeam) + let updatedMember = try await Amplify.API.mutate(request: .update(existingMember)).get() +} catch { + print("Create team or update member failed", error) +} +``` + + + +```dart +final newTeam = Team(mantra: "Go Fullstack!"); +final newTeamRequest = ModelMutations.create(team); +final newTeamResponse = await Amplify.API.mutate(request: teamRequest).response; + +final memberWithUpdatedTeam = existingMember.copyWith(team: newTeamResponse.data); +final memberUpdateRequest = ModelMutations.update(memberWithUpdatedTeam); +final memberUpdateResponse = await Amplify.API.mutate(request: memberUpdateRequest).response; +``` + + +### Delete a "Has Many" relationship between records + +If your reference field is not required, then you can "delete" a one-to-many relationship by setting the relationship value to `null`. + + +```ts +await client.models.Member.update({ + id: "MY_MEMBER_ID", + teamId: null, +}); +``` + + + +```kt +val memberWithRemovedTeam = existingMember.copyOfBuilder().team(null).build() + +Amplify.API.mutate(ModelMutation.update(memberWithRemovedTeam), + { Log.i("MyAmplifyApp", "Updated Member with id: ${it.data.id}")}, + { Log.e("MyAmplifyApp", "Create failed", it)}, +) +``` + + + +```swift +do { + existingMember.setTeam(nil) + let memberRemovedTeam = try await Amplify.API.mutate(request: .update(existingMember)).get() +} catch { + print("Failed to remove team from member", error) +} +``` + + + +```dart +final memberWithRemovedTeam = existingMember.copyWith(team: null); +final memberRemoveRequest = ModelMutations.update(memberWithRemovedTeam); +final memberRemoveResponse = await Amplify.API.mutate(request: memberRemoveRequest).response; +``` + + + +### Load related data in a "Has Many" relationship + +```dart +// Fetch the team with the team id. +final teamRequest = ModelQueries.get( + Team.classType, TeamModelIdentifier(id: "YOUR_TEAM_ID")); +final teamResult = await Amplify.API.query(request: teamRequest).response; +final team = teamResult.data!; + +// Define a limit for your pagination +const limit = 100; + +// Do the initial call to get the initial items +final firstRequest = ModelQueries.list(Member.classType, + limit: limit, where: Member.TEAMID.eq(team.id)); +final firstResult = await Amplify.API.query(request: firstRequest).response; +final firstPageData = firstResult.data; + +// If there are more than 100 items you can reiterate the following code to get next pages. +if (firstPageData?.hasNextResult ?? false) { + final secondRequest = firstPageData!.requestForNextResult; + final secondResult = + await Amplify.API.query(request: secondRequest!).response; + return secondResult.data?.items ?? []; +} else { + // You can return the page data by calling items property. + return firstPageData?.items ?? []; +} +``` + + + +### Lazy load a "Has Many" relationship + + +```ts +const { data: team } = await client.models.Team.get({ id: "MY_TEAM_ID"}); + +const { data: members } = await team.members(); + +members.forEach(member => console.log(member.id)); +``` + + + +```kt +Amplify.API.query( + ModelQuery.get(Team::class.java, Team.TeamIdentifier("YOUR_TEAM_ID")), + { + suspend { + try { + val members = + when (val membersModelList = it.data.members) { + is LoadedModelList -> { + // Eager loading loads the 1st page only. + membersModelList.items + } + + is LazyModelList -> { + var page = membersModelList.fetchPage() + var loadedMembers = + mutableListOf(page.items) // initial page of members + // loop through all pages to fetch the full list of members + while (page.hasNextPage) { + val nextToken = page.nextToken + page = + membersModelList.fetchPage(nextToken) + // add the page of members to the members variable + loadedMembers += page.items + } + loadedMembers + } + } + Log.i("MyAmplifyApp", "members: $members") + } catch (error: ApiException) { + Log.e("MyAmplifyApp", "Failed to fetch members", error) + } + } + }, + { Log.e("MyAmplifyApp", "Failed to fetch team")}) +``` + + + +```swift +do { + let queriedTeam = try await Amplify.API.query( + request: .get( + Team.self, + byIdentifier: team.identifier)).get() + + guard let queriedTeam, let members = queriedTeam.members else { + print("Missing team or members") + return + } + try await members.fetch() + print("Number of members: \(members.count)") +} catch { + print("Failed to fetch team or members", error) +} +``` + + +### Eagerly load a "Has Many" relationship + + +```ts +const { data: teamWithMembers } = await client.models.Team.get( + { id: "MY_TEAM_ID" }, + { selectionSet: ["id", "members.*"] }, +); + +teamWithMembers.members.forEach(member => console.log(member.id)); +``` + + + +```kt +Amplify.API.query( + ModelQuery.get( + Team::class.java, + Team.TeamIdentifier("YOUR_TEAM_ID") + ) { teamPath -> includes(teamPath.members) }, + { + val members = (it.data.members as? LoadedModelList)?.items + }, + { Log.e("MyAmplifyApp", "Failed to fetch team")} +) +``` + + + +```swift +do { + let queriedTeamWithMembers = try await Amplify.API.query( + request: .get( + Team.self, + byIdentifier: team.identifier, + includes: { team in [team.members]})) + .get() + guard let queriedTeamWithMembers, let members = queriedTeamWithMembers.members else { + print("Missing team or members") + return + } + print("Number of members: \(members.count)") +} catch { + print("Failed to fetch team with members", error) +} +``` + + + + +### Handling orphaned foreign keys on parent record deletion in "Has Many" relationship + +```ts +// Get the IDs of the related members. +const { data: teamWithMembers } = await client.models.Team.get( + { id: teamId }, + { selectionSet: ["id", "members.*"] }, +); + +// Delete Team +await client.models.Team.delete({ id: teamWithMembers.id }); + +// Delete all members in parallel +await Promise.all( + teamWithMembers.members.map(member => + client.models.Member.delete({ id: member.id }) +)); +``` + + +## Model a "one-to-one" relationship + +Create a one-to-one relationship between two models using the `hasOne()` and `belongsTo()` methods. In the example below, a **Customer** has a **Cart** and a *Cart* belongs to a **Customer**. + +1. Create a **reference field** called `customerId` on the **Cart** model. This reference field's type **MUST** match the type of **Customer**'s identifier. In this case, it's an auto-generated `id: a.id().required()` field. +2. Add a **relationship field** called `customer` that references the `customerId` field. This allows you to query for the customer information from the **Cart** model. +3. Add a **relationship field** called `activeCart` that references the `customerId` field on the **Cart** model. + +```typescript +const schema = a.schema({ + Cart: a.model({ + items: a.string().required().array(), + // 1. Create reference field + customerId: a.id(), + // 2. Create relationship field with the reference field + customer: a.belongsTo('Customer', 'customerId'), + }), + Customer: a.model({ + name: a.string(), + // 3. Create relationship field with the reference field + // from the Cart model + activeCart: a.hasOne('Cart', 'customerId') + }), +}).authorization((allow) => allow.publicApiKey()); +``` + +### Create a "Has One" relationship between records + +To create a "has one" relationship between records, first create the parent item and then create the child item and assign the parent. + + +```ts +const { data: customer, errors } = await client.models.Customer.create({ + name: "Rene", +}); + +const { data: cart } = await client.models.Cart.create({ + items: ["Tomato", "Ice", "Mint"], + customerId: customer?.id, +}); +``` + + + +```kt +val customer = Customer.builder() + .name("Rene") + .build() + +Amplify.API.mutate(ModelMutation.create(customer), + { + Log.i("MyAmplifyApp", "Added customer with id: ${it.data.id}") + val cart = Cart.builder() + .items(listOf("Tomato", "Ice", "Mint")) + .customer(customer) + .build() + + Amplify.API.mutate(ModelMutation.create(cart), + { Log.i("MyAmplifyApp", "Added Cart with id: ${it.data.id}")}, + { Log.e("MyAmplifyApp", "Create failed", it)}, + ) + }, { + Log.e("MyAmplifyApp", "Create failed", it) + }) +``` + + + +```swift +do { + let customer = Customer(name: "Rene") + let createdCustomer = try await Amplify.API.mutate(request: .create(customer)).get() + + let cart = Cart( + items: ["Tomato", "Ice", "Mint"], + customer: createdCustomer) + let createdCart = try await Amplify.API.mutate(request: .create(cart)).get() +} catch { + print("Create customer or cart failed", error) +} +``` + + + +```dart +final customer = Customer(name: "Rene"); +final customerRequest = ModelMutations.create(customer); +final customerResponse = await Amplify.API.mutate(request: customerRequest).response; + +final cart = Cart(items: ["Tomato", "Ice", "Mint"], customer: teamResponse.customer); +final cartRequest = ModelMutations.create(cart); +final cartResponse = await Amplify.API.mutate(request: cartRequest).response; +``` + + +### Update a "Has One" relationship between records + +To update a "Has One" relationship between records, you first retrieve the child item and then update the reference to the parent to another parent. For example, to reassign a Cart to another Customer: + + +```ts +const { data: newCustomer } = await client.models.Customer.create({ + name: 'Ian', +}); + +await client.models.Cart.update({ + id: cart.id, + customerId: newCustomer?.id, +}); +``` + + + +```kt +val newCustomer = Customer.builder() + .mantra("Ian") + .build() + +Amplify.API.mutate(ModelMutation.create(newCustomer), + { + Log.i("MyAmplifyApp", "Added customer with id: ${it.data.id}") + + val updatingCart = existingCart.copyOfBuilder().customer(it.data).build() + + Amplify.API.mutate(ModelMutation.update(updatingCart), + { Log.i("MyAmplifyApp", "Updated cart with id: ${it.data.id}")}, + { Log.e("MyAmplifyApp", "Create failed", it)}, + ) + }, { + Log.e("MyAmplifyApp", "Create failed", it) + }) +``` + + + +```swift +do { + let newCustomer = Customer(name: "Rene") + let newCustomerCreated = try await Amplify.API.mutate(request: .create(newCustomer)).get() + existingCart.setCustomer(newCustomerCreated) + let updatedCart = try await Amplify.API.mutate(request: .update(existingCart)).get() +} catch { + print("Create customer or cart failed", error) +} +``` + + + +```dart +final newCustomer = Customer(name: "Ian"); +final newCustomerRequest = ModelMutations.create(newCustomer); +final newCustomerResponse = await Amplify.API.mutate(request: newCustomerRequest).response; + +final cartWithUpdatedCustomer = existingCart.copyWith(customer: newCustomerResponse.data); +final cartUpdateRequest = ModelMutations.update(cartWithUpdatedCustomer); +final cartUpdateResponse = await Amplify.API.mutate(request: cartUpdateRequest).response; +``` + + +### Delete a "Has One" relationship between records + + +You can set the relationship field to `null` to delete a "Has One" relationship between records. + +```ts +await client.models.Cart.update({ + id: project.id, + customerId: null, +}); +``` + + + +You can set the relationship field to `null` to delete a "Has One" relationship between records. + +```kt +val cartWithRemovedCustomer = existingCart.copyOfBuilder().customer(null).build() + +Amplify.API.mutate(ModelMutation.update(cartWithRemovedCustomer), + { Log.i("MyAmplifyApp", "Updated cart with id: ${it.data.id}")}, + { Log.e("MyAmplifyApp", "Create failed", it)}, +) +``` + + + +You can set the relationship field to `nil` to delete a "Has One" relationship between records. + +```swift +do { + existingCart.setCustomer(nil) + let cartWithCustomerRemoved = try await Amplify.API.mutate(request: .update(existingCart)).get() +} catch { + print("Failed to remove customer from cart", error) +} +``` + + + +```dart +final cartWithRemovedCustomer = existingCart.copyWith(customer: null); +final cartRemoveRequest = ModelMutations.update(cartWithRemovedCustomer); +final cartRemoveResponse = await Amplify.API.mutate(request: cartRemoveRequest).response; +``` + + + +### Load related data in "Has One" relationships + + +```swift +do { + guard let queriedCart = try await Amplify.API.query( + request: .get( + Cart.self, + byIdentifier: existingCart.identifier)).get() else { + print("Missing cart") + return + } + + let customer = try await queriedCart.customer +} catch { + print("Failed to fetch cart or customer", error) +} +``` + + + +```dart +// Fetch the cart with the cart id. +final cartRequest = ModelQueries.get( + Cart.classType, CartModelIdentifier(id: "MY_CART_ID")); +final cartResult = await Amplify.API.query(request: cartRequest).response; +final cart = cartResult.data!; + +// Do the customer call to with the id from cart +if (cart.customerId != null) { + final customerRequest = ModelQueries.get( + Customer.classType, CustomerModelIdentifier(id: cart.customerId!)); + final customerResult = + await Amplify.API.query(request: customerRequest).response; + final customer = customerResult.data!; +} +``` + + + + +### Lazy load a "Has One" relationship + + +```ts +const { data: cart } = await client.models.Cart.get({ id: "MY_CART_ID"}); +const { data: customer } = await cart.customer(); +``` + + + +```kt +Amplify.API.query( + ModelQuery.get(Team::class.java, Team.TeamIdentifier("YOUR_TEAM_ID")), + { + suspend { + try { + val customer = when (val customerReference = cart.customer) { + is LoadedModelReference -> { + customerReference.value + } + + is LazyModelReference -> { + customerReference.fetchModel() + } + } + Log.i("MyAmplifyApp", "customer: $customer") + } catch (error: ApiException) { + Log.e("MyAmplifyApp", "Failed to fetch customer", error) + } + } + }, + { Log.e("MyAmplifyApp", "Failed to get team")} +) +``` + + +### Eagerly load a "Has One" relationship + + +```ts +const { data: cart } = await client.models.Cart.get( + { id: "MY_CART_ID" }, + { selectionSet: ['id', 'customer.*'] }, +); + +console.log(cart.customer.id) +``` + + + +```kt +val cart = Amplify.API.query( + ModelQuery.get( + Cart::class.java, + Cart.CartIdentifier("YOUR_CART_ID") + ) { cartPath -> + includes(cartPath.customer) + }, +{ val customer = (cart.customer as? LoadedModelReference)?.value }, +{ Log.e("MyAmplifyApp", "Failed to fetch cart", it) }) +``` + + + + +### Handling orphaned foreign keys on parent record deletion in "Has One" relationship + +```ts +// Get the customer with their associated cart +const { data: customerWithCart } = await client.models.Customer.get( + { id: customerId }, + { selectionSet: ["id", "activeCart.*"] }, +); + +// Delete Cart if exists +await client.models.Cart.delete({ id: customerWithCart.activeCart.id }); + +// Delete the customer +await client.models.Customer.delete({ id: customerWithCart.id }); +``` + + +## Model a "many-to-many" relationship +In order to create a many-to-many relationship between two models, you have to create a model that serves as a "join table". This "join table" should contain two one-to-many relationships between the two related entities. For example, to model a **Post** that has many **Tags** and a **Tag** has many **Posts**, you'll need to create a new **PostTag** model that represents the relationship between these two entities. + +```typescript +const schema = a.schema({ + PostTag: a.model({ + // 1. Create reference fields to both ends of + // the many-to-many relationship + // highlight-start + postId: a.id().required(), + tagId: a.id().required(), + // highlight-end + // 2. Create relationship fields to both ends of + // the many-to-many relationship using their + // respective reference fields + // highlight-start + post: a.belongsTo('Post', 'postId'), + tag: a.belongsTo('Tag', 'tagId'), + // highlight-end + }), + Post: a.model({ + title: a.string(), + content: a.string(), + // 3. Add relationship field to the join model + // with the reference of `postId` + // highlight-next-line + tags: a.hasMany('PostTag', 'postId'), + }), + Tag: a.model({ + name: a.string(), + // 4. Add relationship field to the join model + // with the reference of `tagId` + // highlight-next-line + posts: a.hasMany('PostTag', 'tagId'), + }), +}).authorization((allow) => allow.publicApiKey()); +``` + +## Model multiple relationships between two models + +Relationships are defined uniquely by their reference fields. For example, a Post can have separate relationships with a Person model for `author` and `editor`. + +```typescript +const schema = a.schema({ + Post: a.model({ + title: a.string().required(), + content: a.string().required(), + // highlight-start + authorId: a.id(), + author: a.belongsTo('Person', 'authorId'), + editorId: a.id(), + editor: a.belongsTo('Person', 'editorId'), + // highlight-end + }), + Person: a.model({ + name: a.string(), + // highlight-start + editedPosts: a.hasMany('Post', 'editorId'), + authoredPosts: a.hasMany('Post', 'authorId'), + // highlight-end + }), +}).authorization((allow) => allow.publicApiKey()); +``` + +On the client-side, you can fetch the related data with the following code: + + +```ts +const client = generateClient(); + +const { data: post } = await client.models.Post.get({ id: "SOME_POST_ID" }); + +const { data: author } = await post?.author(); +const { data: editor } = await post?.editor(); +``` + + + +```swift + do { + guard let queriedPost = try await Amplify.API.query( + request: .get( + Post.self, + byIdentifier: post.identifier)).get() else { + print("Missing post") + return + } + + let loadedAuthor = try await queriedPost.author + let loadedEditor = try await queriedPost.editor +} catch { + print("Failed to fetch post, author, or editor", error) +} +``` + + +## Model relationships for models with sort keys in their identifier + +In cases where your data model uses sort keys in the identifier, you need to also add reference fields and store the sort key fields in the related data model: + +```ts +const schema = a.schema({ + Post: a.model({ + title: a.string().required(), + content: a.string().required(), + // Reference fields must correspond to identifier fields. + // highlight-start + authorName: a.string(), + authorDoB: a.date(), + // Must pass references in the same order as identifiers. + author: a.belongsTo('Person', ['authorName', 'authorDoB']), + // highlight-end + }), + Person: a.model({ + name: a.string().required(), + dateOfBirth: a.date().required(), + // Must reference all reference fields corresponding to the + // identifier of this model. + authoredPosts: a.hasMany('Post', ['authorName', 'authorDoB']), + // highlight-next-line + }).identifier(['name', 'dateOfBirth']), +}).authorization((allow) => allow.publicApiKey()); +``` + +## Make relationships required or optional + +Amplify Data's relationships use reference fields to determine if a relationship is required or not. If you mark a reference field as required, then you can't "delete" a relationship between two models. You'd have to delete the related record as a whole. + +```ts +const schema = a.schema({ + Post: a.model({ + title: a.string().required(), + content: a.string().required(), + // You must supply an author when creating the post + // Author can't be set to `null`. + // highlight-next-line + authorId: a.id().required(), + author: a.belongsTo('Person', 'authorId'), + // You can optionally supply an editor when creating the post. + // Editor can also be set to `null`. + // highlight-next-line + editorId: a.id(), + editor: a.belongsTo('Person', 'editorId'), + }), + Person: a.model({ + name: a.string(), + // highlight-start + editedPosts: a.hasMany('Post', 'editorId'), + authoredPosts: a.hasMany('Post', 'authorId'), + // highlight-end + }), +}).authorization((allow) => allow.publicApiKey()); +``` + +--- + +--- +title: "Customize data model identifiers" +section: "build-a-backend/data/data-modeling" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-06-06T19:31:52.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/identifiers/" +--- + +Identifiers are defined using the `.identifier()` method on a model definition. Usage of the `.identifier()` method is optional; when it's not present, the model will automatically have a field called `id` of type `ID` that is automatically generated unless manually specified. + +```typescript +const schema = a.schema({ + Todo: a.model({ + content: a.string(), + completed: a.boolean(), + }) + .authorization(allow => [allow.publicApiKey()]), +}); +``` + + +```ts +const client = generateClient(); + +const todo = await client.models.Todo.create({ content: 'Buy Milk', completed: false }); +console.log(`New Todo created: ${todo.id}`); // New Todo created: 5DB6B4CC-CD41-49F5-9844-57C0AB506B69 +``` + + + +```swift +let todo = Todo( + content: "Buy Milk", + completed: false) +let createdTodo = try await Amplify.API.mutate(request: .create(todo)).get() +print("New Todo created: \(createdTodo)") +``` + + +If you want, you can use Amplify Data to define single-field and composite identifiers: +- Single-field identifier with a consumer-provided value (type: `id` or `string`, and must be marked `required`) +- Composite identifier with a set of consumer-provided values (type: `id` or `string`, and must be marked `required`) + +## Single-field identifier + +If the default `id` identifier field needs to be customized, you can do so by passing the name of another field. + +```typescript +const schema = a.schema({ + Todo: a.model({ + todoId: a.id().required(), + content: a.string(), + completed: a.boolean(), + }) + .identifier(['todoId']) + .authorization(allow => [allow.publicApiKey()]), +}); +``` + + +```ts +const client = generateClient(); + +const { data: todo, errors } = await client.models.Todo.create({ todoId: 'MyUniqueTodoId', content: 'Buy Milk', completed: false }); +console.log(`New Todo created: ${todo.todoId}`); // New Todo created: MyUniqueTodoId +``` + + + +```swift +let todo = Todo( + todoId: "MyUniqueTodoId", + content: "Buy Milk", + completed: false) +let createdTodo = try await Amplify.API.mutate(request: .create(todo)).get() +print("New Todo created: \(createdTodo)") +``` + + +## Composite identifier + +For cases where items are uniquely identified by more than a single field, you can pass an array of the field names to the `identifier()` function: + +```typescript +const schema = a.schema({ + StoreBranch: a.model({ + geoId: a.id().required(), + name: a.string().required(), + country: a.string(), + state: a.string(), + city: a.string(), + zipCode: a.string(), + streetAddress: a.string(), + }).identifier(['geoId', 'name']) + .authorization(allow => [allow.publicApiKey()]), +}); +``` + +```ts +const client = generateClient(); + +const branch = await client.models.StoreBranch.get({ geoId: '123', name: 'Downtown' }); // All identifier fields are required when retrieving an item +``` + + + +```swift +let queriedStoreBranch = try await Amplify.API.query( + request: .get( + StoreBranch.self, + byIdentifier: .identifier( + geoId: "123", + name: "Downtown"))) +``` + + +--- + +--- +title: "Customize secondary indexes" +section: "build-a-backend/data/data-modeling" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2026-02-09T14:57:32.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/secondary-index/" +--- + +// cspell:ignore ACCOUNTREPRESENTATIVEID +You can optimize your list queries based on "secondary indexes". For example, if you have a **Customer** model, you can query based on the customer's **id** identifier field by default but you can add a secondary index based on the **accountRepresentativeId** to get list customers for a given account representative. + +A secondary index consists of a "hash key" and, optionally, a "sort key". Use the "hash key" to perform strict equality and the "sort key" for greater than (gt), greater than or equal to (ge), less than (lt), less than or equal to (le), equals (eq), begins with, and between operations. + +```ts title="amplify/data/resource.ts" +export const schema = a.schema({ + Customer: a + .model({ + name: a.string(), + phoneNumber: a.phone(), + accountRepresentativeId: a.id().required(), + }) + // highlight-next-line + .secondaryIndexes((index) => [index("accountRepresentativeId")]) + .authorization(allow => [allow.publicApiKey()]), +}); +``` + + +The example client query below allows you to query for "Customer" records based on their `accountRepresentativeId`: + +```ts title="src/App.tsx" +import { type Schema } from '../amplify/data/resource'; +import { generateClient } from 'aws-amplify/data'; + +const client = generateClient(); + +const { data, errors } = + // highlight-start + await client.models.Customer.listCustomerByAccountRepresentativeId({ + accountRepresentativeId: "YOUR_REP_ID", + }); + // highlight-end +``` + + + +The example client query below creates a custom GraphQL request that allows you to query for "Customer" records based on their `accountRepresentativeId`: + +```swift +struct PaginatedList: Decodable { + let items: [ModelType] + let nextToken: String? +} +let operationName = "listCustomer8ByAccountRepresentativeId" +let document = """ +query ListCustomer8ByAccountRepresentativeId { + \(operationName)(accountRepresentativeId: "\(accountRepresentativeId)") { + items { + createdAt + accountRepresentativeId + id + name + phoneNumber + updatedAt + } + nextToken + } +} +""" + +let request = GraphQLRequest>( + document: document, + responseType: PaginatedList.self, + decodePath: operationName) + +let queriedCustomers = try await Amplify.API.query( + request: request).get() +``` + + + +The example client query below allows you to query for "Customer" records based on their `accountRepresentativeId`: + +```dart title="lib/main.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'models/ModelProvider.dart'; + +// highlight-start +final request = ModelQueries.list( + Customer.classType, + where: Customer.ACCOUNTREPRESENTATIVEID.eq(YOUR_REP_ID), +); +// highlight-end + +``` + + +
    Review how this works under the hood with Amazon DynamoDB + +Amplify uses Amazon DynamoDB tables as the default data source for `a.model()`. For key-value databases, it is critical to model your access patterns with "secondary indexes". Use the `.secondaryIndexes()` modifier to configure a secondary index. + +**Amazon DynamoDB** is a key-value and document database that delivers single-digit millisecond performance at any scale but making it work for your access patterns requires a bit of forethought. DynamoDB query operations may use at most two attributes to efficiently query data. The first query argument passed to a query (the hash key) must use strict equality and the second attribute (the sort key) may use gt, ge, lt, le, eq, beginsWith, and between. DynamoDB can effectively implement a wide variety of access patterns that are powerful enough for the majority of applications. + +
    + +## Add sort keys to secondary indexes + +You can define "sort keys" to add a set of flexible filters to your query, such as "greater than" (gt), "greater than or equal to" (ge), "less than" (lt), "less than or equal to" (le), "equals" (eq), "begins with" (beginsWith), and "between" operations. + +```ts title="amplify/data/resource.ts" +export const schema = a.schema({ + Customer: a + .model({ + name: a.string(), + phoneNumber: a.phone(), + accountRepresentativeId: a.id().required(), + }) + .secondaryIndexes((index) => [ + index("accountRepresentativeId") + // highlight-next-line + .sortKeys(["name"]), + ]) + .authorization(allow => [allow.owner()]), +}); +``` + + +On the client side, you should find a new `listBy...` query that's named after hash key and sort keys. For example, in this case: `listByAccountRepresentativeIdAndName`. You can supply the filter as part of this new list query: + +```ts title="src/App.tsx" +const { data, errors } = + // highlight-next-line + await client.models.Customer.listCustomerByAccountRepresentativeIdAndName({ + accountRepresentativeId: "YOUR_REP_ID", + name: { + beginsWith: "Rene", + }, + }); +``` + + + +On the client side, you can create a custom GraphQL request based on the new `listBy...` query that's named after hash key and sort keys. You can supply the filter as part of this new list query: + +```swift +struct PaginatedList: Decodable { + let items: [ModelType] + let nextToken: String? +} +let operationName = "listCustomer9ByAccountRepresentativeIdAndName" +let document = """ +query ListCustomer8ByAccountRepresentativeId { + \(operationName)(accountRepresentativeId: "\(accountRepresentativeId)", name: {beginsWith: "\(name)"}) { + items { + accountRepresentativeId + createdAt + id + name + phoneNumber + updatedAt + } + nextToken + } +} +""" +let request = GraphQLRequest>( + document: document, + responseType: PaginatedList.self, + decodePath: operationName) + +let queriedCustomers = try await Amplify.API.query( + request: request).get() +``` + + + +The example client query below allows you to query for "Customer" records based on their `name` AND their `accountRepresentativeId`: + +```dart title="lib/main.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'models/ModelProvider.dart'; + +// highlight-start +final request = ModelQueries.list( + Customer.classType, + where: Customer.ACCOUNTREPRESENTATIVEID.eq(YOUR_REP_ID) & Customer.NAME.beginsWith("Rene"), +); +// highlight-end + +``` + + +## Customize the query field for secondary indexes + +You can also customize the auto-generated query name under `client.models..listBy...` by setting the `queryField()` modifier. + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + Customer: a + .model({ + name: a.string(), + phoneNumber: a.phone(), + accountRepresentativeId: a.id().required(), + }) + .secondaryIndexes((index) => [ + index("accountRepresentativeId") + // highlight-next-line + .queryField("listByRep"), + ]) + .authorization(allow => [allow.owner()]), +}); +``` + + +In your client app code, you'll see query updated under the Data client: + +```ts title="src/App.tsx" +const { + data, + errors + // highlight-next-line +} = await client.models.Customer.listByRep({ + accountRepresentativeId: 'YOUR_REP_ID', +}) +``` + + + +In your client app code, you can use the updated query name. + +```swift +struct PaginatedList: Decodable { + let items: [ModelType] + let nextToken: String? +} +let operationName = "listByRep" +let document = """ +query ListByRep { + \(operationName)(accountRepresentativeId: "\(accountRepresentativeId)") { + items { + accountRepresentativeId + createdAt + id + name + phoneNumber + updatedAt + } + nextToken + } +} +""" + +let request = GraphQLRequest>( + document: document, + responseType: PaginatedList.self, + decodePath: operationName) + +let queriedCustomers = try await Amplify.API.query( + request: request).get() +``` + + + +In your client app code, you can use the updated query name. + +```dart title="lib/main.dart" +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'models/ModelProvider.dart'; + +var accountRepresentativeId = "John"; +var operationName = "listByRep"; +var document = """ + query ListByRep { + $operationName(accountRepresentativeId: "$accountRepresentativeId") { + items { + accountRepresentativeId + createdAt + id + name + phoneNumber + updatedAt + } + nextToken + } + } + """; + +final request = GraphQLRequest( + document: document, + variables: { + 'accountRepresentativeId': accountRepresentativeId, + 'operationName': operationName, + }, +); +``` + + +## Customize the name of secondary indexes + +To customize the underlying DynamoDB's index name, you can optionally provide the `name()` modifier. + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + Customer: a + .model({ + name: a.string(), + phoneNumber: a.phone(), + accountRepresentativeId: a.id().required(), + }) + .secondaryIndexes((index) => [ + index("accountRepresentativeId") + // highlight-next-line + .name("MyCustomIndexName"), + ]) + .authorization(allow => [allow.owner()]), +}); +``` + +## Customize the projection type of secondary indexes + +You can control which attributes are projected (copied) into your secondary indexes to reduce storage costs and optimize for your query patterns. Use the `.projection()` modifier to specify a projection type. + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + Product: a + .model({ + id: a.id().required(), + name: a.string().required(), + category: a.string().required(), + price: a.float().required(), + description: a.string(), + inStock: a.boolean().required(), + }) + .secondaryIndexes((index) => [ + // Project only keys - smallest index size + // highlight-next-line + index('category').projection('KEYS_ONLY'), + + // Project specific attributes you need to query in addition to the keys + // highlight-next-line + index('inStock').projection('INCLUDE', ['name', 'price']), + + // Project all attributes (default) + // highlight-next-line + index('name').projection('ALL'), + ]) + .authorization(allow => [allow.publicApiKey()]), +}); +``` + +
    Review projection types and when to use them + +DynamoDB supports three projection types: + +- **KEYS_ONLY** - Projects only index and primary keys. Smallest index size, lowest storage cost. +- **INCLUDE** - Projects keys plus specified attributes. Balances storage cost with query flexibility. +- **ALL** - Projects all attributes. Maximum query flexibility but highest storage cost (default). + +Choose based on your query patterns and storage cost requirements. For more details, see [Projections in DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.Projections). + +
    + +--- + +--- +title: "Disable Operations" +section: "build-a-backend/data/data-modeling" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-03-03T02:02:22.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/disable-operations/" +--- + +The `disableOperations` method allows you to selectively disable specific GraphQL operations for a model in your Amplify application. This can be useful for implementing specialized API designs and reduce the number of resources being deployed. + +You can disable operations by adding the `disableOperations` method to your model definition: + +```ts title="amplify/data/resource.ts" +export const schema = a.schema({ + Customer: a + .model({ + name: a.string(), + phoneNumber: a.phone(), + accountRepresentativeId: a.id().required(), + }) + // highlight-next-line + .disableOperations(["mutations", "subscriptions", "queries"]) + .authorization(allow => [allow.publicApiKey()]), +}); +``` + +## Available Operation Types + +The `disableOperations` method accepts an array of operation types that you want to disable: + +### General Operation Categories + +- `mutations`: Disables all mutation operations (create, update, delete) +- `subscriptions`: Disables all real-time subscription operations (onCreate, onUpdate, onDelete) +- `queries`: Disables all query operations (get, list) + +### Specific Operations +You can also disable more granular operations: +Query Operations + +- `get`: Disables the ability to fetch a single item by ID +- `list`: Disables the ability to fetch multiple items + +### Mutation Operations + +- `create`: Disables the ability to create new items +- `update`: Disables the ability to update existing items +- `delete`: Disables the ability to delete items + +### Subscription Operations + +- `onCreate`: Disables real-time notifications when items are created +- `onUpdate`: Disables real-time notifications when items are updated +- `onDelete`: Disables real-time notifications when items are deleted + +You can specify one or more operation types in the array to disable them: + +``` +// Disable all mutations +disableOperations: ["mutations"] + +// Disable both subscriptions and queries +disableOperations: ["subscriptions", "queries"] + +// Disable specific operations +disableOperations: ["create", "update", "list"] + +// Disable specific subscription types +disableOperations: ["onCreate", "onUpdate"] + +// Mix general categories with specific operations +disableOperations: ["queries", "create", "onDelete"] +``` + +--- + +--- +title: "Customize your auth rules" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-11-05T19:14:26.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/" +--- + +Use the `.authorization()` modifier to configure authorization rules for public, signed-in user, per user, and per user group data access. **Authorization rules operate on the deny-by-default principle**. Meaning that if an authorization rule is not specifically configured, it is denied. + +```ts +const schema = a.schema({ + Post: a.model({ + content: a.string() + }).authorization(allow => [ + // Allow anyone auth'd with an API key to read everyone's posts. + allow.publicApiKey().to(['read']), + // Allow signed-in user to create, read, update, + // and delete their __OWN__ posts. + allow.owner(), + ]) +}) +``` +In the example above, everyone (`public`) can read every Post but authenticated users (`owner`) can create, read, update, and delete their own posts. Amplify also allows you to restrict the allowed operations, combine multiple authorization rules, and apply fine-grained field-level authorization. + +## Available authorization strategies + +Use the guide below to select the correct authorization strategy for your use case: + +| **Recommended use case** | **Strategy** | **`authMode`** | +|--------------------------|--------------|----------------| +| [Public data access where users or devices are anonymous. Anyone with the AppSync API key is granted access.](/[platform]/build-a-backend/data/customize-authz/public-data-access) | `publicApiKey` | `apiKey` | +| [Recommended for production environment's public data access. Public data access where unauthenticated users or devices are granted permissions using Amazon Cognito identity pool's role for unauthenticated identities.]( /[platform]/build-a-backend/data/customize-authz/public-data-access/#add-public-authorization-rule-using-iam-authentication) | `guest` | `identityPool` | +| [Per user data access. Access is restricted to the "owner" of a record. Leverages `amplify/auth/resource.ts` Cognito user pool by default.](/[platform]/build-a-backend/data/customize-authz/per-user-per-owner-data-access) | `owner`/`ownerDefinedIn`/`ownersDefinedIn` | `userPool` / `oidc` | +| [Any signed-in data access. Unlike owner-based access, **any** signed-in user has access.](/[platform]/build-a-backend/data/customize-authz/signed-in-user-data-access) | `authenticated` | `userPool` / `oidc` / `identityPool` | +| [Per user group data access. A specific or dynamically configured group of users has access.](/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access) | `group`/`groupDefinedIn`/`groups`/`groupsDefinedIn` | `userPool` / `oidc` | +| [Define your own custom authorization rule within a serverless function.](/[platform]/build-a-backend/data/customize-authz/custom-data-access-patterns) | `custom` | `lambda` | + +## Understand how authorization rules are applied + +Authorization rules can be applied globally across all data models in a schema, onto specific data models, and onto specific fields. + +Amplify will always use the most specific authorization rule that is available. For example, if there is an authorization rule for a field and an authorization rule for the model that the field belongs to, Amplify will evaluate against the field-level authorization rule. Review [Field-level authorization rules](#field-level-authorization-rules) to learn more. + +If there are multiple authorization rules present, they will be logically OR'ed. Review [Configure multiple authorization rules](#configure-multiple-authorization-rules) to learn more. For `userPools` and `oidc` authorization modes, the rules are evaluated in the sequence `authenticated` > `group(s)` > `owner(s)DefinedIn` > `group(s)DefinedIn`. + +### Global authorization rule (only for getting started) + +To help you get started, you can define an authorization rule on the data schema that will be applied to all data models that **do not** have a model-level authorization rule. Instead of having a global authorization rule for all production environments, we recommend creating specific authorization rules for each model or field. + +The global authorization rule below uses `allow.publicApiKey()`. This example allows anyone to create, read, update, and delete and is applied to every data model. + +```ts +const schema = a.schema({ + // Because no model-level authorization rule is present + // this model will use the global authorization rule. + Todo: a.model({ + content: a.string() + }), + + // Will use model-level authorization rule + Notes: a.model({ + content: a.string() + // [Model-level authorization rule] + }).authorization(allow => [allow.publicApiKey().to(['read'])]) + +// [Global authorization rule] +}).authorization(allow => [ + allow.publicApiKey() +]) +``` + +### Model-level authorization rules + +Add an authorization rule to a model to apply the authorization rule to all fields of that model. + +```ts +const schema = a.schema({ + Post: a.model({ + content: a.string(), + createdBy: a.string() + // [Model-level authorization rule] + // All fields (content, createdBy) will be protected by + // this authorization rule + }).authorization(allow => [ + allow.publicApiKey().to(['read']), + allow.owner(), + ]) +}) +``` + +### Field-level authorization rules + +When an authorization rule is added to a field, it will strictly define the authorization rules applied on the field. Field-level authorization rules **do not** inherit model-level authorization rules. Meaning, only the specified field-level authorization rule is applied. + +In the example below: +- Owners are allowed to create, read, update, and delete Employee records they own +- Any signed in user has read access and can read data with the exception of the `ssn` field +- Only the `ssn` field has `owner` auth applied and this field-level auth rule means that model-level auth rules are not applied + +```ts +const schema = a.schema({ + Employee: a.model({ + name: a.string(), + email: a.string(), + // [Field-level authorization rule] + // This auth rule will be used for the "ssn" field + // All other fields will use the model-level auth rule + ssn: a.string().authorization(allow => [allow.owner()]), + }) + + // [Model-level authorization rule] + .authorization(allow => [ + allow.authenticated().to(["read"]), + allow.owner() + ]), +}); +``` + +### Non-model authorization rules + +**Non-model** types are any types added to the schema without using `a.model()`. These consist of modifiers such as `a.customType()`, `a.enum()`,`a.query()`, `a.mutation()`, or `a.subscription()`. + +Dynamic authorization rules such as `allow.owner()`, `allow.ownerDefinedIn()`, `allow.groupDefinedIn()` are not supported for **non-model** types. + +```ts +const schema = a.schema({ + // ... + listCustomType: a + .query() + .returns(a.ref("CustomType").array()) + .handler( + a.handler.custom({ + entry: "./handler.js", + }) + ) + .authorization((allow) => [ + // Static auth rules - Supported + allow.guest(), + allow.publicApiKey(), + allow.authenticated(), + allow.group("Admin"), + allow.groups(["Teacher", "Student"]), + + // Dynamic auth rules - Not supported + allow.owner(), + allow.ownerDefinedIn("owner"), + allow.ownersDefinedIn("otherOwners"), + allow.groupDefinedIn("group"), + allow.groupsDefinedIn("otherGroups"), + ]), +}); +``` + +There are TS warnings and validation checks in place that will cause a sandbox deployment to fail if unsupported auth rules are defined on custom queries and mutations. + +### Configure multiple authorization rules + +When combining multiple authorization rules, they are "logically OR"-ed. In the following example: +- Any user (using Amazon Cognito identity pool's unauthenticated roles) is allowed to read all posts +- Owners are allowed to create, read, update, and delete their own posts + +```ts +const schema = a.schema({ + Post: a.model({ + title: a.string(), + content: a.string() + }).authorization(allow => [ + allow.guest().to(["read"]), + allow.owner() + ]) +}) +``` + +On the client side, make sure to always authenticate with the corresponding authorization mode. + + +```ts +import { generateClient } from 'aws-amplify/data' +import type { Schema } from '@/amplify/data/resource' // Path to your backend resource definition + +const client = generateClient() + +// Creating a post is restricted to Cognito User Pools +const { data: newPostResult , errors } = await client.models.Post.create({ + query: queries.createPost, + variables: { input: { title: 'Hello World' } }, + authMode: 'userPool', +}); + +// Listing posts is available to unauthenticated users (verified by Amazon Cognito identity pool's unauthenticated role) +const { data: listPostsResult , errors } = await client.models.Post.list({ + query: queries.listPosts, + authMode: 'identityPool', +}); +``` + + + +Creating a post is restricted to Cognito User Pools. + +```swift +do { + let post = Post(title: "Hello World") + let createdTodo = try await Amplify.API.mutate(request: .create( + post, + authMode: .amazonCognitoUserPools)).get() +} catch { + print("Failed to create post", error) +} +``` + +Listing posts is available to unauthenticated users (verified by Amazon Cognito identity pool's unauthenticated role) + +```swift +do { + let queriedPosts = try await Amplify.API.query(request: .list( + Post.self, + authMode: .awsIAM)).get() + print("Number of posts:", queriedPosts.count) +} catch { + print("Failed to list posts", error) +} +``` + + +## IAM authorization + +All Amplify Gen 2 projects enable IAM authorization for data access. This ensures that the Amplify console's [data manager](/[platform]/build-a-backend/data/manage-with-amplify-console/) will be able to access your API. It also allows you to authorize other administrative or machine-to-machine access using your own IAM policies. See the [AWS AppSync Developer Guide](https://docs.aws.amazon.com/appsync/latest/devguide/security_iam_service-with-iam.html) for details on how AWS AppSync works with IAM. + +## Authorization on custom types + +Authorization rules are only supported on data models (model-level and field-level) and custom operations (queries, mutations and subscriptions). They are not fully supported on custom types, including custom types returned by custom operations. For example, consider a custom query that returns a custom type: + +```ts +const schema = a.schema({ + Counter: a.customType({ + value: a.integer(), + }) + .authorization(...), // <-- not supported + getCounter: a + .mutation() + .arguments({ + id: a.string().required(), + }) + .returns(a.ref("Counter")) + .handler( + a.handler.custom({ + entry: "./getCounter.js", + }) + ) + .authorization((allow) => [allow.authenticated()]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema: schema, + authorizationModes: { + defaultAuthorizationMode: "userPool", + }, +}); +``` + +As you can see, the custom `Counter` type does not support the `.authorization()` modifier. Instead, behind the scenes, Amplify will add appropriate authorization rules to `Counter` to allow authenticated users to access it. That means that any signed-in user will be able to access the custom operation and all fields of the custom type. + +> **Info:** **Note**: IAM authorization is not currently supported for custom operations that return custom types if `defaultAuthorizationMode` is not `iam`. See [GitHub issue #2929](https://github.com/aws-amplify/amplify-category-api/issues/2929) for details and suggested workarounds. + +## Learn more about specific authorization strategies + +--- + +--- +title: "Public data access" +section: "build-a-backend/data/customize-authz" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-12-12T20:29:55.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/public-data-access/" +--- + +The public authorization strategy grants everyone access to the API, which is protected behind the scenes with an API key. You can also override the authorization provider to use an unauthenticated IAM role from Cognito instead of an API key for public access. + +## Add public authorization rule using API key-based authentication + +To grant everyone access, use the `.public()` authorization strategy. Behind the scenes, the API will be protected with an API key. + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + .authorization(allow => [allow.publicApiKey()]), +}); +``` + + +In your application, you can perform CRUD operations against the model using `client.models.` by specifying the `apiKey` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'apiKey', + } + // highlight-end +); +``` + + + +In your application, you can perform CRUD operations against the model by specifying the `apiKey` auth mode. + + + +```dart +try { + final todo = Todo(content: 'My new todo'); + final request = ModelMutations.create( + todo, + authorizationMode: APIAuthorizationType.apiKey, + ); + final createdTodo = await Amplify.API.mutations(request: request).response; + + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + +} on APIException catch (e) { + safePrint('Failed to create todo', e); +} +``` + + + +```swift +do { + let todo = Todo(content: "My new todo") + let createdTodo = try await Amplify.API.mutate(request: .create( + todo, + authMode: .apiKey)).get() +} catch { + print("Failed to create todo", error) +} +``` + + +### Extend API Key Expiration + +If the API key has not expired, you can extend the expiration date by deploying your app again. The API key expiration date will be set to `expiresInDays` days from the date when the app is deployed. In the example below, the API key will expire 7 days from the latest deployment. + +```ts title="amplify/data/resource.ts" +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 7, + }, + }, +}); +``` + +### Rotate an API Key + +You can rotate an API key if it was expired, compromised, or deleted. To rotate an API key, you can override the logical ID of the API key resource in the `amplify/backend.ts` file. This will create a new API key with a new logical ID. + +```ts title="amplify/backend.ts" +const backend = defineBackend({ + auth, + data, +}); + +backend.data.resources.cfnResources.cfnApiKey?.overrideLogicalId( + `recoverApiKey${new Date().getTime()}` +); +``` + +Deploy your app. After the deploy has finished, remove the override to the logical ID and deploy your app again to use the default logical ID. + +```ts title="amplify/backend.ts" +const backend = defineBackend({ + auth, + data, +}); + +// backend.data.resources.cfnResources.cfnApiKey?.overrideLogicalId( +// `recoverApiKey${new Date().getTime()}` +// ); +``` + +A new API key will be created for your app. + +## Add public authorization rule using Amazon Cognito identity pool's unauthenticated role + +You can also override the authorization provider. In the example below, `identityPool` is specified as the provider which allows you to use an "Unauthenticated Role" from the Cognito identity pool for public access instead of an API key. Your Auth resources defined in `amplify/auth/resource.ts` generates scoped down IAM policies for the "Unauthenticated role" in the Cognito identity pool automatically. + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + .authorization(allow => [allow.guest()]), +}); +``` + + +In your application, you can perform CRUD operations against the model using `client.models.` with the `identityPool` auth mode. + +> **Info:** If you're not using the auto-generated **amplify_outputs.json** file, then you must set the Amplify Library resource configuration's `allowGuestAccess` flag to `true`. This lets the Amplify Library use the unauthenticated role from your Cognito identity pool when your user isn't logged in. +> +>
    Amplify configuration +> ```ts title="src/App.tsx" +import { Amplify } from "aws-amplify"; +import outputs from "../amplify_outputs.json"; + +Amplify.configure( + { + ...outputs, + Auth: { + Cognito: { + identityPoolId: config.aws_cognito_identity_pool_id, + userPoolClientId: config.aws_user_pools_web_client_id, + userPoolId: config.aws_user_pools_id, + allowGuestAccess: true, + }, + }, + } +); +``` +>
    + +```ts title="src/App.tsx" +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'identityPool', + } + // highlight-end +); +``` + + + +In your application, you can perform CRUD operations against the model with the `iam` auth mode. + +```dart +try { + final todo = Todo(content: 'My new todo'); + final request = ModelMutations.create( + todo, + authorizationMode: APIAuthorizationType.iam, + ); + final createdTodo = await Amplify.API.mutations(request: request).response; + + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + +} on APIException catch (e) { + safePrint('Failed to create todo', e); +} +``` + + + +In your application, you can perform CRUD operations against the model with the `awsIAM` auth mode. + +```swift +do { + let todo = Todo(content: "My new todo") + let createdTodo = try await Amplify.API.mutate(request: .create( + todo, + authMode: .awsIAM)).get() +} catch { + print("Failed to create todo", error) +} +``` + + +--- + +--- +title: "Per-user/per-owner data access" +section: "build-a-backend/data/customize-authz" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-06-19T16:13:00.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/per-user-per-owner-data-access/" +--- + +The `owner` authorization strategy restricts operations on a record to only the record's owner. When configured, the `owner` field will automatically be added and populated with the identity of the created user. The API will authorize against the `owner` field to allow or deny operations. + +## Add per-user/per-owner authorization rule + +You can use the `owner` authorization strategy to restrict a record's access to a specific user. When `owner` authorization is configured, only the record's `owner` is allowed the specified operations. + +```ts title="amplify/data/resource.ts" +// The "owner" of a Todo is allowed to create, read, update, and delete their own todos +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + .authorization(allow => [allow.owner()]), +}); +``` + +```ts title="amplify/data/resource.ts" +// The "owner" of a Todo record is only allowed to create, read, and update it. +// The "owner" of a Todo record is denied to delete it. +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + .authorization(allow => [allow.owner().to(['create', 'read', 'update'])]), +}); +``` + + +In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'userPool', + } + // highlight-end +); +``` + + + +In your application, you can perform CRUD operations against the model with the `userPools` auth mode. + +```dart +try { + final todo = Todo(content: 'My new todo'); + final request = ModelMutations.create( + todo, + authorizationMode: APIAuthorizationType.userPools, + ); + final createdTodo = await Amplify.API.mutations(request: request).response; + + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + +} on APIException catch (e) { + safePrint('Failed to create todo', e); +} +``` + + + +In your application, you can perform CRUD operations against the model with the `amazonCognitoUserPools` auth mode. + +```swift +do { + let todo = Todo(content: "My new todo") + let createdTodo = try await Amplify.API.mutate(request: .create( + todo, + authMode: .amazonCognitoUserPools)).get() +} catch { + print("Failed to create todo", error) +} +``` + + +Behind the scenes, Amplify will automatically add a `owner: a.string()` field to each record which contains the record owner's identity information upon record creation. + +By default, the Cognito user pool's user information is populated into the `owner` field. The value saved includes `sub` and `username` in the format `::`. The API will authorize against the full value of `::` or `sub` / `username` separately and return `username`. You can alternatively configure [OpenID Connect as an authorization provider](/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider). + +> **Warning:** **By default, owners can reassign the owner of their existing record to another user.** +> +> To prevent an owner from reassigning their record to another user, protect the owner field (by default `owner: String`) with a [field-level authorization rule](/[platform]/build-a-backend/data/customize-authz/#field-level-authorization-rules). For example, in a social media app, you would want to prevent Alice from being able to reassign Alice's Post to Bob. +> +> ```ts +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + owner: a.string().authorization(allow => [allow.owner().to(['read', 'delete'])]), + }) + .authorization(allow => [allow.owner()]), +}); +``` + +## Customize the owner field + +You can override the `owner` field to your own preferred field, by specifying a custom `ownerField` in the authorization rule. + +```ts +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + author: a.string(), // record owner information now stored in "author" field + }) + .authorization(allow => [allow.ownerDefinedIn('author')]), +}); +``` + +--- + +--- +title: "Multi-user data access" +section: "build-a-backend/data/customize-authz" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-06-19T16:13:00.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/multi-user-data-access/" +--- + +The `ownersDefinedIn` rule grants a set of users access to a record by automatically creating an `owners` field to store the allowed record owners. You can override the default owners field name by specifying `inField` with the desired field name to store the owner information. You can dynamically manage which users can access a record by updating the owner field. + +## Add multi-user authorization rule + +If you want to grant a set of users access to a record, you use the `ownersDefinedIn` rule. This automatically creates a `owners: a.string().array()` field to store the allowed owners. + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + owners: a.string().array(), + }) + .authorization(allow => [allow.ownersDefinedIn('owners')]), +}); +``` + + +In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +// Create a record with current user as first owner +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'userPool', + } + // highlight-end +); +``` + +Add another user as an owner + +```ts +await client.models.Todo.update( + { + id: newTodo.id, + owners: [...(newTodo.owners as string[]), otherUserId], + }, + // highlight-start + { + authMode: "userPool" + } + // highlight-end +); +``` + + + +In your application, you can perform CRUD operations against the model with the `userPools` auth mode. + +```dart +try { + final todo = Todo(content: 'My new todo'); + final request = ModelMutations.create( + todo, + authorizationMode: APIAuthorizationType.userPools, + ); + final createdTodo = await Amplify.API.mutations(request: request).response; + + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + +} on APIException catch (e) { + safePrint('Failed to create todo', e); +} +``` + +Add another user as an owner + +```dart +try { + createdTodo.owners!.add(otherUserId); + let updateRequest = ModelMutations.update( + createdTodo, + authorizationMode: APIAuthorizationType.userPools, + ); + final updatedTodo = await Amplify.API.mutations(request: updateRequest).response; + + if (updatedTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + +} catch { + safePrint("Failed to update todo", error) +} +``` + + + +In your application, you can perform CRUD operations against the model with the `amazonCognitoUserPools` auth mode. + +```swift +do { + let todo = Todo(content: "My new todo") + let createdTodo = try await Amplify.API.mutate(request: .create( + todo, + authMode: .amazonCognitoUserPools)).get() +} catch { + print("Failed to create todo", error) +} +``` + +Add another user as an owner + +```swift +do { + createdTodo.owners?.append(otherUserId) + let updatedTodo = try await Amplify.API.mutate(request: .update( + createdTodo, + authMode: .amazonCognitoUserPools)).get() +} catch { + print("Failed to update todo", error) +} +``` + + +## Override to a list of owners + +You can override the `inField` to a list of owners. Use this if you want a dynamic set of users to have access to a record. In the example below, the `authors` list is populated with the creator of the record upon record creation. The creator can then update the `authors` field with additional users. Any user listed in the `authors` field can access the record. + +```ts +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + authors: a.string().array(), // record owner information now stored in "authors" field + }) + .authorization(allow => [allow.ownersDefinedIn('authors')]), +}); +``` + +--- + +--- +title: "Signed-in user data access" +section: "build-a-backend/data/customize-authz" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-06-19T16:13:00.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/signed-in-user-data-access/" +--- + +The `authenticated` authorization strategy restricts record access to only signed-in users authenticated through IAM, Cognito, or OpenID Connect, applying the authorization rule to all users. It provides a simple way to make data private to all authenticated users. + +## Add signed-in user authorization rule + +You can use the `authenticated` authorization strategy to restrict a record's access to every signed-in user. + + +**Note:** If you want to restrict a record's access to a specific user, see [Per-user/per-owner data access](/[platform]/build-a-backend/data/customize-authz/per-user-per-owner-data-access/). The `authenticated` authorization strategy detailed on this page applies the authorization rule for data access to **every** signed-in user. + + +In the example below, anyone with a valid JWT token from the Cognito user pool is allowed access to all Todos. + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + .authorization(allow => [allow.authenticated()]), +}); +``` + + +In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'userPool', + } + // highlight-end +); +``` + + + +In your application, you can perform CRUD operations against the model with the `userPools` auth mode. + +```dart +try { + final todo = Todo(content: 'My new todo'); + final request = ModelMutations.create( + todo, + authorizationMode: APIAuthorizationType.userPools, + ); + final createdTodo = await Amplify.API.mutations(request: request).response; + + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + +} on APIException catch (e) { + safePrint('Failed to create todo', e); +} +``` + + + +In your application, you can perform CRUD operations against the model with the `amazonCognitoUserPools` auth mode. + +```swift +do { + let todo = Todo(content: "My new todo") + let createdTodo = try await Amplify.API.mutate(request: .create( + todo, + authMode: .amazonCognitoUserPools)).get() +} catch { + print("Failed to create todo", error) +} +``` + + +## Use identity pool for signed-in user authentication + +You can also override the authorization provider. In the example below, `identityPool` is specified as the provider which allows you to use an "Unauthenticated Role" from the Cognito identity pool for public access instead of an API key. Your Auth resources defined in `amplify/auth/resource.ts` generates scoped down IAM policies for the "Unauthenticated role" in the Cognito identity pool automatically. + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + .authorization(allow => [allow.authenticated('identityPool')]), +}); +``` + + +In your application, you can perform CRUD operations against the model using `client.models.` with the `iam` auth mode. + +> **Info:** The user must be logged in for the Amplify Library to use the authenticated role from your Cognito identity pool. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'identityPool', + } + // highlight-end +); +``` + + + +In your application, you can perform CRUD operations against the model with the `iam` auth mode. + +```dart +try { + final todo = Todo(content: 'My new todo'); + final request = ModelMutations.create( + todo, + authorizationMode: APIAuthorizationType.iam, + ); + final createdTodo = await Amplify.API.mutations(request: request).response; + + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + +} on APIException catch (e) { + safePrint('Failed to create todo', e); +} +``` + + + +In your application, you can perform CRUD operations against the model with the `awsIAM` auth mode. + +> **Info:** The user must be logged in for the Amplify Library to use the authenticated role from your Cognito identity pool. + +```swift +do { + let todo = Todo(content: "My new todo") + let createdTodo = try await Amplify.API.mutate(request: .create( + todo, + authMode: .awsIAM)).get() +} catch { + print("Failed to create todo", error) +} +``` + + +In addition, you can also use OpenID Connect with `authenticated` authorization. See [OpenID Connect as an authorization provider](/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider/). + +--- + +--- +title: "User group-based data access" +section: "build-a-backend/data/customize-authz" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-12-23T16:31:56.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/user-group-based-data-access/" +--- + +You can use the `group` authorization strategy to restrict access based on user groups. The user group authorization strategy allows restricting data access to specific user groups or groups defined dynamically on each data record. + +## Add authorization rules for specific user groups + +When you want to restrict access to a specific set of user groups, provide the group names in the `groups` parameter. In the example below, only users that are part of the "Admin" user group are granted access to the Salary model. + +```ts title="amplify/data/resource.ts" +// allow one specific group +const schema = a.schema({ + Salary: a + .model({ + wage: a.float(), + currency: a.string(), + }) + .authorization(allow => [allow.group('Admin')]), +}); +``` + + +In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +// As a signed-in user that belongs to the 'Admin' User Pool Group +const { errors, data: newSalary } = await client.models.Salary.create( + { + wage: 50.25, + currency: 'USD' + }, + // highlight-start + { + authMode: 'userPool', + } + // highlight-end +); +``` + + + +In your application, you can perform CRUD operations against the model with the `userPools` auth mode. + +```dart +try { + final todo = Todo(content: 'My new todo'); + final request = ModelMutations.create( + todo, + authorizationMode: APIAuthorizationType.userPools, + ); + final createdTodo = await Amplify.API.mutations(request: request).response; + + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + +} on APIException catch (e) { + safePrint('Failed to create todo', e); +} +``` + + + +In your application, you can perform CRUD operations against the model with the `amazonCognitoUserPools` auth mode. + +```swift +do { + let salary = Salary( + wage: 50.25, + currency: "USD") + let createdSalary = try await Amplify.API.mutate(request: .create( + salary, + authMode: .amazonCognitoUserPools)).get() +} catch { + print("Failed to create salary", error) +} +``` + + +This can then be updated to allow access to multiple defined groups; in this example below we added access for "Leadership". + +```ts +// allow multiple specific groups +const schema = a.schema({ + Salary: a + .model({ + wage: a.float(), + currency: a.string(), + }) + .authorization(allow => [allow.groups(['Admin', 'Leadership'])]), +}); +``` + +## Add authorization rules for dynamically set user groups + +With dynamic group authorization, each record contains an attribute specifying what Cognito groups should be able to access it. Use the first argument to specify which attribute in the underlying data store holds this group information. To specify that a single group should have access, use a field of type `a.string()`. To specify that multiple groups should have access, use a field of type `a.string().array()`. + +```ts +// Dynamic group authorization with multiple groups +const schema = a.schema({ + Post: a + .model({ + title: a.string(), + groups: a.string().array(), + }) + .authorization(allow => [allow.groupsDefinedIn('groups')]), +}); +``` + +```ts +// Dynamic group authorization with a single group +const schema = a.schema({ + Post: a + .model({ + title: a.string(), + group: a.string(), + }) + .authorization(allow => [allow.groupDefinedIn('group')]), +}); +``` + +By default, `group` authorization leverages Amazon Cognito user pool groups but you can also use OpenID Connect with `group` authorization. See [OpenID Connect as an authorization provider](/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider). + + +**Known limitations for real-time subscriptions when using dynamic group authorization:** + +1. If you authorize based on a single group per record, then subscriptions are only supported if the user is part of 5 or fewer user groups +2. If you authorize via an array of groups (`groups: a.string().array()` used in the example above), + - subscriptions are only supported if the user is part of 20 or fewer groups + - you can only authorize 20 or fewer user groups per record + + +## Access user groups from the session + + +You can access a user's groups from their session using the Auth category: + +```ts +import { fetchAuthSession } from 'aws-amplify/auth'; + +const session = await fetchAuthSession(); +const groups = session.tokens.accessToken.payload['cognito:groups'] || []; + +console.log('User groups:', groups); +``` + + +--- + +--- +title: "Custom data access using Lambda functions" +section: "build-a-backend/data/customize-authz" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-06-19T16:13:00.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/custom-data-access-patterns/" +--- + +You can define your own custom authorization rule with a Lambda function. + +```ts title="amplify/data/resource.ts" +import { + type ClientSchema, + a, + defineData, + defineFunction, +} from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + // STEP 1 + // Indicate which models / fields should use a custom authorization rule + .authorization(allow => [allow.custom()]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'lambda', + // STEP 2 + // Pass in the function to be used for a custom authorization rule + lambdaAuthorizationMode: { + function: defineFunction({ + entry: './custom-authorizer.ts', + }), + // (Optional) STEP 3 + // Configure the token's time to live + timeToLiveInSeconds: 300, + }, + }, +}); +``` + +In your application, you can perform CRUD operations against the model using `client.models.` with the `lambda` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + content: 'My new todo', + }, + // highlight-start + { + authMode: 'lambda', + } + // highlight-end +); +``` + + + +In your application, you can perform CRUD operations against the model with the `function` auth mode. + +```dart +try { + final todo = Todo(content: 'My new todo'); + final request = ModelMutations.create( + todo, + authorizationMode: APIAuthorizationType.function, + ); + final createdTodo = await Amplify.API.mutations(request: request).response; + + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + +} on APIException catch (e) { + safePrint('Failed to create todo', e); +} +``` + + +The Lambda function of choice will receive an authorization token from the client and execute the desired authorization logic. The AppSync GraphQL API will receive a payload from Lambda after invocation to allow or deny the API call accordingly. + +To configure a Lambda function as the authorization mode, create a new file `amplify/data/custom-authorizer.ts`. You can use this Lambda function code template as a starting point for your authorization handler code: + +```ts +// amplify/data/custom-authorizer.ts + +// This is sample code. Update this to suite your needs +import type { AppSyncAuthorizerHandler } from 'aws-lambda'; // types imported from @types/aws-lambda + +type ResolverContext = { + userid: string; + info: string; + more_info: string; +}; + +export const handler: AppSyncAuthorizerHandler = async ( + event +) => { + console.log(`EVENT: ${JSON.stringify(event)}`); + const { + authorizationToken, + requestContext: { apiId, accountId } + } = event; + const response = { + isAuthorized: authorizationToken === 'custom-authorized', + resolverContext: { + // eslint-disable-next-line spellcheck/spell-checker + userid: 'user-id', + info: 'contextual information A', + more_info: 'contextual information B' + }, + deniedFields: [ + `arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Event/fields/comments`, + `Mutation.createEvent` + ], + ttlOverride: 300 + }; + console.log(`RESPONSE: ${JSON.stringify(response, null, 2)}`); + return response; +}; +``` + +You can use the template above as a starting point for your custom authorization rule. The authorization Lambda function receives the following event: + +```json +{ + "authorizationToken": "ExampleAuthToken123123123", # Authorization token specified by client + "requestContext": { + "apiId": "aaaaaa123123123example123", # AppSync API ID + "accountId": "111122223333", # AWS Account ID + "requestId": "f4081827-1111-4444-5555-5cf4695f339f", + "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", # GraphQL query + "operationName": "MyQuery", # GraphQL operation name + "variables": {} # any additional variables supplied to the operation + } +} +``` + +Your Lambda authorization function needs to return the following JSON: + +```json +{ + // required + "isAuthorized": true, // if "false" then an UnauthorizedException is raised, access is denied + "resolverContext": { "banana": "very yellow" }, // JSON object visible as $ctx.identity.resolverContext in VTL resolver templates + + // optional + "deniedFields": ["TypeName.FieldName"], // Forces the fields to "null" when returned to the client + "ttlOverride": 10 // The number of seconds that the response should be cached for. Overrides default specified in "amplify update api" +} +``` + +Review the Amplify documentation to set the custom authorization token for the [Data client](/[platform]/build-a-backend/data/connect-to-API). + +--- + +--- +title: "Use OpenID Connect as an authorization provider" +section: "build-a-backend/data/customize-authz" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-06-19T16:13:00.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/using-oidc-authorization-provider/" +--- + +Private, owner, and group authorization can be configured with an OpenID Connect (OIDC) authorization mode. Add `"oidc"` to the authorization rule as the provider. Use the `oidcAuthorizationMode` property to configure the *OpenID Connect provider name*, *OpenID Connect provider domain*, *Client ID*, *Issued at TTL*, and *Auth Time TTL*. + +The example below highlights the supported authorization strategies with a `oidc` authorization provider. For owner and group-based authorization, you also will need to [specify a custom identity and group claim](/[platform]/build-a-backend/data/customize-authz/configure-custom-identity-and-group-claim). + +```ts title="amplify/data/resource.ts" +// amplify/data/resource.ts +import { a, defineData, type ClientSchema } from '@aws-amplify/backend'; + +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + .authorization(allow => [ + allow.owner('oidc').identityClaim('user_id'), + allow.authenticated('oidc'), + allow + .groups(['testGroupName'], 'oidc') + .withClaimIn('user_groups'), + ]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'oidc', + oidcAuthorizationMode: { + oidcProviderName: 'oidc-provider-name', + oidcIssuerUrl: 'https://example.com', + clientId: 'client-id', + tokenExpiryFromAuthInSeconds: 300, + tokenExpireFromIssueInSeconds: 600, + }, + }, +}); +``` + + +In your application, you can perform CRUD operations against the model using `client.models.` by specifying the `oidc` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: todos } = await client.models.Todo.list({ + // highlight-start + authMode: "oidc", + // highlight-end +}); +``` + + + +In your application, you can perform CRUD operations against the model with the `oidc` auth mode. + +```dart +try { + final todo = Todo(content: 'My new todo'); + final request = ModelMutations.create( + todo, + authorizationMode: APIAuthorizationType.oidc, + ); + final createdTodo = await Amplify.API.mutations(request: request).response; + + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + +} on APIException catch (e) { + safePrint('Failed to create todo', e); +} +``` + + +--- + +--- +title: "Configure custom identity and group claims" +section: "build-a-backend/data/customize-authz" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-06-19T16:13:00.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/configure-custom-identity-and-group-claim/" +--- + +Amplify Data supports using custom identity and group claims if you do not wish to use the default Amazon Cognito-provided `cognito:groups` or the double-colon-delimited claims, `sub::username`, from your JWT token. This can be helpful if you are using tokens from a 3rd party OIDC system or if you wish to populate a claim with a list of groups from an external system, such as when using a [Pre Token Generation Lambda Trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html) which reads from a database. + +To use custom claims specify `identityClaim` or `groupClaim` as appropriate. In the example below, the `identityClaim` is specified and the record owner will check against this `user_id` claim. Similarly, if the `user_groups` claim contains a "Moderator" string then access will be granted. + +```ts title="amplify/data/resource.ts" +import { a, defineData, type ClientSchema } from '@aws-amplify/backend'; + +const schema = a.schema({ + Post: a + .model({ + id: a.id(), + owner: a.string(), + postname: a.string(), + content: a.string(), + }) + .authorization(allow => [ + allow.owner().identityClaim('user_id'), + allow.groups(['Moderator']).withClaimIn('user_groups'), + ]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ schema }); + +``` + + +In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. + +```ts +import { generateClient } from 'aws-amplify/data'; +import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition + +const client = generateClient(); + +const { errors, data: newTodo } = await client.models.Todo.create( + { + postname: 'My New Post' + content: 'My post content', + }, + // highlight-start + { + authMode: 'userPool', + } + // highlight-end +); +``` + + + +In your application, you can perform CRUD operations against the model with the `userPools` auth mode. + +```dart +try { + final todo = Todo(content: 'My new todo'); + final request = ModelMutations.create( + todo, + authorizationMode: APIAuthorizationType.userPools, + ); + final createdTodo = await Amplify.API.mutations(request: request).response; + + if (createdTodo == null) { + safePrint('errors: ${response.errors}'); + return; + } + safePrint('Mutation result: ${createdTodo.name}'); + +} on APIException catch (e) { + safePrint('Failed to create todo', e); +} +``` + + +--- + +--- +title: "Grant Lambda function access to API and Data" +section: "build-a-backend/data/customize-authz" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-04-18T18:44:52.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/" +--- + +Function access to `defineData` can be configured using an authorization rule on the schema object. + +```ts title="amplify/data/resource.ts" +import { + a, + defineData, + type ClientSchema +} from '@aws-amplify/backend'; +import { functionWithDataAccess } from '../function/data-access/resource'; + +const schema = a + .schema({ + Todo: a.model({ + name: a.string(), + description: a.string(), + isDone: a.boolean() + }) + }) + // highlight-next-line + .authorization(allow => [allow.resource(functionWithDataAccess)]); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema +}); +``` + +Create a new directory and a resource file, `amplify/functions/data-access/resource.ts`. Then, define the Function with `defineFunction`: + +```ts title="amplify/functions/data-access/resource.ts" +import { defineFunction } from '@aws-amplify/backend'; + +export const functionWithDataAccess = defineFunction({ + name: 'data-access', +}); +``` + +The object returned from `defineFunction` can be passed directly to `allow.resource()` in the schema authorization rules. This will grant the function the ability to execute Query, Mutation, and Subscription operations against the GraphQL API. Use the `.to()` method to narrow down access to one or more operations. + +```ts title="amplify/data/resource.ts" +const schema = a + .schema({ + Todo: a.model({ + name: a.string(), + description: a.string(), + isDone: a.boolean() + }) + }) + // highlight-start + .authorization(allow => [ + allow.resource(functionWithDataAccess).to(['query', 'listen']) + ]); // allow query and subscription operations but not mutations +// highlight-end +``` + +> **Info:** Function access can only be configured on the schema object. It cannot be configured on individual models or fields. + +## Access the API using `aws-amplify` + +In the handler file for your function, configure the Amplify data client + +```ts title="amplify/functions/data-access/handler.ts" +import type { Handler } from 'aws-lambda'; +import type { Schema } from '../../data/resource'; +import { Amplify } from 'aws-amplify'; +import { generateClient } from 'aws-amplify/data'; +import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; +import { env } from '$amplify/env/'; // replace with your function name + +const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig(env); + +Amplify.configure(resourceConfig, libraryOptions); + +const client = generateClient(); + +export const handler = async (event) => { + // your function code goes here +} +``` + +> **Warning:** When configuring Amplify with `getAmplifyDataClientConfig`, your function consumes schema information from an S3 bucket created during backend deployment with grants for the access your function need to use it. Any changes to this bucket outside of backend deployment may break your function. + +Once you have generated the client code, update the function to access the data. The following code creates a todo and then lists all todos. + +```ts title="amplify/functions/data-access/handler.ts" +//... +const client = generateClient(); + +export const handler: Handler = async (event) => { + const { errors: createErrors, data: newTodo } = await client.models.Todo.create({ + name: "My new todo", + description: "Todo description", + isDone: false, + }) + + const { errors: listErrors, data: todos } = await client.models.Todo.list(); + + return event; +}; +``` + +--- + +--- +title: "Add custom queries and mutations" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-02-25T18:57:30.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/" +--- + +The `a.model()` data model provides a solid foundation for querying, mutating, and fetching data. However, you may need additional customizations to meet specific requirements around custom API requests, response formatting, and/or fetching from external data sources. + +In the following sections, we walk through the three steps to create a custom query or mutation: + +1. Define a custom query or mutation +2. Configure custom business logic handler code +3. Invoke the custom query or mutation + +## Step 1 - Define a custom query or mutation + +| Type | When to choose | +| --- | --- | +| Query | When the request only needs to read data and will not modify any backend data | +| Mutation | When the request will modify backend data | + +For every custom query or mutation, you need to set a return type and, optionally, arguments. Use `a.query()` or `a.mutation()` to define your custom query or mutation in your **amplify/data/resource.ts** file: + +#### [Custom query] + +```ts +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + // 1. Define your return type as a custom type + EchoResponse: a.customType({ + content: a.string(), + executionDuration: a.float() + }), + + // 2. Define your query with the return type and, optionally, arguments + echo: a + .query() + // arguments that this query accepts + .arguments({ + content: a.string() + }) + // return type of the query + .returns(a.ref('EchoResponse')) + // only allow signed-in users to call this API + .authorization(allow => [allow.authenticated()]) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema +}); +``` + +#### [Custom mutation] + +```ts +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + // 1. Define your return type as a custom type or model + Post: a.model({ + id: a.id(), + content: a.string(), + likes: a.integer() + }), + + // 2. Define your mutation with the return type and, optionally, arguments + likePost: a + .mutation() + // arguments that this query accepts + .arguments({ + postId: a.string() + }) + // return type of the query + .returns(a.ref('Post')) + // only allow signed-in users to call this API + .authorization(allow => [allow.authenticated()]) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema +}); +``` + +## Step 2 - Configure custom business logic handler code + +After your query or mutation is defined, you need to author your custom business logic. You can either define it in a [function](/[platform]/build-a-backend/functions/) or using a [custom resolver powered by AppSync JavaScript resolver](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html). + +#### [Function] + +In your `amplify/data/echo-handler/` folder, create a `handler.ts` file. You can import a utility type for your function handler via the `Schema` type from your backend resource. This gives you type-safe handler parameters and return values. + +```ts title="amplify/data/echo-handler/handler.ts" +import type { Schema } from '../resource' + +export const handler: Schema["echo"]["functionHandler"] = async (event, context) => { + const start = performance.now(); + return { + content: `Echoing content: ${event.arguments.content}`, + executionDuration: performance.now() - start + }; +}; +``` + +In your `amplify/data/resource.ts` file, define the function using `defineFunction` and then reference the function with your query or mutation using `a.handler.function()` as a handler. + +```ts title="amplify/data/resource.ts" +import { + type ClientSchema, + a, + defineData, + defineFunction // 1.Import "defineFunction" to create new functions +} from '@aws-amplify/backend'; + +// 2. define a function +const echoHandler = defineFunction({ + entry: './echo-handler/handler.ts' +}) + +const schema = a.schema({ + EchoResponse: a.customType({ + content: a.string(), + executionDuration: a.float() + }), + + echo: a + .query() + .arguments({ content: a.string() }) + .returns(a.ref('EchoResponse')) + .authorization(allow => [allow.publicApiKey()]) + // 3. set the function has the handler + .handler(a.handler.function(echoHandler)) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30 + }, + }, +}); +``` + +If you want to use an existing Lambda function, you can reference it by its name: `a.handler.function('name-of-existing-lambda-fn')`. Note that Amplify will not update this external Lambda function or its dependencies. + +#### [Custom resolver powered by AppSync JavaScript resolvers] + +Custom resolvers work on a "request/response" basis. You choose a data source, map your request to the data source's input parameters, and then map the data source's response back to the query/mutation's return type. Custom resolvers provide the benefit of no cold starts, less infrastructure to manage, and no additional charge for Lambda function invocations. Review [Choosing between custom resolver and function](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-overview-js.html#choosing-data-source). + +In your `amplify/data/resource.ts` file, define a custom handler using `a.handler.custom`. + +```ts title="amplify/data/resource.ts" +import { + type ClientSchema, + a, + defineData, +} from '@aws-amplify/backend'; + +const schema = a.schema({ + Post: a.model({ + content: a.string(), + likes: a.integer() + .authorization(allow => [allow.authenticated().to(['read'])]) + }).authorization(allow => [ + allow.owner(), + allow.authenticated().to(['read']) + ]), + + likePost: a + .mutation() + .arguments({ postId: a.id() }) + .returns(a.ref('Post')) + .authorization(allow => [allow.authenticated()]) + .handler(a.handler.custom({ + dataSource: a.ref('Post'), + entry: './increment-like.js' + })) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30 + } + }, +}); +``` + +```ts title="amplify/data/increment-like.js" +import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + return { + operation: 'UpdateItem', + key: util.dynamodb.toMapValues({ id: ctx.args.postId}), + update: { + expression: 'ADD likes :plusOne', + expressionValues: { ':plusOne': { N: 1 } }, + } + } +} + +export function response(ctx) { + return ctx.result +} +``` + +By default, you'll be able to access any existing database tables (powered by Amazon DynamoDB) using `a.ref('MODEL_NAME')`. But you can also reference any other external data source from within your AWS account, by adding them to your backend definition. + +The supported data sources are: +- Amazon DynamoDB +- AWS Lambda +- Amazon RDS databases with Data API +- Amazon EventBridge +- OpenSearch +- HTTP endpoints + +You can add these additional data sources via our `amplify/backend.ts` file: + +```ts title="amplify/backend.ts" +import * as dynamoDb from 'aws-cdk-lib/aws-dynamodb' +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +import { data } from './data/resource'; + +export const backend = defineBackend({ + auth, + data, +}); + +const externalDataSourcesStack = backend.createStack("MyExternalDataSources") + +const externalTable = dynamoDb.Table.fromTableName(externalDataSourcesStack, "MyTable", "MyExternalTable") + +backend.data.addDynamoDbDataSource( + // highlight-next-line + "ExternalTableDataSource", + externalTable) +``` + +In your schema you can then reference these additional data sources based on their name: + +```ts title="amplify/data/resource.ts" +import { + type ClientSchema, + a, + defineData, +} from '@aws-amplify/backend'; + +const schema = a.schema({ + Post: a.model({ + content: a.string(), + likes: a.integer() + .authorization(allow => [allow.authenticated().to(['read'])]) + }).authorization(allow => [ + allow.owner(), + allow.authenticated().to(['read']) + ]), + + likePost: a + .mutation() + .arguments({ postId: a.id() }) + .returns(a.ref('Post')) + .authorization(allow => [allow.authenticated()]) + .handler(a.handler.custom({ + // highlight-next-line + dataSource: "ExternalTableDataSource", + entry: './increment-like.js' + })) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30 + } + }, +}); +``` + +> **Warning:** All handlers must be of the same type. For example, you can't mix and match `a.handler.function` with `a.handler.custom` within a single `.handler()` modifier. + +## Step 3 - Invoke the custom query or mutation + + +From your generated Data client, you can find all your custom queries and mutations under the `client.queries.` and `client.mutations.` APIs respectively. + +#### [Custom query] + +```ts +const { data, errors } = await client.queries.echo({ + content: 'hello world!!!' +}); +``` + +#### [Custom mutation] + +```ts +const { data, errors } = await client.mutations.likePost({ + postId: 'hello' +}); +``` + + + + +```swift +struct EchoResponse: Codable { + public let echo: Echo + + struct Echo: Codable { + public let content: String + public let executionDuration: Float + } +} + +let document = """ + query EchoQuery($content: String!) { + echo(content: $content) { + content + executionDuration + } + } + """ + +let result = try await Amplify.API.query(request: GraphQLRequest( + document: document, + variables: [ + "content": "hello world!!!" + ], + responseType: EchoResponse.self +)) +switch result { +case .success(let response): + print(response.echo) +case .failure(let error): + print(error) +} +``` + + + +```kt +data class EchoDetails( + val content: String, + val executionDuration: Float +) + +data class EchoResponse( + val echo: EchoDetails +) + +val document = """ + query EchoQuery(${'$'}content: String!) { + echo(content: ${'$'}content) { + content + executionDuration + } + } +""".trimIndent() +val echoQuery = SimpleGraphQLRequest( + document, + mapOf("content" to "hello world!!!"), + String::class.java, + GsonVariablesSerializer()) + +Amplify.API.query( + echoQuery, + { + var gson = Gson() + val response = gson.fromJson(it.data, EchoResponse::class.java) + Log.i("MyAmplifyApp", "${response.echo.content}") + }, + { Log.e("MyAmplifyApp", "$it")} +) +``` + + + +First define a class that matches your response shape: + +```dart +class EchoResponse { + final Echo echo; + + EchoResponse({required this.echo}); + + factory EchoResponse.fromJson(Map json) { + return EchoResponse( + echo: Echo.fromJson(json['echo']), + ); + } +} + +class Echo { + final String content; + final double executionDuration; + + Echo({required this.content, required this.executionDuration}); + + factory Echo.fromJson(Map json) { + return Echo( + content: json['content'], + executionDuration: json['executionDuration'], + ); + } +} +``` + +Next, make the request and map the response to the classes defined above: + +```dart +// highlight-next-line +import 'dart:convert'; + +// highlight-start +const graphQLDocument = ''' + query Echo(\$content: String!) { + echo(content: \$content) { + content + executionDuration + } + } +'''; + +final echoRequest = GraphQLRequest( + document: graphQLDocument, + variables: {"content": "Hello world!!!"}, +); + +final response = + await Amplify.API.query(request: echoRequest).response; +safePrint(response); + +Map jsonMap = json.decode(response.data!); +EchoResponse echoResponse = EchoResponse.fromJson(jsonMap); +safePrint(echoResponse.echo.content); +// highlight-end +``` + +## Supported Argument Types in Custom Operations + +Custom operations can accept different types of arguments. Understanding these options helps define flexible and well-structured APIs. + +### Defining Arguments in Custom Operations + +When defining a custom operation, you can specify arguments using different types: + +- **Scalar Fields**: Basic types such as `string`, `integer`, `float`, etc +- **Custom Types**: Define inline `customType` +- **Reference Types**: Use `a.ref()` to reference enums and custom types + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + Status: a.enum(['ACCEPTED', 'REJECTED']), + + getPost: a + .query() + .arguments({ + // scalar field + content: a.string(), + // inline custom type + config: a.customType({ + filter: a.string(), + // reference to enum + status: a.ref('Status') + }), + }) + .returns(a.string()) + .authorization(allow => [allow.authenticated()]) +}); +``` + +## Async function handlers + +Async function handlers allow you to execute long-running operations asynchronously, improving the responsiveness of your API. This is particularly useful for tasks that don't require an immediate response, such as batch processing, putting messages in a queue, and initiating a generative AI model inference. + +### Usage + +To define an async function handler, use the `.async()` method when defining your handler: + +```ts title="amplify/data/resource.ts" +const signUpForNewsletter = defineFunction({ + entry: './sign-up-for-newsletter/handler.ts' +}); + +const schema = a.schema({ + someAsyncOperation: a.mutation() + .arguments({ + email: a.email().required() + }) + .handler(a.handler.function(signUpForNewsletter).async()) + .authorization((allow) => allow.guest()), +}) +``` + +### Key Characteristics + +1. **Single Return Type**: Async handlers return a static type `EventInvocationResponse` and don't support specifying a return type. The `.returns()` method is not available for operations using async handlers. + +2. **Fire and Forget**: The client is informed whether the invocation was successfully queued, but doesn't receive data from the Lambda function execution. + +3. **Pipeline Support**: Async handlers can be used in function pipelines. If the final handler is an async function, the return type of the query or mutation is `EventInvocationResponse`. + +--- + +--- +title: "Connect to Amazon OpenSearch for search and aggregate queries" +section: "build-a-backend/data/custom-business-logic" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-05-05T16:41:40.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/search-and-aggregate-queries/" +--- + +Amazon OpenSearch Service provides a managed platform for deploying search and analytics solutions with OpenSearch or Elasticsearch. The zero-ETL integration between Amazon DynamoDB and OpenSearch Service allows seamless search on DynamoDB data by automatically replicating and transforming it without requiring custom code or infrastructure. This integration simplifies processes and reduces the operational workload of managing data pipelines. + +DynamoDB users gain access to advanced OpenSearch features like full-text search, fuzzy search, auto-complete, and vector search for machine learning capabilities. Amazon OpenSearch Ingestion synchronizes data between DynamoDB and OpenSearch Service, enabling near-instant updates and comprehensive insights across multiple DynamoDB tables. Developers can adjust index mapping templates to match Amazon DynamoDB fields with OpenSearch Service indexes. + +Amazon OpenSearch Ingestion, combined with S3 exports and DynamoDB streams, facilitates seamless data input from DynamoDB tables and automatic ingestion into OpenSearch. Additionally, the pipeline can back up data to S3 for potential future re-ingestion as needed. + +## Step 1: Setup the project + +Begin by setting up your project by following the instructions in the [Quickstart guide](/[platform]/start/quickstart/). For the purpose of this guide, we'll sync a Todo table from DynamoDB to OpenSearch. + +Firstly, add the Todo model to your schema: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + // highlight-start + Todo: a + .model({ + content: a.string(), + done: a.boolean(), + priority: a.enum(["low", "medium", "high"]), + }) + .authorization((allow) => [allow.publicApiKey()]) + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); + +``` + + +Important considerations: + +Ensure Point in Time Recovery (PITR) is enabled, which is crucial for the pipeline integration. +Enable DynamoDB streams to capture item changes that will be ingested into OpenSearch. + + + +```ts title="amplify/backend.ts" +// highlight-start +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +// highlight-end +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +import { data } from './data/resource'; + +const backend = defineBackend({ + auth, + data +}); + +// highlight-start +const todoTable = + backend.data.resources.cfnResources.amplifyDynamoDbTables['Todo']; + +// Update table settings +todoTable.pointInTimeRecoveryEnabled = true; + +todoTable.streamSpecification = { + streamViewType: dynamodb.StreamViewType.NEW_IMAGE +}; + +// Get the DynamoDB table ARN +const tableArn = backend.data.resources.tables['Todo'].tableArn; +// Get the DynamoDB table name +const tableName = backend.data.resources.tables['Todo'].tableName; +// highlight-end +``` + +## Step 2: Setting Up the OpenSearch Instance + +Create an OpenSearch instance with encryption. + +```ts title="amplify/backend.ts" +// highlight-start +import * as opensearch from 'aws-cdk-lib/aws-opensearchservice'; +import { RemovalPolicy } from "aws-cdk-lib"; +// highlight-end +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +import { data } from './data/resource'; + +const backend = defineBackend({ + auth, + data +}); + +const todoTable = + backend.data.resources.cfnResources.amplifyDynamoDbTables['Todo']; + +// Update table settings +todoTable.pointInTimeRecoveryEnabled = true; + +todoTable.streamSpecification = { + streamViewType: dynamodb.StreamViewType.NEW_IMAGE +}; + +// Get the DynamoDB table ARN +const tableArn = backend.data.resources.tables['Todo'].tableArn; +// Get the DynamoDB table name +const tableName = backend.data.resources.tables['Todo'].tableName; + +// highlight-start + +// Create the OpenSearch domain +const openSearchDomain = new opensearch.Domain( + backend.data.stack, + 'OpenSearchDomain', + { + version: opensearch.EngineVersion.OPENSEARCH_2_11, + capacity: { + // upgrade instance types for production use + masterNodeInstanceType: "t3.small.search", + masterNodes: 0, + dataNodeInstanceType: "t3.small.search", + dataNodes: 1, + }, + nodeToNodeEncryption: true, + // set removalPolicy to DESTROY to make sure the OpenSearch domain is deleted on stack deletion. + removalPolicy: RemovalPolicy.DESTROY, + encryptionAtRest: { + enabled: true + } + } +); +// highlight-end +``` +> **Warning:** **Important considerations:** +> +> We recommend configuring the `removalPolicy` to destroy resources for sandbox environments. By default, OpenSearch instances are not deleted when you run `npx ampx sandbox delete`, as the default removal policy for stateful resources is set to retain the resource. + +## Step 3: Setting Up Zero ETL from DynamoDB to OpenSearch + +### Step 3a: Setup Storage and IAM Role + +Establish Storage to back up raw events consumed by the OpenSearch pipeline. +Generate a file named `amplify/storage/resource.ts` and insert the provided content to set up a storage resource. Tailor your storage configurations to regulate access to different paths within your storage bucket. + +```ts title="amplify/storage/resource.ts" +import { defineStorage } from "@aws-amplify/backend" + +export const storage = defineStorage({ + name: "opensearch-backup-bucket-amplify-gen-2", + access: allow => ({ + 'public/*': [ + allow.guest.to(['list', 'write', 'get']) + ] + }) +}) +``` + +Get the `s3BucketArn` and `s3BucketName` values from storage resource as shown below. Additionally, configure an IAM role for the pipeline and assign the roles as indicated below. For further information on the required IAM roles, please refer to the [Setting up roles and users](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/pipeline-security-overview.html#pipeline-security-create) documentation. + +```ts title="amplify/backend.ts" +import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; +import * as opensearch from "aws-cdk-lib/aws-opensearchservice"; +import { RemovalPolicy } from "aws-cdk-lib"; +// highlight-next-line +import * as iam from "aws-cdk-lib/aws-iam"; +import { defineBackend } from "@aws-amplify/backend"; +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; +// highlight-next-line +import { storage } from "./storage/resource"; + +// Define backend resources +const backend = defineBackend({ + auth, + data, + //highlight-start + storage, + //highlight-end +}); + +const todoTable = + backend.data.resources.cfnResources.amplifyDynamoDbTables["Todo"]; + +// Update table settings +todoTable.pointInTimeRecoveryEnabled = true; + +todoTable.streamSpecification = { + streamViewType: dynamodb.StreamViewType.NEW_IMAGE, +}; + +// Get the DynamoDB table ARN +const tableArn = backend.data.resources.tables["Todo"].tableArn; +// Get the DynamoDB table name +const tableName = backend.data.resources.tables["Todo"].tableName; + +// Create the OpenSearch domain +const openSearchDomain = new opensearch.Domain( + backend.data.stack, + "OpenSearchDomain", + { + version: opensearch.EngineVersion.OPENSEARCH_2_11, + capacity: { + // upgrade instance types for production use + masterNodeInstanceType: "t3.small.search", + masterNodes: 0, + dataNodeInstanceType: "t3.small.search", + dataNodes: 1, + }, + nodeToNodeEncryption: true, + // set removalPolicy to DESTROY to make sure the OpenSearch domain is deleted on stack deletion. + removalPolicy: RemovalPolicy.DESTROY, + encryptionAtRest: { + enabled: true, + }, + } +); +// highlight-start +// Get the S3Bucket ARN +const s3BucketArn = backend.storage.resources.bucket.bucketArn; +// Get the S3Bucket Name +const s3BucketName = backend.storage.resources.bucket.bucketName; + +// Create an IAM role for OpenSearch integration +const openSearchIntegrationPipelineRole = new iam.Role( + backend.data.stack, + "OpenSearchIntegrationPipelineRole", + { + assumedBy: new iam.ServicePrincipal("osis-pipelines.amazonaws.com"), + inlinePolicies: { + openSearchPipelinePolicy: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + actions: ["es:DescribeDomain"], + resources: [ + openSearchDomain.domainArn, + openSearchDomain.domainArn + "/*", + ], + effect: iam.Effect.ALLOW, + }), + new iam.PolicyStatement({ + actions: ["es:ESHttp*"], + resources: [ + openSearchDomain.domainArn, + openSearchDomain.domainArn + "/*", + ], + effect: iam.Effect.ALLOW, + }), + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "s3:GetObject", + "s3:AbortMultipartUpload", + "s3:PutObject", + "s3:PutObjectAcl", + ], + resources: [s3BucketArn, s3BucketArn + "/*"], + }), + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "dynamodb:DescribeTable", + "dynamodb:DescribeContinuousBackups", + "dynamodb:ExportTableToPointInTime", + "dynamodb:DescribeExport", + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + ], + resources: [tableArn, tableArn + "/*"], + }), + ], + }), + }, + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName( + "AmazonOpenSearchIngestionFullAccess" + ), + ], + } +); +// highlight-end +``` + +For the S3 bucket, follow standard security practices: block public access, encrypt data at rest, and enable versioning. + +The IAM role should allow the OpenSearch Ingestion Service (OSIS) pipelines to assume it. Grant specific OpenSearch Service permissions and also provide DynamoDB and S3 access. You may customize permissions to follow the principle of least privilege. + +### Step 3b: OpenSearch Service Pipeline + +Define the pipeline construct and its configuration. + +When using OpenSearch, you can define the index template or mapping in advance based on your data structure, which allows you to set data types for each field in the document. This approach can be incredibly powerful for precise data ingestion and search. For more information on index mapping/templates, please refer to [OpenSearch documentation](https://opensearch.org/docs/latest/im-plugin/index-templates/). + +Customize the `template_content` JSON-representation to define the data structure for the ingestion pipeline. + +```ts title="amplify/backend.ts" +import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; +import * as opensearch from "aws-cdk-lib/aws-opensearchservice"; +import { RemovalPolicy } from "aws-cdk-lib"; +import * as iam from "aws-cdk-lib/aws-iam"; +import { defineBackend } from "@aws-amplify/backend"; +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; +import { storage } from "./storage/resource"; + +// Define backend resources +const backend = defineBackend({ + auth, + data, + storage, +}); + +const todoTable = + backend.data.resources.cfnResources.amplifyDynamoDbTables["Todo"]; + +// Update table settings +todoTable.pointInTimeRecoveryEnabled = true; + +todoTable.streamSpecification = { + streamViewType: dynamodb.StreamViewType.NEW_IMAGE, +}; + +// Get the DynamoDB table ARN +const tableArn = backend.data.resources.tables["Todo"].tableArn; +// Get the DynamoDB table name +const tableName = backend.data.resources.tables["Todo"].tableName; + +// Create the OpenSearch domain +const openSearchDomain = new opensearch.Domain( + backend.data.stack, + "OpenSearchDomain", + { + version: opensearch.EngineVersion.OPENSEARCH_2_11, + capacity: { + // upgrade instance types for production use + masterNodeInstanceType: "t3.small.search", + masterNodes: 0, + dataNodeInstanceType: "t3.small.search", + dataNodes: 1, + }, + nodeToNodeEncryption: true, + // set removalPolicy to DESTROY to make sure the OpenSearch domain is deleted on stack deletion. + removalPolicy: RemovalPolicy.DESTROY, + encryptionAtRest: { + enabled: true, + }, + } +); + +// Get the S3Bucket ARN +const s3BucketArn = backend.storage.resources.bucket.bucketArn; +// Get the S3Bucket Name +const s3BucketName = backend.storage.resources.bucket.bucketName; + +// Create an IAM role for OpenSearch integration +const openSearchIntegrationPipelineRole = new iam.Role( + backend.data.stack, + "OpenSearchIntegrationPipelineRole", + { + assumedBy: new iam.ServicePrincipal("osis-pipelines.amazonaws.com"), + inlinePolicies: { + openSearchPipelinePolicy: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + actions: ["es:DescribeDomain"], + resources: [ + openSearchDomain.domainArn, + openSearchDomain.domainArn + "/*", + ], + effect: iam.Effect.ALLOW, + }), + new iam.PolicyStatement({ + actions: ["es:ESHttp*"], + resources: [ + openSearchDomain.domainArn, + openSearchDomain.domainArn + "/*", + ], + effect: iam.Effect.ALLOW, + }), + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "s3:GetObject", + "s3:AbortMultipartUpload", + "s3:PutObject", + "s3:PutObjectAcl", + ], + resources: [s3BucketArn, s3BucketArn + "/*"], + }), + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "dynamodb:DescribeTable", + "dynamodb:DescribeContinuousBackups", + "dynamodb:ExportTableToPointInTime", + "dynamodb:DescribeExport", + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + ], + resources: [tableArn, tableArn + "/*"], + }), + ], + }), + }, + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName( + "AmazonOpenSearchIngestionFullAccess" + ), + ], + } +); + +// highlight-start +// Define OpenSearch index mappings +const indexName = "todo"; + +const indexMapping = { + settings: { + number_of_shards: 1, + number_of_replicas: 0, + }, + mappings: { + properties: { + id: { + type: "keyword", + }, + done: { + type: "boolean", + }, + content: { + type: "text", + }, + }, + }, +}; +// highlight-end +``` + +The configuration is a data-prepper feature of OpenSearch. For specific documentation on DynamoDB configuration, refer to [OpenSearch data-prepper documentation](https://opensearch.org/docs/latest/data-prepper/pipelines/configuration/sources/dynamo-db/). + +```ts title="amplify/backend.ts" +import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; +import * as opensearch from "aws-cdk-lib/aws-opensearchservice"; +import { RemovalPolicy } from "aws-cdk-lib"; +import * as iam from "aws-cdk-lib/aws-iam"; +import { defineBackend } from "@aws-amplify/backend"; +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; +import { storage } from "./storage/resource"; + +// Define backend resources +const backend = defineBackend({ + auth, + data, + storage, +}); + +const todoTable = + backend.data.resources.cfnResources.amplifyDynamoDbTables["Todo"]; + +// Update table settings +todoTable.pointInTimeRecoveryEnabled = true; + +todoTable.streamSpecification = { + streamViewType: dynamodb.StreamViewType.NEW_IMAGE, +}; + +// Get the DynamoDB table ARN +const tableArn = backend.data.resources.tables["Todo"].tableArn; +// Get the DynamoDB table name +const tableName = backend.data.resources.tables["Todo"].tableName; + +// Create the OpenSearch domain +const openSearchDomain = new opensearch.Domain( + backend.data.stack, + "OpenSearchDomain", + { + version: opensearch.EngineVersion.OPENSEARCH_2_11, + capacity: { + // upgrade instance types for production use + masterNodeInstanceType: "t3.small.search", + masterNodes: 0, + dataNodeInstanceType: "t3.small.search", + dataNodes: 1, + }, + nodeToNodeEncryption: true, + // set removalPolicy to DESTROY to make sure the OpenSearch domain is deleted on stack deletion. + removalPolicy: RemovalPolicy.DESTROY, + encryptionAtRest: { + enabled: true, + }, + } +); + +// Get the S3Bucket ARN +const s3BucketArn = backend.storage.resources.bucket.bucketArn; +// Get the S3Bucket Name +const s3BucketName = backend.storage.resources.bucket.bucketName; + +// Create an IAM role for OpenSearch integration +const openSearchIntegrationPipelineRole = new iam.Role( + backend.data.stack, + "OpenSearchIntegrationPipelineRole", + { + assumedBy: new iam.ServicePrincipal("osis-pipelines.amazonaws.com"), + inlinePolicies: { + openSearchPipelinePolicy: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + actions: ["es:DescribeDomain"], + resources: [ + openSearchDomain.domainArn, + openSearchDomain.domainArn + "/*", + ], + effect: iam.Effect.ALLOW, + }), + new iam.PolicyStatement({ + actions: ["es:ESHttp*"], + resources: [ + openSearchDomain.domainArn, + openSearchDomain.domainArn + "/*", + ], + effect: iam.Effect.ALLOW, + }), + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "s3:GetObject", + "s3:AbortMultipartUpload", + "s3:PutObject", + "s3:PutObjectAcl", + ], + resources: [s3BucketArn, s3BucketArn + "/*"], + }), + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "dynamodb:DescribeTable", + "dynamodb:DescribeContinuousBackups", + "dynamodb:ExportTableToPointInTime", + "dynamodb:DescribeExport", + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + ], + resources: [tableArn, tableArn + "/*"], + }), + ], + }), + }, + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName( + "AmazonOpenSearchIngestionFullAccess" + ), + ], + } +); + +// Define OpenSearch index mappings +const indexName = "todo"; + +const indexMapping = { + settings: { + number_of_shards: 1, + number_of_replicas: 0, + }, + mappings: { + properties: { + id: { + type: "keyword", + }, + isDone: { + type: "boolean", + }, + content: { + type: "text", + }, + priority: { + type: "text", + }, + }, + }, +}; + +// highlight-start + +// OpenSearch template definition +const openSearchTemplate = ` +version: "2" +dynamodb-pipeline: + source: + dynamodb: + acknowledgments: true + tables: + - table_arn: "${tableArn}" + stream: + start_position: "LATEST" + export: + s3_bucket: "${s3BucketName}" + s3_region: "${backend.storage.stack.region}" + s3_prefix: "${tableName}/" + aws: + sts_role_arn: "${openSearchIntegrationPipelineRole.roleArn}" + region: "${backend.data.stack.region}" + sink: + - opensearch: + hosts: + - "https://${openSearchDomain.domainEndpoint}" + index: "${indexName}" + index_type: "custom" + template_content: | + ${JSON.stringify(indexMapping)} + document_id: '\${getMetadata("primary_key")}' + action: '\${getMetadata("opensearch_action")}' + document_version: '\${getMetadata("document_version")}' + document_version_type: "external" + bulk_size: 4 + aws: + sts_role_arn: "${openSearchIntegrationPipelineRole.roleArn}" + region: "${backend.data.stack.region}" +`; +// highlight-end +``` + +This configuration defines the desired behavior of the pipeline for a single model. + +In the source configuration, DynamoDB is specified as the data source, along with the target table for ingestion and the starting point of the stream. Additionally, besides ingesting the stream into OpenSearch, a target S3 bucket is defined for backup purposes. Furthermore, an IAM role is set for the ingestion pipeline, ensuring it possesses the necessary permissions and policies as detailed in the documentation. + +Regarding the sink configuration, the OpenSearch domain cluster is specified by setting the host, index name, type, and template content (index mapping) for data formatting. Document-related metadata is configured along with the maximum bulk size for requests to OpenSearch in MB. Once again, an IAM role is specified for the sink portion of the pipeline. For further details on Sink configuration, please refer to the [OpenSearch documentation](https://opensearch.org/docs/latest/data-prepper/pipelines/configuration/sinks/sinks/). + +The sink configuration is an array. To create a different index on the same table, you can achieve this by adding a second OpenSearch configuration to the sink array. + +To index multiple tables, you'll need to configure multiple pipelines in the configuration. For further guidance, please consult the [pipeline section](https://opensearch.org/docs/latest/data-prepper/pipelines/pipelines/) of the OpenSearch documentation. + + + +**Note**: An OpenSearch Ingestion pipeline supports only one DynamoDB table as its source. For more details on current limitations, Please refer to [Amazon OpenSearch Limitation](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/configure-client-ddb.html#ddb-pipeline-limitations) section. + + + +Now, create the OSIS pipeline resource: + +```ts title="amplify/backend.ts" +import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; +import * as opensearch from "aws-cdk-lib/aws-opensearchservice"; +import { RemovalPolicy } from "aws-cdk-lib"; +import * as iam from "aws-cdk-lib/aws-iam"; +// highlight-start +import * as osis from "aws-cdk-lib/aws-osis"; +import * as logs from "aws-cdk-lib/aws-logs"; +import { RemovalPolicy } from "aws-cdk-lib"; +// highlight-end +import { defineBackend } from "@aws-amplify/backend"; +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; +import { storage } from "./storage/resource"; + +// Define backend resources +const backend = defineBackend({ + auth, + data, + storage, +}); + +const todoTable = + backend.data.resources.cfnResources.amplifyDynamoDbTables["Todo"]; + +// Update table settings +todoTable.pointInTimeRecoveryEnabled = true; + +todoTable.streamSpecification = { + streamViewType: dynamodb.StreamViewType.NEW_IMAGE, +}; + +// Get the DynamoDB table ARN +const tableArn = backend.data.resources.tables["Todo"].tableArn; +// Get the DynamoDB table name +const tableName = backend.data.resources.tables["Todo"].tableName; + +// Create the OpenSearch domain +const openSearchDomain = new opensearch.Domain( + backend.data.stack, + "OpenSearchDomain", + { + version: opensearch.EngineVersion.OPENSEARCH_2_11, + capacity: { + // upgrade instance types for production use + masterNodeInstanceType: "t3.small.search", + masterNodes: 0, + dataNodeInstanceType: "t3.small.search", + dataNodes: 1, + }, + nodeToNodeEncryption: true, + // set removalPolicy to DESTROY to make sure the OpenSearch domain is deleted on stack deletion. + removalPolicy: RemovalPolicy.DESTROY, + encryptionAtRest: { + enabled: true, + }, + } +); + +// Get the S3Bucket ARN +const s3BucketArn = backend.storage.resources.bucket.bucketArn; +// Get the S3Bucket Name +const s3BucketName = backend.storage.resources.bucket.bucketName; + +// Create an IAM role for OpenSearch integration +const openSearchIntegrationPipelineRole = new iam.Role( + backend.data.stack, + "OpenSearchIntegrationPipelineRole", + { + assumedBy: new iam.ServicePrincipal("osis-pipelines.amazonaws.com"), + inlinePolicies: { + openSearchPipelinePolicy: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + actions: ["es:DescribeDomain"], + resources: [ + openSearchDomain.domainArn, + openSearchDomain.domainArn + "/*", + ], + effect: iam.Effect.ALLOW, + }), + new iam.PolicyStatement({ + actions: ["es:ESHttp*"], + resources: [ + openSearchDomain.domainArn, + openSearchDomain.domainArn + "/*", + ], + effect: iam.Effect.ALLOW, + }), + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "s3:GetObject", + "s3:AbortMultipartUpload", + "s3:PutObject", + "s3:PutObjectAcl", + ], + resources: [s3BucketArn, s3BucketArn + "/*"], + }), + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "dynamodb:DescribeTable", + "dynamodb:DescribeContinuousBackups", + "dynamodb:ExportTableToPointInTime", + "dynamodb:DescribeExport", + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + ], + resources: [tableArn, tableArn + "/*"], + }), + ], + }), + }, + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName( + "AmazonOpenSearchIngestionFullAccess" + ), + ], + } +); + +// Define OpenSearch index mappings +const indexName = "todo"; + +const indexMapping = { + settings: { + number_of_shards: 1, + number_of_replicas: 0, + }, + mappings: { + properties: { + id: { + type: "keyword", + }, + isDone: { + type: "boolean", + }, + content: { + type: "text", + }, + priority: { + type: "text", + }, + }, + }, +}; + +// OpenSearch template definition +const openSearchTemplate = ` +version: "2" +dynamodb-pipeline: + source: + dynamodb: + acknowledgments: true + tables: + - table_arn: "${tableArn}" + stream: + start_position: "LATEST" + export: + s3_bucket: "${s3BucketName}" + s3_region: "${backend.storage.stack.region}" + s3_prefix: "${tableName}/" + aws: + sts_role_arn: "${openSearchIntegrationPipelineRole.roleArn}" + region: "${backend.data.stack.region}" + sink: + - opensearch: + hosts: + - "https://${openSearchDomain.domainEndpoint}" + index: "${indexName}" + index_type: "custom" + template_content: | + ${JSON.stringify(indexMapping)} + document_id: '\${getMetadata("primary_key")}' + action: '\${getMetadata("opensearch_action")}' + document_version: '\${getMetadata("document_version")}' + document_version_type: "external" + bulk_size: 4 + aws: + sts_role_arn: "${openSearchIntegrationPipelineRole.roleArn}" + region: "${backend.data.stack.region}" +`; + +// highlight-start +// Create a CloudWatch log group +const logGroup = new logs.LogGroup(backend.data.stack, "LogGroup", { + logGroupName: "/aws/vendedlogs/OpenSearchService/pipelines/1", + removalPolicy: RemovalPolicy.DESTROY, +}); + +// Create an OpenSearch Integration Service pipeline +const cfnPipeline = new osis.CfnPipeline( + backend.data.stack, + "OpenSearchIntegrationPipeline", + { + maxUnits: 4, + minUnits: 1, + pipelineConfigurationBody: openSearchTemplate, + pipelineName: "dynamodb-integration-2", + logPublishingOptions: { + isLoggingEnabled: true, + cloudWatchLogDestination: { + logGroup: logGroup.logGroupName, + }, + }, + } +); +//highlight-end +``` + +After deploying the resources, you can test the data ingestion process by adding an item to the `Todo` table. However, before doing that, let's verify that the pipeline has been set up correctly. + +In the AWS console, navigate to OpenSearch and then to the pipelines section. You should find your configured pipeline and review its settings to ensure they match your expectations: + +![A screenshot displaying the OpenSearch OSIS pipeline created under the DynamoDB integrations section](/images/gen2/opensearch-integration/OpenSearch_pipeline.png) + +You can also check this in the DynamoDB console by going to the Integrations section of the tables. + +![A screenshot displaying the OpenSearch OSIS pipeline created within the 'Ingestion -> Pipelines' section of the OpenSearch Console.](/images/gen2/opensearch-integration/OpenSearch_DynamoDB_integration.png) + +## Step 4: Expose new queries on OpenSearch + +### Step 4a: Add OpenSearch Datasource to backend + +First, Add the OpenSearch data source to the data backend. Add the following code to the end of the `amplify/backend.ts` file. + +```ts title="amplify/backend.ts" +// Add OpenSearch data source +const osDataSource = backend.data.addOpenSearchDataSource( + "osDataSource", + openSearchDomain +); + +``` +### Step 4b: Create Resolver and attach to query + +Let's create the search resolver. Create a new file named `amplify/data/searchTodoResolver.js` and paste the following code. For additional details please refer to [Amazon OpenSearch Service Resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-elasticsearch-resolvers-js.html) + +```ts title="amplify/data/searchTodoResolver.js" +import { util } from "@aws-appsync/utils"; + +/** + * Searches for documents by using an input term + * @param {import('@aws-appsync/utils').Context} ctx the context + * @returns {*} the request + */ +export function request(ctx) { + return { + operation: "GET", + path: "/todo/_search", + }; +} + +/** + * Returns the fetched items + * @param {import('@aws-appsync/utils').Context} ctx the context + * @returns {*} the result + */ +export function response(ctx) { + if (ctx.error) { + util.error(ctx.error.message, ctx.error.type); + } + return ctx.result.hits.hits.map((hit) => hit._source); +} +``` + +### Step 4c: Add the AppSync Resolver for the Search Query + +Update the schema and add a searchTodo query. + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + done: a.boolean(), + priority: a.enum(["low", "medium", "high"]), + }) + .authorization((allow) => [allow.publicApiKey()]), + + //highlight-start + searchTodos: a + .query() + .returns(a.ref("Todo").array()) + .authorization((allow) => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + entry: "./searchTodoResolver.js", + dataSource: "osDataSource", + }) + ), + //highlight-end + +}); +``` + +Once you've deployed the resources, you can verify the changes by checking the AppSync console. Run the 'searchTodo' query and review the results to confirm their accuracy. + +![AppSync console displaying a generated query for 'searchTodo' with the results fetched from OpenSearch on the right side.](/images/gen2/opensearch-integration/opensearch_appsync_console.png) + +--- + +--- +title: "Connect to Amazon EventBridge to send and receive events" +section: "build-a-backend/data/custom-business-logic" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-08T01:04:46.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-eventbridge-datasource/" +--- + +Amazon EventBridge is a serverless event bus that simplifies how applications communicate with each other. It acts as a central hub for events generated by various sources, including AWS services, custom applications, and third-party SaaS providers. + +EventBridge delivers this event data in real-time, allowing you to build applications that react swiftly to changes. You define rules to filter and route these events to specific destinations, known as targets. Targets can include services like AWS Lambda, Amazon SQS Queues, Amazon SNS Topics. For the purpose of this guide, we will use AWS AppSync as the target for events. + +By adopting an event-driven architecture with EventBridge, you can achieve: + +- **Loose Coupling**: Applications become independent and communicate through events, improving scalability and maintainability. + +- **Increased Resilience**: System failures are isolated as events are delivered asynchronously, ensuring overall application availability. + +- **Simplified Integration**: EventBridge provides a unified interface for integrating diverse event sources, streamlining development. + +This section will guide you through adding an event bus as a datasource to your API, defining routing rules, and configuring targets to build robust event-driven applications with AWS Amplify Gen 2 and Amazon EventBridge. + +1. Set up your API +2. Add your Amazon EventBridge event bus as a data source +3. Define custom queries and mutations +4. Configure custom business logic handler code +5. Invoke custom mutations to send events to EventBridge +6. Subscribe to mutations invoked by EventBridge +7. Invoke mutations and trigger subscriptions from EventBridge + +## Step 1 - Set up your API + +For the purpose of this guide, we will define an `OrderStatusChange` custom type that represents an order status change event. This type includes fields for the order ID, status, and message. + +In your `amplify/data/resource.ts` file, use the following code to define an `OrderStatusChange` custom type and an `OrderStatus` enum, adding them to your schema: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + .authorization(allow => [allow.publicApiKey()]), + // highlight-start + OrderStatus: a.enum(["OrderPending", "OrderShipped", "OrderDelivered"]), + OrderStatusChange: a.customType({ + orderId: a.id().required(), + status: a.ref("OrderStatus").required(), + message: a.string().required(), + }), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +> **Info:** **NOTE:** At least one query is required for a schema to be valid. Otherwise, deployments will fail a schema error. The Amplify Data schema is auto-generated with a `Todo` model and corresponding queries under the hood. You can leave the `Todo` model in the schema until you add the first custom query to the schema in the next steps. + +## Step 2 - Add your Amazon EventBridge event bus as a data source + +In your `amplify/backend.ts` file, use the following code to add the default event bus as a data source for your API: + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend"; +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; +import { aws_events } from "aws-cdk-lib"; +import { + Effect, + PolicyDocument, + PolicyStatement, + Role, + ServicePrincipal, +} from "aws-cdk-lib/aws-iam"; + +export const backend = defineBackend({ + auth, + data, +}); + +// Create a new stack for the EventBridge data source +const eventStack = backend.createStack("MyExternalDataSources"); + +// Reference or create an EventBridge EventBus +const eventBus = aws_events.EventBus.fromEventBusName( + eventStack, + "MyEventBus", + "default" +); + +// Add the EventBridge data source +// highlight-start +backend.data.addEventBridgeDataSource("MyEventBridgeDataSource", eventBus); +// highlight-end + +// Create a policy statement to allow invoking the AppSync API's mutations +const policyStatement = new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["appsync:GraphQL"], + resources: [`${backend.data.resources.graphqlApi.arn}/types/Mutation/*`], +}); + +// Create a role for the EventBus to assume +const eventBusRole = new Role(eventStack, "AppSyncInvokeRole", { + assumedBy: new ServicePrincipal("events.amazonaws.com"), + inlinePolicies: { + PolicyStatement: new PolicyDocument({ + statements: [policyStatement], + }), + }, +}); + +// Create an EventBridge rule to route events to the AppSync API +const rule = new aws_events.CfnRule(eventStack, "MyOrderRule", { + eventBusName: eventBus.eventBusName, + name: "broadcastOrderStatusChange", + eventPattern: { + source: ["amplify.orders"], + /* The shape of the event pattern must match EventBridge's event message structure. + So, this field must be spelled as "detail-type". Otherwise, events will not trigger the rule. + + https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html + */ + ["detail-type"]: ["OrderStatusChange"], + detail: { + orderId: [{ exists: true }], + status: ["PENDING", "SHIPPED", "DELIVERED"], + message: [{ exists: true }], + }, + }, + targets: [ + { + id: "orderStatusChangeReceiver", + arn: backend.data.resources.cfnResources.cfnGraphqlApi + .attrGraphQlEndpointArn, + roleArn: eventBusRole.roleArn, + appSyncParameters: { + graphQlOperation: ` + mutation PublishOrderFromEventBridge( + $orderId: String! + $status: String! + $message: String! + ) { + publishOrderFromEventBridge(orderId: $orderId, status: $status, message: $message) { + orderId + status + message + } + }`, + }, + inputTransformer: { + inputPathsMap: { + orderId: "$.detail.orderId", + status: "$.detail.status", + message: "$.detail.message", + }, + inputTemplate: JSON.stringify({ + orderId: "", + status: "", + message: "", + }), + }, + }, + ], +}); +``` + +> **Warning:** The selection set returned by the mutation must match the selection set of the subscription. If the selection set of the mutation is different from the selection set of the subscription, the subscription will not receive the event. + +In the code snippet above, the `addEventBridgeDataSource` method is used to add the default event bus as a data source to your API. This allows you to reference the event bus in your custom queries and mutations. + +The `CfnRule` construct is used to create an EventBridge rule that routes events to the AppSync API. The rule specifies the event pattern to match and the target to invoke when the event is received. In this example, the target is an AppSync mutation named `publishOrderFromEventBridge`. + +The `appSyncParameters` property specifies the mutation to invoke when the event is received. The `inputTransformer` property maps the event data to the mutation arguments. + +## Step 3 - Define custom queries and mutations + +Now that your event bus has been added as a data source, you can reference it in custom queries and mutations using the `a.handler.custom()` modifier which accepts the name of the data source and an entry point for your resolver. + +Use the following code to add `publishOrderToEventBridge` and `publishOrderFromEventBridge` custom mutations, and an `onOrderStatusChange` custom subscription to your schema: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + // ... + OrderStatus: a.enum(["OrderPending", "OrderShipped", "OrderDelivered"]), + OrderStatusChange: a.customType({ + orderId: a.id().required(), + status: a.ref("OrderStatus").required(), + message: a.string().required(), + }), + // highlight-start + publishOrderToEventBridge: a + .mutation() + .arguments({ + orderId: a.id().required(), + status: a.string().required(), + message: a.string().required(), + }) + .returns(a.ref("OrderStatusChange")) + .authorization((allow) => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + dataSource: "EventBridgeDataSource", + entry: "./publishOrderToEventBridge.js", + }) + ), + publishOrderFromEventBridge: a + .mutation() + .arguments({ + orderId: a.id().required(), + status: a.string().required(), + message: a.string().required(), + }) + .returns(a.ref("OrderStatusChange")) + .authorization((allow) => [allow.publicApiKey(), allow.guest()]) + .handler( + a.handler.custom({ + entry: "./publishOrderFromEventBridge.js", + }) + ), + onOrderFromEventBridge: a + .subscription() + .for(a.ref("publishOrderFromEventBridge")) + .authorization((allow) => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + entry: "./onOrderFromEventBridge.js", + }) + ), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + name: "MyLibrary", + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +In the code snippet above: + +- The `publishOrderToEventBridge` custom mutation uses an EventBridge data source and so it is able to publish events to the event bus from its resolver. + +- The `publishOrderFromEventBridge` custom mutation uses a None data source as a passthrough and is invoked by the EventBridge rule when an event is received that matches the rule pattern. The `allow.guest` rule uses IAM under the hood and allows the mutation to be invoked by the EventBridge rule. + +- The `onOrderFromEventBridge` custom subscription can be triggered either by EventBridge invoking the `publishOrderFromEventBridge` mutation or by a client invoking the `publishOrderToEventBridge` mutation. + +## Step 4 - Configure custom business logic handler code + +Next, create the following files in your `amplify/data` folder and use the code examples to define custom resolvers for the custom queries and mutations added to your schema from the previous step. These are AppSync JavaScript resolvers + +#### [Subscription] + +The following code defines the custom business logic handler for the `onOrderStatusChange` subscription. Since the subscription uses a None data source the `response` function is empty as the subscription does not require any additional processing. + +```js title="amplify/data/onOrderStatusChange.js" +export function request(ctx) { + return { + payload: {}, + }; +} + +export function response(ctx) { +} +``` + +#### [Publish Order to EventBridge] + +In the following code, the `request` function constructs the event payload to be published to the event bus. To match the rule pattern configured in the previous steps, the event source is set to `amplify.orders` and the `detail-type` is set to `OrderStatusChange`. The mutation arguments are passed to the event detail. + +```js title="amplify/data/publishOrderToEventBridge.js" +export function request(ctx) { + return { + operation: "PutEvents", + events: [ + { + source: "amplify.orders", + ["detail-type"]: "OrderStatusChange", + detail: { ...ctx.args }, + }, + ], + }; +} + +export function response(ctx) { + return ctx.args; +} +``` + +#### [Publish Order From EventBridge] + +The following code defines the custom business logic handler for the `publishOrderFromEventBridge` mutation. The `request` function constructs the mutation arguments from the event payload received from the event bus. The `response` function returns the mutation arguments. + +```js title="amplify/data/publishOrderFromEventBridge.js" +export function request(ctx) { + return { + payload: ctx.arguments, + }; +} + +export function response(ctx) { + return ctx.arguments; +} +``` + +## Step 5 - Invoke custom mutations to send events to EventBridge + +From your generated Data client, you can find all your custom queries and mutations under the `client.queries` and `client.mutations` APIs respectively. + +The custom mutation below will publish an order status change event to the event bus: + +```ts title="App.tsx" +await client.mutations.publishOrderToEventBridge({ + orderId: "12345", + status: "SHIPPED", + message: "Order has been shipped", +}); +``` + +## Step 6 - Subscribe to mutations invoked by EventBridge + +To subscribe to events from your event bus, you can use the `client.subscriptions` API: + +```ts title="App.tsx" +// Subscribe to the mutations triggered by the EventBridge rule +const sub = client.subscriptions.onOrderStatusChange().subscribe({ + next: (data) => { + console.log(data); + }, +}); + +//... + +// Clean up subscription +sub.unsubscribe(); +``` + +## Step 7 - Invoke a mutation and trigger a subscription from EventBridge + +You can test your custom mutation and subscriptions by using the EventBridge console to send an event which will invoke the custom mutation. You can then observe the results from the subscription being triggered: + +1. Navigate to the Amazon EventBridge console and choose "Send Events" + +![Amazon EventBridge console, page titled β€œEvent buses”. Shows a table of event buses and a highlighted button labeled "Send events."](/images/send-events.png) + +2. Fill out the form, specifying the event source to be `amplify.orders` and the `detail-type` to be `OrderStatusChange`. + +![Amazon EventBridge console, page titled "Send events". Shows a form with input fields and values of "event bus: default", "event source: amplify.orders", "detail type: OrderStatusChange", and an Event detail field with JSON data containing an "orderId", "status", and "message". ](/images/send-events-2.png) + +3. Choose "Send" and observe the subscription output in the AppSync Queries console. + +![AppSync console, page titled "Queries". Shows a running subscription named "onOrderFromEventBridge" and a result with data containing an "orderId", "status", and "message." ](/images/send-events-3.png) + +## Conclusion + +In this guide, you’ve added an Amazon EventBridge event bus as a data source to an Amplify API and defined custom queries and mutations to publish and receive events from the event bus. You’ve also configured custom business logic handler code to handle the event data and invoke the appropriate mutations. + +To clean up, you can delete your sandbox by accepting the prompt when terminating the sandbox process in your terminal. Alternatively, you can also use the AWS Amplify console to manage and delete sandbox environments. + +--- + +--- +title: "Connect to Amazon Polly for Text-To-Speech APIs" +section: "build-a-backend/data/custom-business-logic" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-14T20:52:33.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-amazon-polly/" +--- + +Amazon Polly is a text-to-speech (TTS) service offered by Amazon Web Services (AWS). It uses advanced deep learning technologies to convert written text into lifelike speech, enabling you to create applications with speech capabilities in various languages and voices. + +With Amazon Polly, you can easily add voice interactions and accessibility features to your applications. The service supports a wide range of use cases, such as providing audio content for the visually impaired, enhancing e-learning experiences, creating interactive voice response (IVR) systems, and more. + +Key features of Amazon Polly include: + +- **Multiple Voices and Languages**: Amazon Polly supports dozens of voices across various languages and dialects, giving you the flexibility to choose the most appropriate voice for your use case. + +- **High-Quality Speech**: Amazon Polly's neural and standard voices offer natural and realistic speech quality. + +- **Speech Marks and Speech Synthesis Markup Language**: Customize your speech output with Speech Synthesis Markup Language tags and obtain speech timing information with speech marks. + +- **Scalable and Cost-Effective**: Amazon Polly's pay-as-you-go pricing model makes it a cost-effective solution for adding speech capabilities to your applications. + +In this section, you'll learn how to integrate Amazon Polly into your application using AWS Amplify, enabling you to leverage its powerful text-to-speech capabilities seamlessly. + +## Step 1 - Setup the project + +Set up your project by following the instructions in the [Quickstart guide](/[platform]/start/quickstart/). + +## Step 2 - Install Polly Libraries +We'll create a new API endpoint that'll use the the AWS SDK to call the Amazon Polly service. To install the Amazon Polly SDK, run the following command in your project's root folder: + +```bash title="Terminal" showLineNumbers={false} +npm add @aws-sdk/client-polly +``` + +## Step 3 - Setup Storage + +Create a file named `amplify/storage/resource.ts` and add the following content to configure a storage resource: + +```ts title="amplify/storage/resource.ts" +import { defineStorage } from '@aws-amplify/backend'; + +export const storage = defineStorage({ + name: 'predictions_gen2' +}); + +``` + +## Step 4 - Configure IAM Roles + + To access Amazon Polly service, you need to configure the proper IAM policy for Lambda to utilize the desired feature effectively. Update the `amplify/backend.ts` file with the following code to add the necessary permissions to a lambda's Role policy. + + ```ts title= "amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend"; +import { auth } from "./auth/resource"; +import { data, convertTextToSpeech } from "./data/resource"; +import { Stack } from "aws-cdk-lib"; +import { PolicyStatement } from "aws-cdk-lib/aws-iam"; +import { storage } from "./storage/resource"; + +const backend = defineBackend({ + auth, + data, + storage, + convertTextToSpeech, +}); + +backend.convertTextToSpeech.resources.lambda.addToRolePolicy( + new PolicyStatement({ + actions: ["polly:StartSpeechSynthesisTask"], + resources: ["*"], + }) +); +``` +## Step 5 - Configure the function handler + +Define the function handler by creating a new file, `amplify/data/convertTextToSpeech.ts`. This function converts text into speech using Amazon Polly and stores the synthesized speech as an MP3 file in an S3 bucket. + +```ts title="amplify/data/convertTextToSpeech.ts" +import { Schema } from "./resource"; +import { + PollyClient, + StartSpeechSynthesisTaskCommand, +} from "@aws-sdk/client-polly"; +import { env } from "$amplify/env/convertTextToSpeech"; + +export const handler: Schema["convertTextToSpeech"]["functionHandler"] = async ( + event +) => { + const client = new PollyClient(); + const task = new StartSpeechSynthesisTaskCommand({ + OutputFormat: "mp3", + SampleRate: "8000", + Text: event.arguments.text, + TextType: "text", + VoiceId: "Amy", + OutputS3BucketName: env.PREDICTIONS_GEN_2_BUCKET_NAME, + OutputS3KeyPrefix: "public/", + }); + const result = await client.send(task); + + return ( + result.SynthesisTask?.OutputUri?.replace( + "https://s3.us-east-1.amazonaws.com/" + + env.PREDICTIONS_GEN_2_BUCKET_NAME + + "/public/", + "" + ) ?? "" + ); +}; +``` + +## Step 6 - Define the custom mutation and function + +In your `amplify/data/resource.ts` file, define the function using defineFunction and then reference the function with your mutation using a.handler.function() as a handler. + +```ts title="amplify/data/resource.ts" +import { + type ClientSchema, + a, + defineData, + defineFunction, +} from "@aws-amplify/backend"; + +export const convertTextToSpeech = defineFunction({ + entry: "./convertTextToSpeech.ts", +}); + +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + .authorization(allow => [allow.publicApiKey()]), + convertTextToSpeech: a + .mutation() + .arguments({ + text: a.string().required(), + }) + .returns(a.string().required()) + .authorization(allow => [allow.publicApiKey()]) + .handler(a.handler.function(convertTextToSpeech)), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + // API Key is used for allow.publicApiKey() rules + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +> **Info:** **NOTE:** At least one query is required for a schema to be valid. Otherwise, deployments will fail a schema error. The Amplify Data schema is auto-generated with a `Todo` model and corresponding queries under the hood. You can leave the `Todo` model in the schema until you add the first custom query to the schema in the next steps. + +## Step 7 - Update Storage permissions + +Customize your storage settings to manage access to various paths within your storage bucket. It's necessary to update the Storage resource to provide access to the `convertTextToSpeech` resource. Modify the file `amplify/storage/resource.ts` as shown below. + +```ts title="amplify/storage/resource.ts" +import { defineStorage } from "@aws-amplify/backend"; +import { convertTextToSpeech } from "../data/resource"; + +export const storage = defineStorage({ + name: "predictions_gen2", + access: (allow) => ({ + "public/*": [ + allow.resource(convertTextToSpeech).to(["write"]), + allow.guest.to(["read", "write"]), + ], + }), +}); +``` + +## Step 8 - Configure the frontend + +Import and load the configuration file in your app. It's recommended you add the Amplify configuration step to your app's root entry point. +``` ts title="main.tsx" +import { Amplify } from "aws-amplify"; +import outputs from "../amplify_outputs.json"; + +Amplify.configure(outputs); +``` +### Invoke the API + +Example frontend code to create an audio buffer for playback using a text input. + + +```ts title="App.tsx" +import "./App.css"; +import { generateClient } from "aws-amplify/api"; +import type { Schema } from "../amplify/data/resource"; +import { getUrl } from "aws-amplify/storage"; +import { useState } from "react"; + +const client = generateClient(); + +type PollyReturnType = Schema["convertTextToSpeech"]["returnType"]; + +function App() { + const [src, setSrc] = useState(""); + const [file, setFile] = useState(""); + return ( +
    + + + Get audio file +
    + ); +} + +export default App; +``` + + + +```ts title="app.component.ts" +import type { Schema } from '../../../amplify/data/resource'; +import { Component } from '@angular/core'; +import { generateClient } from 'aws-amplify/api'; +import { getUrl } from 'aws-amplify/storage'; + +const client = generateClient(); + +type PollyReturnType = Schema['convertTextToSpeech']['returnType']; + +@Component({ + selector: 'app-root', + template: ` +
    + + + Get audio file +
    + `, + styleUrls: ['./app.component.css'], +}) +export class App { + src: string = ''; + file: PollyReturnType = ''; + + async synthesize() { + const { data, errors } = await client.mutations.convertTextToSpeech({ + text: 'Hello World!', + }); + + if (!errors && data) { + this.file = data; + } else { + console.log(errors); + } + } + + async fetchAudio() { + const res = await getUrl({ + path: 'public/' + this.file, + }); + + this.src = res.url.toString(); + } +} +``` + + +--- + +--- +title: "Connect to Amazon Bedrock for generative AI use cases" +section: "build-a-backend/data/custom-business-logic" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-14T20:52:33.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-bedrock/" +--- + +[Amazon Bedrock](https://aws.amazon.com/bedrock/) is a fully managed service that removes the complexity of using foundation models (FMs) for generative AI development. It acts as a central hub, offering a curated selection of high-performing FMs from leading AI companies like Anthropic, AI21 Labs, Cohere, and Amazon itself. + +Amazon Bedrock streamlines generative AI development by providing: + +- **Choice and Flexibility**: Experiment and evaluate a wide range of FMs to find the perfect fit for your use case. + +- **Simplified Integration**: Access and use FMs through a single, unified API, reducing development time. + +- **Enhanced Security and Privacy**: Benefit from built-in safeguards to protect your data and prevent misuse. + +- **Responsible AI Features**: Implement guardrails to control outputs and mitigate bias. + +In the following sections, we walk through the steps to add Amazon Bedrock to your API as a data source and connect to it from your Amplify app: + +1. Add Amazon Bedrock as a data source +2. Define a custom query +3. Configure custom business logic handler code +4. Invoke a custom query to prompt a generative AI model + +## Step 1 - Add Amazon Bedrock as a data source + +To connect to Amazon Bedrock as a data source, you can choose between two methods - using a Lambda function or a custom resolver powered by AppSync JavaScript resolvers. The following steps demonstrate both methods: + +#### [Function] + +In your `amplify/backend.ts` file, replace the content with the following code to add a lambda function to your backend and grant it permission to invoke a generative AI model in Amazon Bedrock. The `generateHaikuFunction` lambda function will be defined in and exported from the `amplify/data/resource.ts` file in the next steps: + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend"; +import { auth } from "./auth/resource"; +import { data, MODEL_ID, generateHaikuFunction } from "./data/resource"; +import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam"; + +export const backend = defineBackend({ + auth, + data, + generateHaikuFunction, +}); + +backend.generateHaikuFunction.resources.lambda.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["bedrock:InvokeModel"], + resources: [ + `arn:aws:bedrock:*::foundation-model/${MODEL_ID}`, + ], + }) +); +``` + +#### [Custom resolver powered by AppSync JavaScript resolvers] + +In your `amplify/backend.ts` file, replace the content with the following code to add an HTTP data source for Amazon Bedrock to your API and grant it permissions to invoke a generative AI model: + +```ts title="amplify/backend.ts" +import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam"; +import { defineBackend } from "@aws-amplify/backend"; +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; + +export const backend = defineBackend({ + auth, + data, +}); + +const MODEL_ID = "anthropic.claude-3-haiku-20240307-v1:0"; + +const bedrockDataSource = backend.data.addHttpDataSource( + "BedrockDataSource", + "https://bedrock-runtime.us-east-1.amazonaws.com", + { + authorizationConfig: { + signingRegion: backend.data.stack.region, + signingServiceName: "bedrock", + }, + } +); + +bedrockDataSource.grantPrincipal.addToPrincipalPolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["bedrock:InvokeModel"], + resources: [ + `arn:aws:bedrock:${backend.data.stack.region}::foundation-model/${MODEL_ID}`, + ], + }) +); + +backend.data.resources.cfnResources.cfnGraphqlApi.environmentVariables = { + MODEL_ID +} +``` + +For the purpose of this guide, we will use Anthropic's Claude 3 Haiku to generate content. If you want to use a different model, you can find the ID for your model of choice in the Amazon Bedrock documentation's [list of model IDs](https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html) or the [Amazon Bedrock console](https://console.aws.amazon.com/bedrock/) and replace the value of `MODEL_ID`. + +> **Info:** The availability of Amazon Bedrock and its foundation models may vary by region. +> +> The policy statement in the code above assumes that your Amplify app is deployed in a region supported by Amazon Bedrock and the Claude 3 Haiku model. If you are deploying your app in a region where Amazon Bedrock is not available, update the code above accordingly. +> +> For a list of supported regions please refer to the [Amazon Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html). + +## Step 2 - Define a custom query + +#### [Function] +Next, replace the contents of your `amplify/data/resource.ts` file with the following code. This will define and export a lambda function that was granted permission to invoke a generative AI model in Amazon Bedrock in the previous step. A custom query named `generateHaiku` is added to the schema with the `generateHaikuFunction` as the handler using the `a.handler.function()` modifier: + +```ts title="amplify/data/resource.ts" +import { + type ClientSchema, + a, + defineData, + defineFunction, +} from "@aws-amplify/backend"; + +export const MODEL_ID = "anthropic.claude-3-haiku-20240307-v1:0"; + +export const generateHaikuFunction = defineFunction({ + entry: "./generateHaiku.ts", + environment: { + MODEL_ID, + }, +}); + +const schema = a.schema({ + generateHaiku: a + .query() + .arguments({ prompt: a.string().required() }) + .returns(a.string()) + .authorization((allow) => [allow.publicApiKey()]) + .handler(a.handler.function(generateHaikuFunction)), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +#### [Custom resolver powered by AppSync JavaScript resolvers] +With Amazon Bedrock added as a data source, you can reference it in custom queries using the `a.handler.custom()` modifier which accepts the name of the data source and an entry point for your resolvers. Replace the contents of your `amplify/data/resource.ts` file with the following code to define a custom query named `generateHaiku` in the schema: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + generateHaiku: a + .query() + .arguments({ prompt: a.string().required() }) + .returns(a.string()) + .authorization((allow) => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + dataSource: "BedrockDataSource", + entry: "./generateHaiku.js", + }) + ), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +## Step 3 - Configure custom business logic handler code + +#### [Function] +Next, create a `generateHaiku.ts` file in your `amplify/data` folder and use the following code to define a custom resolver for the custom query added to your schema in the previous step: + +The following code uses the `BedrockRuntimeClient` from the `@aws-sdk/client-bedrock-runtime` package to invoke the generative AI model in Amazon Bedrock. The `handler` function takes the user prompt as an argument, invokes the model, and returns the generated haiku. + +```ts title="amplify/data/generateHaiku.ts" +import type { Schema } from "./resource"; +import { + BedrockRuntimeClient, + InvokeModelCommand, + InvokeModelCommandInput, +} from "@aws-sdk/client-bedrock-runtime"; + +// initialize bedrock runtime client +const client = new BedrockRuntimeClient(); + +export const handler: Schema["generateHaiku"]["functionHandler"] = async ( + event, + context +) => { + // User prompt + const prompt = event.arguments.prompt; + + // Invoke model + const input = { + modelId: process.env.MODEL_ID, + contentType: "application/json", + accept: "application/json", + body: JSON.stringify({ + anthropic_version: "bedrock-2023-05-31", + system: + "You are a an expert at crafting a haiku. You are able to craft a haiku out of anything and therefore answer only in haiku.", + messages: [ + { + role: "user", + content: [ + { + type: "text", + text: prompt, + }, + ], + }, + ], + max_tokens: 1000, + temperature: 0.5, + }), + } as InvokeModelCommandInput; + + const command = new InvokeModelCommand(input); + + const response = await client.send(command); + + // Parse the response and return the generated haiku + const data = JSON.parse(Buffer.from(response.body).toString()); + + return data.content[0].text; +}; +``` + +#### [Custom resolver powered by AppSync JavaScript resolvers] +Next, create a `generateHaiku.js` file in your `amplify/data` folder and use the following code to define a custom resolver for the custom query added to your schema in the previous step: + +The following code defines a `request` function that constructs the HTTP request to invoke the generative AI model in Amazon Bedrock. The `response` function parses the response and returns the generated haiku. + +```js title="amplify/data/generateHaiku.js" +export function request(ctx) { + + // Define a system prompt to give the model a persona + const system = + "You are a an expert at crafting a haiku. You are able to craft a haiku out of anything and therefore answer only in haiku."; + + const prompt = ctx.args.prompt + + // Construct the HTTP request to invoke the generative AI model + return { + resourcePath: `/model/${ctx.env.MODEL_ID}/invoke`, + method: "POST", + params: { + headers: { + "Content-Type": "application/json", + }, + body: { + anthropic_version: "bedrock-2023-05-31", + system, + messages: [ + { + role: "user", + content: [ + { + type: "text", + text: prompt, + }, + ], + }, + ], + max_tokens: 1000, + temperature: 0.5, + }, + }, + }; +} + +// Parse the response and return the generated haiku +export function response(ctx) { + const res = JSON.parse(ctx.result.body); + const haiku = res.content[0].text; + + return haiku; +} +``` + +The code above uses the [Messages API](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html), which is supported by chat models such as Anthropic's Claude 3 Haiku. + +The `system` prompt is used to give the model a persona or directives to follow, and the `messages` array can contain a history of messages. The `max_tokens` parameter controls the maximum number of tokens the model can generate, and the `temperature` parameter determines the randomness, or creativity, of the generated response. + +## Step 4 - Invoke a custom query to prompt a generative AI model + +From your generated Data client, you can find all your custom queries and mutations under the `client.queries` and `client.mutations` APIs respectively. + +The custom query below will prompt a generative AI model to create a haiku based on the given prompt. Replace the `prompt` value with your desired prompt text or user input and invoke the query as shown below: + +```ts title="App.tsx" +const { data, errors } = await client.queries.generateHaiku({ + prompt: "Frank Herbert's Dune", +}); +``` + +Here's an example of a simple UI that prompts a generative AI model to create a haiku based on user input: + + +```tsx title="App.tsx" +import type { Schema } from '@/amplify/data/resource'; +import type { FormEvent } from 'react'; +import { useState } from 'react'; +import { Amplify } from 'aws-amplify'; +import { generateClient } from 'aws-amplify/api'; +import outputs from '@/amplify_outputs.json'; + +Amplify.configure(outputs); + +const client = generateClient(); + +export default function App() { + const [prompt, setPrompt] = useState(''); + const [answer, setAnswer] = useState(null); + + const sendPrompt = async (event: FormEvent) => { + event.preventDefault(); + + const { data, errors } = await client.queries.generateHaiku({ + prompt + }); + + if (!errors) { + setAnswer(data); + setPrompt(''); + } else { + console.log(errors); + } + }; + + return ( +
    +
    +

    Haiku Generator

    +
    + setPrompt(event.target.value)} + /> +
    +
    +
    {answer}
    +
    +
    +
    + ); +} +``` + + + +```ts title="app.component.ts" +import type { Schema } from '../../../amplify/data/resource'; +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { Amplify } from 'aws-amplify'; +import { generateClient } from 'aws-amplify/api'; +import outputs from '../../../amplify_outputs.json'; + +Amplify.configure(outputs); + +const client = generateClient(); + +@Component({ + selector: 'app-haiku', + standalone: true, + imports: [FormsModule], + template: ` +
    +
    +

    Haiku Generator

    +
    + +
    +
    +
    {{ answer }}
    +
    +
    +
    + `, +}) +export class HaikuComponent { + prompt: string = ''; + answer: string | null = null; + + async sendPrompt() { + const { data, errors } = await client.queries.generateHaiku({ + prompt: this.prompt, + }); + + if (!errors) { + this.answer = data; + this.prompt = ''; + } else { + console.log(errors); + } + } +} +``` + + +![A webpage titled "Haiku Generator" and input field. "Frank Herbert's Dune" is entered and submitted. Shortly after, a haiku is rendered to the page.](/images/haiku-generator.gif) + +## Conclusion + +In this guide, you learned how to connect to Amazon Bedrock from your Amplify app. By adding Bedrock as a data source, defining a custom query, configuring custom business logic handler code, and invoking custom queries, you can leverage the power of generative AI models in your application. + +To clean up, you can delete your sandbox by accepting the prompt when terminating the sandbox process in your terminal. Alternatively, you can also use the AWS Amplify console to manage and delete sandbox environments. + +--- + +--- +title: "Connect to Amazon Rekognition for Image Analysis APIs" +section: "build-a-backend/data/custom-business-logic" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-04-25T18:09:07.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-amazon-rekognition/" +--- + +**Amazon Rekognition** is an advanced machine learning service provided by Amazon Web Services (AWS), allowing developers to incorporate image and video analysis into their applications. It uses state-of-the-art machine learning models to analyze images and videos, providing valuable insights such as object and scene detection, text recognition, face analysis, and more. + +Key features of Amazon Rekognition include: + +- **Object and Scene Detection**: Amazon Rekognition can identify thousands of objects and scenes in images and videos, providing valuable context for your media content. + +- **Text Detection and Recognition**: The service can detect and recognize text within images and videos, making it an invaluable tool for applications requiring text extraction. + +- **Facial Analysis**: Amazon Rekognition offers accurate facial analysis, enabling you to detect, analyze, and compare faces in images and videos. + +- **Facial Recognition**: You can build applications with the capability to recognize and verify individuals using facial recognition. + +- **Content Moderation**: Amazon Rekognition can analyze images and videos to identify inappropriate or objectionable content, helping you maintain safe and compliant content. + +In this section, you will learn how to integrate Amazon Rekognition into your application using AWS Amplify, leveraging its powerful image analysis capabilities seamlessly. + +## Step 1 - Set up the project + +Set up your project by following the instructions in the [Quickstart guide](/[platform]/start/quickstart/). + +## Step 2 - Install Rekognition Libraries +Create a new API endpoint that'll use the the AWS SDK to call the Amazon Rekognition service. To install the Amazon Rekognition SDK, run the following command in your project's root folder: + +```bash title="Terminal" showLineNumbers={false} +npm add @aws-sdk/client-rekognition +``` + +## Step 3 - Setup Storage + +Create a file named `amplify/storage/resource.ts` and add the following content to configure a storage resource: + +```ts title="amplify/storage/resource.ts" +import { defineStorage } from '@aws-amplify/backend'; + +export const storage = defineStorage({ + name: 'predictions_gen2' +}); + +``` + +## Step 4 - Add your Amazon Rekognition as Datasource + +To use the Amazon Rekognition service, you need to add Amazon Rekognition as an HTTP Data Source and configure the proper IAM policy for Lambda to effectively utilize the desired feature and grant permission to access the storage. In this case, you can add the `rekognition:DetectText` and `rekognition:DetectLabels` actions to the policy. Update the `amplify/backend.ts` file as shown below. + + ```ts title= "amplify/backend.ts" +import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +import { data } from './data/resource'; +import { storage } from './storage/resource'; + +const backend = defineBackend({ + auth, + data, + storage +}); + +// Set environment variables for the S3 Bucket name +backend.data.resources.cfnResources.cfnGraphqlApi.environmentVariables = { + S3_BUCKET_NAME: backend.storage.resources.bucket.bucketName, +}; + +const rekognitionDataSource = backend.data.addHttpDataSource( + "RekognitionDataSource", + `https://rekognition.${backend.data.stack.region}.amazonaws.com`, + { + authorizationConfig: { + signingRegion: backend.data.stack.region, + signingServiceName: "rekognition", + }, + } +); + +rekognitionDataSource.grantPrincipal.addToPrincipalPolicy( + new PolicyStatement({ + actions: ["rekognition:DetectText", "rekognition:DetectLabels"], + resources: ["*"], + }) +); + +backend.storage.resources.bucket.grantReadWrite( + rekognitionDataSource.grantPrincipal +); + +``` +## Step 5 - Configure the function handler + +Define the function handler by creating a new file, `amplify/data/identifyText.js`. This function analyzes the image and extracts text using the Amazon Rekognition DetectText service. + +```ts title="amplify/data/identifyText.js" + +export function request(ctx) { + return { + method: "POST", + resourcePath: "/", + params: { + body: { + Image: { + S3Object: { + Bucket: ctx.env.S3_BUCKET_NAME, + Name: ctx.arguments.path, + }, + }, + }, + headers: { + "Content-Type": "application/x-amz-json-1.1", + "X-Amz-Target": "RekognitionService.DetectText", + }, + }, + }; +} + +export function response(ctx) { + return JSON.parse(ctx.result.body) + .TextDetections.filter((item) => item.Type === "LINE") + .map((item) => item.DetectedText) + .join("\n") + .trim(); +} + +``` + +## Step 6 - Define the custom query + +After adding Amazon Rekognition as a data source, you can reference it in custom query using the `a.handler.custom()` modifier, which takes the name of the data source and an entry point for your resolvers. In your `amplify/data/resource.ts` file, specify `RekognitionDataSource` as the data source and `identifyText.js` as the entry point, as shown below. + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + identifyText: a + .query() + .arguments({ + path: a.string(), + }) + .returns(a.string()) + .authorization((allow) => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + entry: "./identifyText.js", + dataSource: "RekognitionDataSource", + }) + ), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` +## Step 7 - Update Storage permissions + +Customize your storage settings to manage access to various paths within your storage bucket. Modify the file `amplify/storage/resource.ts` as shown below. + +```ts title="amplify/storage/resource.ts" +import { defineStorage } from "@aws-amplify/backend" + +export const storage = defineStorage({ + name: "predictions_gen2", + access: allow => ({ + 'public/*': [ + allow.guest.to(['list', 'write', 'get']) + ] + }) +}) +``` + +## Step 8 - Configure the frontend + +Import and load the configuration file in your app. It's recommended you add the Amplify configuration step to your app's root entry point. + +``` ts title="main.tsx" +import { Amplify } from "aws-amplify"; +import outputs from "../amplify_outputs.json"; + +Amplify.configure(outputs); + +``` +### Invoke the Text Recognition API + +This code sets up a React app to upload an image to an S3 bucket and then use Amazon Rekognition to recognize the text in the uploaded image. + + +```ts title="App.tsx" +import { type ChangeEvent, useState } from "react"; +import { generateClient } from "aws-amplify/api"; +import { uploadData } from "aws-amplify/storage"; +import { Schema } from "@/amplify/data/resource"; +import "./App.css"; + +// Generating the client +const client = generateClient(); + +type IdentifyTextReturnType = Schema["identifyText"]["returnType"]; + +function App() { + // State to hold the recognized text + const [path, setPath] = useState(""); + const [textData, setTextData] = useState(); + + // Function to handle file upload to S3 bucket + const handleTranslate = async (event: ChangeEvent) => { + if (event.target.files) { + const file = event.target.files[0]; + + const s3Path = "public/" + file.name; + + try { + uploadData({ + path: s3Path, + data: file, + }); + + setPath(s3Path); + } catch (error) { + console.error(error); + } + } + }; + + // Function to recognize text from the uploaded image + const recognizeText = async () => { + // Identifying text in the uploaded image + const { data } = await client.queries.identifyText({ + path, // File name + }); + setTextData(data); + }; + + return ( +
    +

    Amazon Rekognition Text Recognition

    +
    + + +
    +

    Recognized Text:

    + {textData} +
    +
    +
    + ); +} + +export default App; +``` + + +```ts title="app.component.ts" +import type { Schema } from '../../../amplify/data/resource'; +import { Component } from '@angular/core'; +import { generateClient } from 'aws-amplify/api'; +import { uploadData } from 'aws-amplify/storage'; +import { CommonModule } from '@angular/common'; + +// Generating the client +const client = generateClient(); + +type IdentifyTextReturnType = Schema['identifyText']['returnType']; + +@Component({ + selector: 'app-text-recognition', + standalone: true, + imports: [CommonModule], + template: ` +
    +

    Amazon Rekognition Text Recognition

    +
    + + +
    +

    Recognized Text:

    + {{ textData }} +
    +
    +
    + `, +}) +export class TodosComponent { + // Component properties instead of React state + path: string = ''; + textData?: IdentifyTextReturnType; + + // Function to handle file upload to S3 bucket + async handleTranslate(event: Event) { + const target = event.target as HTMLInputElement; + if (target.files && target.files.length > 0) { + const file = target.files[0]; + const s3Path = 'public/' + file.name; + + try { + await uploadData({ + path: s3Path, + data: file, + }); + + this.path = s3Path; + } catch (error) { + console.error(error); + } + } + } + + // Function to recognize text from the uploaded image + async recognizeText() { + // Identifying text in the uploaded image + const { data } = await client.queries.identifyText({ + path: this.path, // File name + }); + this.textData = data; + } +} +``` + + +--- + +--- +title: "Connect to Amazon Translate for language translation APIs" +section: "build-a-backend/data/custom-business-logic" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-10-15T16:15:13.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-amazon-translate/" +--- + +Amazon Translate is a neural machine translation service provided by Amazon Web Services (AWS). It uses advanced deep learning technologies to deliver fast and high-quality language translation. With Amazon Translate, you can easily add multilingual support to your applications and services, enabling users to communicate and interact in their preferred language. + +Key features of Amazon Translate include: + +- **Accurate and Fluent Translations**: Amazon Translate produces translations that are both accurate and natural-sounding, providing a seamless experience for users. + +- **Support for Multiple Languages**: The service supports a broad range of languages, allowing you to expand your application’s reach to diverse audiences around the world. + +- **Real-Time and Batch Translation**: Amazon Translate can handle real-time translation for dynamic content and batch translation for larger volumes of text, making it suitable for various use cases. + +- **Cost-Effective and Scalable**: With its pay-as-you-go pricing model and automatic scaling, Amazon Translate is an economical and flexible solution for adding translation capabilities to your applications. + +In this section, you will learn how to integrate Amazon Translate into your application using AWS Amplify, enabling you to leverage its powerful translation capabilities effortlessly. + +## Step 1 - Set up the project + +Set up your project by following the instructions in the [Quickstart guide](/[platform]/start/quickstart/). + +## Step 2 - Install Amazon Translate libraries +To install the Amazon Translate SDK, run the following command in your project's root folder: + +```bash title="Terminal" showLineNumbers={false} +npm add @aws-sdk/client-translate +``` + +## Step 3 - Add your Amazon Translate as Datasource + + To access Amazon Translate service, you need to add Amazon Translate as an HTTP Data Source and configure the proper IAM policy for AWS Lambda to utilize the desired feature effectively. Update `amplify/backend.ts` file as shown below. + + ```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { auth } from './auth/resource'; +import { data } from './data/resource'; + +const backend = defineBackend({ + auth, + data +}); + +const translateDataSource = backend.data.addHttpDataSource( + "TranslateDataSource", + `https://translate.${backend.data.stack.region}.amazonaws.com`, + { + authorizationConfig: { + signingRegion: backend.data.stack.region, + signingServiceName: "translate", + }, + } +); + +translateDataSource.grantPrincipal.addToPrincipalPolicy( + new PolicyStatement({ + actions: ["translate:TranslateText"], + resources: ["*"], + }) +); +``` +## Step 4 - Configure custom business logic handler + +Next, create the following `translate.js` file in your `amplify/data` folder and use the code below to define custom resolvers. + +```ts title="amplify/data/translate.js" + +export function request(ctx) { + return { + method: 'POST', + resourcePath: '/', + params: { + body: { + SourceLanguageCode: ctx.arguments.sourceLanguage, + TargetLanguageCode: ctx.arguments.targetLanguage, + Text: ctx.arguments.text + }, + headers: { + 'Content-Type': 'application/x-amz-json-1.1', + 'X-Amz-Target': 'AWSShineFrontendService_20170701.TranslateText' + } + }, + } +} + +export function response(ctx) { + return JSON.parse(ctx.result.body).TranslatedText +} + +``` + +## Step 5 - Define the custom query + +After adding Amazon Translate as a data source, you can reference it in a custom query using the `a.handler.custom()` modifier, which takes the name of the data source and an entry point for your resolvers. In your `amplify/data/resource.ts` file, specify `TranslateDataSource` as the data source and `translate.js` as the entry point, as shown below. + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + translate: a.query() + .arguments({ + sourceLanguage: a.string().required(), + targetLanguage: a.string().required(), + text: a.string().required() + }) + .returns(a.string()) + .authorization(allow => [allow.publicApiKey()]) + .handler(a.handler.custom({ + dataSource: "TranslateDataSource", + entry: './translate.js' + })) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); + +``` + +## Step 6 - Configure the frontend + +Import and load the configuration file in your app. It's recommended you add the Amplify configuration step to your app's root entry point. + +``` ts title="main.tsx" +import { Amplify } from "aws-amplify"; +import outputs from "../amplify_outputs.json"; + +Amplify.configure(outputs); +``` +### Invoke the API + +Sample frontend code to translate text from one language to another. + +```ts +import { generateClient } from 'aws-amplify/data'; +import { type Schema } from '../amplify/data/resource'; + +const client = generateClient(); + +const { data } = await client.queries.translate({ + sourceLanguage: "en", + targetLanguage: "es", + text: "Hello World!", +}); +``` + +--- + +--- +title: "Connect to an external HTTP endpoint" +section: "build-a-backend/data/custom-business-logic" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-06T18:52:05.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-http-datasource/" +--- + +The HTTP Datasource allows you to quickly configure HTTP resolvers within your Data API. + +This guide will demonstrate how to establish a connection to an external REST API using an HTTP data source and use Amplify Data's custom mutations and queries to interact with the REST API. + +## Step 1 - Set up your custom type + +For the purpose of this guide we will define a `Post` type and use an existing external REST API that will store records for it. In Amplify Gen 2, `customType` adds a type to the schema that is not backed by an Amplify-generated DynamoDB table. + +With the `Post` type defined, it can then be referenced as the return type when defining your custom queries and mutations. + +First, add the `Post` custom type to your schema: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + .authorization(allow => [allow.publicApiKey()]), + // highlight-start + Post: a.customType({ + title: a.string(), + content: a.string(), + author: a.string().required(), + }), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +## Step 2 - Add your REST API or HTTP API as Datasource + +To integrate the external REST API or HTTP API, you'll need to set it up as the HTTP Datasource. Add the following code in your `amplify/backend.ts` file. + +```ts title="amplify/backend.ts" + +import { defineBackend } from "@aws-amplify/backend"; +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; + +const backend = defineBackend({ + auth, + data, +}); + +const httpDataSource = backend.data.addHttpDataSource( + "HttpDataSource", + "https://www.example.com" +); + +``` +## Step 3 - Define custom queries and mutations + +Now that your REST API has been added as a data source, you can reference it in custom queries and mutations using the `a.handler.custom()` modifier which accepts the name of the data source and an entry point for your resolvers. + +Use the following code examples to add `addPost`, `getPost`, `updatePost`, and `deletePost` as custom queries and mutations to your schema: + +#### [addPost] +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Post: a.customType({ + title: a.string(), + content: a.string(), + author: a.string().required(), + }), + // highlight-start + addPost: a + .mutation() + .arguments({ + title: a.string(), + content: a.string(), + author: a.string().required(), + }) + .returns(a.ref("Post")) + .authorization(allow => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + dataSource: "HttpDataSource", + entry: "./addPost.js", + }) + ), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +#### [getPost] +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Post: a.customType({ + title: a.string(), + content: a.string(), + author: a.string().required(), + }), + // highlight-start + getPost: a + .query() + .arguments({ id: a.id().required() }) + .returns(a.ref("Post")) + .authorization(allow => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + dataSource: "HttpDataSource", + entry: "./getPost.js", + }) + ), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +#### [updatePost] +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Post: a.customType({ + title: a.string(), + content: a.string(), + author: a.string().required(), + }), + // highlight-start + updatePost: a + .mutation() + .arguments({ + id: a.id().required(), + title: a.string(), + content: a.string(), + author: a.string(), + }) + .returns(a.ref("Post")) + .authorization(allow => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + dataSource: "HttpDataSource", + entry: "./updatePost.js", + }) + ), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +#### [deletePost] +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Post: a.customType({ + title: a.string(), + content: a.string(), + author: a.string().required(), + }), + // highlight-start + deletePost: a + .mutation() + .arguments({ id: a.id().required() }) + .returns(a.ref("Post")) + .authorization(allow => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + dataSource: "HttpDataSource", + entry: "./deletePost.js", + }) + ), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +## Step 4 - Configure custom business logic handler code + +Next, create the following files in your `amplify/data` folder and use the code examples to define custom resolvers for the custom queries and mutations added to your schema from the previous step. These are AppSync JavaScript resolvers. + +#### [addPost] +```js title="amplify/data/addPost.js" +import { util } from "@aws-appsync/utils"; + +export function request(ctx) { + return { + method: "POST", + resourcePath: "/post", + params: { + headers: { + "Content-Type": "application/json", + }, + body: { + title: ctx.arguments.title, + content: ctx.arguments.content, + author: ctx.arguments.author, + }, + }, + }; +} + +export function response(ctx) { + if (ctx.error) { + return util.error(ctx.error.message, ctx.error.type); + } + if (ctx.result.statusCode == 200) { + return JSON.parse(ctx.result.body).data; + } else { + return util.appendError(ctx.result.body, "ctx.result.statusCode"); + } +} +``` + +#### [getPost] +```js title="amplify/data/getPost.js" +import { util } from "@aws-appsync/utils"; + +export function request(ctx) { + return { + method: "GET", + resourcePath: "/posts/" + ctx.arguments.id, + params: { + headers: { + "Content-Type": "application/json", + }, + }, + }; +} + +export function response(ctx) { + if (ctx.error) { + return util.error(ctx.error.message, ctx.error.type); + } + if (ctx.result.statusCode == 200) { + return JSON.parse(ctx.result.body).data; + } else { + return util.appendError(ctx.result.body, "ctx.result.statusCode"); + } +} +``` + +#### [updatePost] +```js title="amplify/data/updatePost.js" +import { util } from "@aws-appsync/utils"; + +export function request(ctx) { + return { + method: "POST", + resourcePath: "/posts/" + ctx.arguments.id, + params: { + headers: { + "Content-Type": "application/json", + }, + body: { + title: ctx.arguments.title, + content: ctx.arguments.content, + author: ctx.arguments.author, + }, + }, + }; +} + +export function response(ctx) { + if (ctx.error) { + return util.error(ctx.error.message, ctx.error.type); + } + if (ctx.result.statusCode == 200) { + return JSON.parse(ctx.result.body).data; + } else { + return util.appendError(ctx.result.body, "ctx.result.statusCode"); + } +} + +``` + +#### [deletePost] +```js title="amplify/data/deletePost.js" +import { util } from "@aws-appsync/utils"; + +export function request(ctx) { + return { + method: "DELETE", + resourcePath: "/posts/" + ctx.arguments.id, + params: { + headers: { + "Content-Type": "application/json", + }, + }, + }; +} + +export function response(ctx) { + if (ctx.error) { + return util.error(ctx.error.message, ctx.error.type); + } + if (ctx.result.statusCode == 200) { + return JSON.parse(ctx.result.body).data; + } else { + return util.appendError(ctx.result.body, "ctx.result.statusCode"); + } +} +``` + +## Step 5 - Invoke custom queries or mutations + +From your generated Data client, you can find all your custom queries and mutations under the client.queries. and client.mutations. APIs respectively. + +#### [addPost] +```ts title="App.tsx" +const { data, errors } = await client.mutations.addPost({ + title: "My Post", + content: "My Content", + author: "Chris", +}); +``` + +#### [getPost] +```ts title="App.tsx" +const { data, errors } = await client.queries.getPost({ + id: "" +}); +``` + +#### [updatePost] +```ts title="App.tsx" +const { data, errors } = await client.mutations.updatePost({ + id: "", + title: "An Updated Post", +}); +``` + +#### [deletePost] +```ts title="App.tsx" +const { data, errors } = await client.mutations.deletePost({ + id: "", +}); +``` + +## Conclusion + +In this guide, you’ve added an external REST API as a HTTP data source to an Amplify Data API and defined custom queries and mutations, handled by AppSync JS resolvers, to manipulate Post items in an external REST API using the Amplify Gen 2 Data client. + +To clean up, you can delete your sandbox by accepting the prompt when terminating the sandbox process in your terminal. Alternatively, you can also use the AWS Amplify console to manage and delete sandbox environments. + +--- + +--- +title: "Batch DynamoDB Operations" +section: "build-a-backend/data/custom-business-logic" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-13T16:03:51.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/batch-ddb-operations/" +--- + +Batch DynamoDB operations allow you to add multiple items in single mutation. + +## Step 1 - Define a custom mutation + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + // 1. Define your return type as a custom type or model + Post: a.model({ + id: a.id(), + content: a.string(), + likes: a.integer() + }), + + // 2. Define your mutation with the return type and, optionally, arguments + BatchCreatePost: a + .mutation() + // arguments that this query accepts + .arguments({ + content: a.string().array() + }) + .returns(a.ref('Post').array()) + // only allow signed-in users to call this API + .authorization(allow => [allow.authenticated()]) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema +}); +``` + +## Step 2 - Configure custom business logic handler code + +After your query or mutation is defined, you need to author your custom business logic using a [custom resolver powered by AppSync JavaScript resolver](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html). + +Custom resolvers work on a "request/response" basis. You choose a data source, map your request to the data source's input parameters, and then map the data source's response back to the query/mutation's return type. Custom resolvers provide the benefit of no cold starts, less infrastructure to manage, and no additional charge for Lambda function invocations. Review [Choosing between custom resolver and function](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-overview-js.html#choosing-data-source). + +In your `amplify/data/resource.ts` file, define a custom handler using `a.handler.custom`. + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + Post: a.model({ + id: a.id(), + content: a.string(), + likes: a.integer() + }), + + BatchCreatePost: a + .mutation() + .arguments({ + contents: a.string().array() + }) + .returns(a.ref('Post').array()) + .authorization(allow => [allow.authenticated()]) + // 1. Add the custom handler + .handler( + a.handler.custom({ + dataSource: a.ref('Post'), + entry: './BatchCreatePostHandler.js', + }) + ) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema +}); +``` + +Amplify will store some values in the resolver context stash that can be accessed in the custom resolver. + +| Name | Description | +| ------------------------- | -------------------------------------------- | +| awsAppsyncApiId | The ID of the AppSync API. | +| amplifyApiEnvironmentName | The Amplify api environment name. (`NONE` in sandbox) | + +The Amplify generated DynamoDB table names can be constructed from the variables in the context stash. The table name is in the format `--`. For example, the table name for the `Post` model would be `Post-123456-dev` where `123456` is the AppSync API ID and `dev` is the Amplify API environment name. + +```ts title="amplify/data/BatchCreatePostHandler.js" +import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + var now = util.time.nowISO8601(); + + return { + operation: 'BatchPutItem', + tables: { + [`Post-${ctx.stash.awsAppsyncApiId}-${ctx.stash.amplifyApiEnvironmentName}`]: ctx.args.contents.map((content) => + util.dynamodb.toMapValues({ + content, + id: util.autoId(), + createdAt: now, + updatedAt: now, + }) + ), + }, + }; +} + +export function response(ctx) { + if (ctx.error) { + util.error(ctx.error.message, ctx.error.type); + } + return ctx.result.data[`Post-${ctx.stash.awsAppsyncApiId}-${ctx.stash.amplifyApiEnvironmentName}`]; +} +``` + +## Step 3 - Invoke the custom query or mutation + +From your generated Data client, you can find all your custom queries and mutations under the `client.queries.` and `client.mutations.` APIs respectively. + +```ts +const { data, errors } = await client.mutations.BatchCreatePost({ + contents: ['Post 1', 'Post 2', 'Post 3'] +}); +``` + +--- + +--- +title: "Working with files/attachments" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-14T20:52:33.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/working-with-files/" +--- + +The Storage and GraphQL API categories can be used together to associate a file, such as an image or video, with a particular record. For example, you might create a `User` model with a profile picture, or a `Post` model with an associated image. With Amplify's GraphQL API and Storage categories, you can reference the file within the model itself to create an association. + +## Set up the project + +Set up your project by following the instructions in the [Quickstart guide](/[platform]/start/quickstart/). + +## Define the model + +Open `amplify/data/resource.ts` and add the following model as shown below: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Song: a + .model({ + id: a.id().required(), + name: a.string().required(), + coverArtPath: a.string(), + }) + .authorization((allow) => [allow.publicApiKey()]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` +## Setup the Storage + +Next, Let's configure Storage and allow access to all authenticated(signed in) users of your application. create a file `amplify/storage/resource.ts` and add the following code,This will restrict file access to only the signed-in user. + +```ts title="amplify/storage/resource.ts" + +import { defineStorage } from "@aws-amplify/backend"; + +export const storage = defineStorage({ + name: "amplify-gen2-files", + access: (allow) => ({ + "images/*": [allow.authenticated.to(["read", "write", "delete"])], + }), +}); +``` + +Configure the storage in the `amplify/backend.ts` file as demonstrated below: + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend"; +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; +import { storage } from "./storage/resource"; + +export const backend = defineBackend({ + auth, + data, + storage, +}); +``` + +## Configuring authorization + +Your application needs authorization credentials for reading and writing to both Storage and the Data, except in the case where all data and files are intended to be publicly accessible. + +The Storage and Data categories govern data access based on their own authorization patterns, meaning that it's necessary to configure appropriate auth roles for each individual category. Although both categories share the same access credentials set up through the Auth category, they work independently from one another. For instance, adding an `allow.authenticated()` to the Data does not guard against file access in the Storage category. Likewise, adding authorization rules to the Storage category does not guard against data access in the API. + +When you configure Storage, Amplify will configure appropriate IAM policies on the bucket using a Cognito Identity Pool role. You will then have the option of adding CRUD (Create, Update, Read and Delete) based permissions as well, so that Authenticated and Guest users will be granted limited permissions within these levels. **Even after adding this configuration, all Storage access is still `guest` by default.** To guard against accidental public access, the Storage access levels must either be configured on the Storage object globally, or set within individual function calls. This guide uses the former approach, setting all Storage access to `authenticated` users. + +The ability to independently configure authorization rules for each category allows for more granular control over data access, and adds greater flexibility. For scenarios where authorization patterns must be mixed and matched, configure the access level on individual Storage function calls. For example, you may want to use `entity_id` CRUD access on an individual Storage function call for files that should only be accessible by the owner (such as personal files), `authenticated` read access to allow all logged in users to view common files (such as images in a shared photo album), and `guest` read access to allow all users to view a file (such as a public profile picture). + +For more details on how to configure Storage authorization levels, see the [Storage documentation](/[platform]/build-a-backend/storage/authorization/). For more on configuring Data authorization, see the [API documentation](/[platform]/build-a-backend/data/customize-authz/). + +## Create a record with an associated file + +You can create a record via the Amplify Data client, upload a file to Storage, and finally update the record to associate it with the uploaded file. Use the following example with the Amplify Data client and Amplify Storage library helpers, `uploadData` and `getUrl`, to create a record and associate it the file with the record. + + + +The API record's `id` is prepended to the Storage file name to ensure uniqueness. If this is excluded, multiple API records could then be associated with the same file path unintentionally. + + + + +```swift title="ContentView" +let song = Song(name: name) + +guard let imageData = artCover.pngData() else { + print("Could not get data from image.") + return +} + +// Create the song record +var createdSong = try await Amplify.API.mutate(request: .create(song)).get() +let coverArtPath = "images/\(createdSong.id)" + +// Upload the art cover image +_ = try await Amplify.Storage.uploadData(path: .fromString(coverArtPath), data: imageData).value + +// Update the song record with the image path +createdSong.coverArtPath = coverArtPath +let updatedSong = try await Amplify.API.mutate(request: .update(createdSong)).get() +``` + + + +```ts title="src/App.tsx" + +import { generateClient } from "aws-amplify/api"; +import { uploadData, getUrl } from "aws-amplify/storage"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +// Create the API record: +const response = await client.models.Song.create({ + name: `My first song`, +}); + +const song = response.data; + +if (!song) return; + +// Upload the Storage file: +const result = await uploadData({ + path: `images/${song.id}-${file.name}`, + data: file, + options: { + contentType: "image/png", // contentType is optional + }, +}).result; + +// Add the file association to the record: +const updateResponse = await client.models.Song.update({ + id: song.id, + coverArtPath: result?.path, +}); + +const updatedSong = updateResponse.data; + +setCurrentSong(updatedSong); + +// If the record has no associated file, we can return early. +if (!updatedSong.coverArtPath) return; + +// Retrieve the file's signed URL: +const signedURL = await getUrl({ path: updatedSong.coverArtPath }); +``` + + +## Add or update a file for an associated record + +To associate a file with a record, update the record with the path returned by the Storage upload. The following example uploads the file using Storage, updates the record with the file's path, then retrieves the signed URL to download the image. If an image is already associated with the record, this will update the record with the new image. + + +```swift title="ContentView" +guard var currentSong = currentSong else { + print("There is no song to associated the image with. Create a Song first.") + return +} +guard let imageData = artCover.pngData() else { + print("Could not get data from UIImage.") + return +} + +let coverArtPath = "images/\(currentSong.id)" + +// Upload the new art image +_ = try await Amplify.Storage.uploadData(path: .fromString(coverArtPath), data: imageData).value + +// Update the song record +currentSong.coverArtPath = coverArtPath +let updatedSong = try await Amplify.API.mutate(request: .update(currentSong)).get() +``` + + + + +```ts title="src/App.tsx" +import { generateClient } from "aws-amplify/api"; +import { uploadData, getUrl } from "aws-amplify/storage"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +// Upload the Storage file: +const result = await uploadData({ + path: `images/${currentSong.id}-${file.name}`, + data: file, + options: { + contentType: "image/png", // contentType is optional + }, +}).result; + +// Add the file association to the record: +const response = await client.models.Song.update({ + id: currentSong.id, + coverArtPath: result?.path, +}); + +const updatedSong = response.data; + +setCurrentSong(updatedSong); + +// If the record has no associated file, we can return early. +if (!updatedSong?.coverArtPath) return; + +// Retrieve the file's signed URL: +const signedURL = await getUrl({ path: updatedSong.coverArtPath }); +``` + + + +## Query a record and retrieve the associated file + +To retrieve the file associated with a record, first query the record, then use Storage to get the signed URL. The signed URL can then be used to download the file, display an image, etc: + +```swift title="ContentView" +// Get the song record +guard let song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { + print("Song may have been deleted, no song by id: ", currentSong.id) + return +} + +// If the record has no associated file, we can return early. +guard let coverArtPath = song.coverArtPath else { + print("Song does not contain cover art") + return +} + +// Download the art cover +print("coverArtPath: ", coverArtPath) +let imageData = try await Amplify.Storage.downloadData(path: .fromString(coverArtPath)).value + +let image = UIImage(data: imageData) +``` + + + +```ts title="src/App.tsx" +import { generateClient } from "aws-amplify/api"; +import { getUrl } from "aws-amplify/storage"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +const response = await client.models.Song.get({ + id: currentSong.id, +}); + +const song = response.data; + +// If the record has no associated file, we can return early. +if (!song?.coverArtPath) return; + +// Retrieve the signed URL: +const signedURL = await getUrl({ path: song.coverArtPath }); +``` + + +## Delete and remove files associated with API records + +There are three common deletion workflows when working with Storage files and the GraphQL API: + +1. Remove the file association, continue to persist both file and record. +2. Remove the record association and delete the file. +3. Delete both file and record. + +### Remove the file association, continue to persist both file and record + +The following example removes the file association from the record, but does not delete the file from S3, nor the record from the database. + + +```swift title="ContentView" +// Get the song record +guard var song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { + print("Song may have been deleted, no song by id: ", currentSong.id) + return +} + +guard song.coverArtPath != nil else { + print("There is no cover art path to remove image association") + return +} + +// Set the association to nil and update it +song.coverArtPath = nil + +let updatedSong = try await Amplify.API.mutate(request: .update(song)).get() +``` + + + +```ts title="src/App.tsx" + +import { generateClient } from "aws-amplify/api"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +const response = await client.models.Song.get({ + id: currentSong.id, +}); + +const song = response.data; + +// If the record has no associated file, we can return early. +if (!song?.coverArtPath) return; + +const updatedSong = await client.models.Song.update({ + id: song.id, + coverArtPath: null, +}); +``` + + +### Remove the record association and delete the file + +The following example removes the file from the record, then deletes the file from S3: + + +```swift title="ContentView" +// Get the song record +guard var song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { + print("Song may have been deleted, no song by id: ", currentSong.id) + return +} + +guard let coverArtPath = song.coverArtPath else { + print("There is no cover art path to remove image association") + return +} + +// Set the association to nil and update it +song.coverArtPath = nil +let updatedSong = try await Amplify.API.mutate(request: .update(song)).get() + +// Remove the image +try await Amplify.Storage.remove(path: .fromString(coverArtPath)) +``` + + + + +```ts title="src/App.tsx" +import { generateClient } from "aws-amplify/api"; +import { remove } from "aws-amplify/storage"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); +const response = await client.models.Song.get({ + id: currentSong.id, +}); +const song = response?.data; + +// If the record has no associated file, we can return early. +if (!song?.coverArtPath) return; + +// Remove associated file from record +const updatedSong = await client.models.Song.update({ + id: song.id, + coverArtPath: null, +}); + +// Delete the file from S3: +await remove({ path: song.coverArtPath }); + +``` + + +### Delete both file and record + + +```swift title="ContentView" +// Get the song record +guard let song = try await Amplify.API.query(request: .get(Song.self, byId: currentSong.id)).get() else { + print("Song may have been deleted, no song by id: ", currentSong.id) + return +} + +if let coverArt = song.coverArtPath { + // Delete the file from S3 + try await Amplify.Storage.remove(path: .fromString(coverArt)) +} + +// Delete the song record +_ = try await Amplify.API.mutate(request: .delete(song)).get() +``` + + + +```ts title="src/App.tsx" +import { generateClient } from "aws-amplify/api"; +import { remove } from "aws-amplify/storage"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); +const response = await client.models.Song.get({ + id: currentSong.id, +}); + +const song = response.data; + +// If the record has no associated file, we can return early. +if (!song?.coverArtPath) return; + +await remove({ path: song.coverArtPath }); + +// Delete the record from the API: +await client.models.Song.delete({ id: song.id }); + +``` + + +## Working with multiple files + +You may want to add multiple files to a single record, such as a user profile with multiple images. To do this, you can add a list of file keys to the record. The following example adds a list of file keys to a record: + +### GraphQL schema to associate a data model with multiple files + +Add the following model in `amplify/data/resource.ts" file. + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + PhotoAlbum: a + .model({ + id: a.id().required(), + name: a.string().required(), + imagePaths: a.string().array(), + }) + .authorization((allow) => [allow.publicApiKey()]), +}); +``` + +CRUD operations when working with multiple files is the same as when working with a single file, with the exception that we are now working with a list of image keys, as opposed to a single image key. + +### Create a record with multiple associated files + +First create a record via the GraphQL API, then upload the files to Storage, and finally add the associations between the record and files. + + +```swift title="ContentView" +// Create the photo album record +let album = PhotoAlbum(name: name) +var createdAlbum = try await Amplify.API.mutate(request: .create(album)).get() + +// Upload the photo album images +let imagePaths = await withTaskGroup(of: String?.self) { group in + for imageData in imagesData { + group.addTask { + let path = "images/\(album.id)-\(UUID().uuidString)" + do { + _ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value + return path + } catch { + print("Failed with error:", error) + return nil + } + } + } + + var imagePaths: [String?] = [] + for await imagePath in group { + imagePaths.append(imagePath) + } + return imagePaths.compactMap { $0 } +} + +// Update the album with the image paths +createdAlbum.imagePaths = imagePaths +let updatedAlbum = try await Amplify.API.mutate(request: .update(createdAlbum)).get() +``` + + + +```ts title="src/App.tsx" +import { generateClient } from "aws-amplify/api"; +import { uploadData, getUrl } from "aws-amplify/storage"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +// Create the API record: +const response = await client.models.PhotoAlbum.create({ + name: `My first photoAlbum`, +}); + +const photoAlbum = response.data.createPhotoAlbum; + +if (!photoAlbum) return; + +// Upload all files to Storage: +const imagePaths = await Promise.all( + Array.from(e.target.files).map(async (file) => { + const result = await uploadData({ + path: `images/${photoAlbum.id}-${file.name}`, + data: file, + options: { + contentType: "image/png", // contentType is optional + }, + }).result; + + return result.path; + }) +); + +const updatePhotoAlbumDetails = { + id: photoAlbum.id, + imagePaths: imagePaths, +}; + +// Add the file association to the record: +const updateResponse = await client.graphql({ + query: mutations.updatePhotoAlbum, + variables: { input: updatePhotoAlbumDetails }, +}); + +const updatedPhotoAlbum = updateResponse.data.updatePhotoAlbum; + +// If the record has no associated file, we can return early. +if (!updatedPhotoAlbum.imageKeys?.length) return; + +// Retrieve signed urls for all files: +const signedUrls = await Promise.all( + updatedPhotoAlbum?.imagePaths.map( + async (path) => await getUrl({ path: path! }) + ) +); +``` + + +### Add new files to an associated record + +To associate additional files with a record, update the record with the paths returned by the Storage uploads. + + +```swift title="ContentView" +// Upload the new photo album image +let path = "images/\(currentAlbum.id)-\(UUID().uuidString)" +_ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value + +// Get the latest album +guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { + print("Album may have been deleted, no album by id: ", currentAlbum.id) + return +} + +guard var imagePaths = album.imagePaths else { + print("Album does not contain images") + await setCurrentAlbum(album) + await setCurrentImages([]) + return +} + +// Add new to the existing paths +imagePaths.append(path) + +// Update the album with the image paths +album.imagePaths = imagePaths +let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() +``` + + + +```ts title="src/App.tsx" + +import { generateClient } from "aws-amplify/api"; +import { uploadData, getUrl } from "aws-amplify/storage"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +// Upload all files to Storage: +const newimagePaths = await Promise.all( + Array.from(e.target.files).map(async (file) => { + const result = await uploadData({ + path: `images/${currentPhotoAlbum.id}-${file.name}`, + data: file, + options: { + contentType: "image/png", // contentType is optional + }, + }).result; + + return result.path; + }) +); + +// Query existing record to retrieve currently associated files: +const queriedResponse = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, +}); + +const photoAlbum = queriedResponse.data; + +if (!photoAlbum?.imagePaths) return; + +// Merge existing and new file paths: +const updatedimagePaths = [...newimagePaths, ...photoAlbum.imagePaths]; + +// Update record with merged file associations: +const response = await client.models.PhotoAlbum.update({ + id: currentPhotoAlbum.id, + imagePaths: updatedimagePaths, +}); + +const updatedPhotoAlbum = response.data; + +// If the record has no associated file, we can return early. +if (!updatedPhotoAlbum?.imageKeys) return; + +// Retrieve signed urls for merged image paths: +const signedUrls = await Promise.all( + updatedPhotoAlbum?.imagePaths.map( + async (path) => await getUrl({ path: path! }) + ) +); +``` + + +### Update the file for an associated record + +Updating a file for an associated record is the same as updating a file for a single file record, with the exception that you will need to update the list of file keys. + +```swift title="ContentView" +// Upload new file to Storage: +let path = "images/\(currentAlbum.id)-\(UUID().uuidString)" + +_ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value + +// Update the album with the image keys +var album = currentAlbum + +if var imagePaths = album.imagePaths { + imagePaths.removeLast() + imagePaths.append(path) + album.imagePaths = imagePaths +} else { + album.imagePaths = [path] +} + +// Update record with updated file associations: +let updateResult = try await Amplify.API.mutate(request: .update(album)).get() +``` + + +```ts title="src/App.tsx" +import { generateClient } from "aws-amplify/api"; +import { uploadData, getUrl } from "aws-amplify/storage"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +// Upload new file to Storage: +const result = await uploadData({ + path: `images/${currentPhotoAlbum.id}-${file.name}`, + data: file, + options: { + contentType: "image/png", // contentType is optional + }, +}).result; + +const newFilePath = result.path; + +// Query existing record to retrieve currently associated files: +const queriedResponse = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, +}); + +const photoAlbum = queriedResponse.data; + +if (!photoAlbum?.imagePaths?.length) return; + +// Retrieve last image path: +const [lastImagePath] = photoAlbum.imagePaths.slice(-1); + +// Remove last file association by path +const updatedimagePaths = [ + ...photoAlbum.imagePaths.filter((path) => path !== lastImagePath), + newFilePath, +]; + +// Update record with updated file associations: +const response = await client.models.PhotoAlbum.update({ + id: currentPhotoAlbum.id, + imagePaths: updatedimagePaths, +}); + +const updatedPhotoAlbum = response.data; + +// If the record has no associated file, we can return early. +if (!updatedPhotoAlbum?.imagePaths) return; + +// Retrieve signed urls for merged image paths: +const signedUrls = await Promise.all( + updatedPhotoAlbum?.imagePaths.map( + async (path) => await getUrl({ path: path! }) + ) +); + +``` + +### Query a record and retrieve the associated files + +To retrieve the files associated with a record, first query the record, then use Storage to retrieve all of the signed URLs. + + +```swift title="ContentView" +// Query the record to get the file paths: +guard let album = try await Amplify.API.query( + request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { + print("Album may have been deleted, no album by id: ", currentAlbum.id) + return +} + +guard let imagePathsOptional = album.imagePaths else { + print("Album does not contain images") + await setCurrentAlbum(album) + await setCurrentImages([]) + return +} + +let imagePaths = imagePathsOptional.compactMap { $0 } + +// Download the photos +let images = await withTaskGroup(of: UIImage?.self) { group in + for path in imagePaths { + group.addTask { + do { + let imageData = try await Amplify.Storage.downloadData(path: .fromString(path)).value + return UIImage(data: imageData) + } catch { + print("Failed with error:", error) + return nil + } + } + } + + var images: [UIImage?] = [] + for await image in group { + images.append(image) + } + return images.compactMap { $0 } +} +``` + + +```ts title="src/App.tsx" +async function getImagesForPhotoAlbum() { +import { generateClient } from "aws-amplify/api"; +import { uploadData, getUrl } from "aws-amplify/storage"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +// Query the record to get the file paths: +const response = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, +}); + +const photoAlbum = response.data; + +// If the record has no associated files, we can return early. +if (!photoAlbum?.imagePaths) return; + +// Retrieve the signed URLs for the associated images: +const signedUrls = await Promise.all( + photoAlbum.imagePaths.map(async (imagePath) => { + if (!imagePath) return; + return await getUrl({ path: imagePath }); + }) +); +} +``` + + +### Delete and remove files associated with API records + +The workflow for deleting and removing files associated with API records is the same as when working with a single file, except that when performing a delete you will need to iterate over the list of file paths and call `Storage.remove()` for each file. + +#### Remove the file association, continue to persist both files and record + + +```swift title="ContentView" +// Get the album record +guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { + print("Album may have been deleted, no album by id: ", currentAlbum.id) + return +} + +guard let imagePaths = album.imagePaths, !imagePaths.isEmpty else { + print("There are no images to remove association") + return +} + +// Set the association to nil and update it +album.imagePaths = nil +let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() +``` + + + +```ts title="src/App.tsx" + +import { generateClient } from "aws-amplify/api"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +const response = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, +}); + +const photoAlbum = response.data; + +// If the record has no associated file, we can return early. +if (!photoAlbum?.imagePaths) return; + +const updatedPhotoAlbum = await client.models.PhotoAlbum.update({ + id: photoAlbum.id, + imagePaths: null, +}); +``` + + +#### Remove the record association and delete the files + + +```swift title="ContentView" +// Get the album record +guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { + print("Album may have been deleted, no album by id: ", currentAlbum.id) + return +} + +guard let imagePathsOptional = album.imagePaths else { + print("Album does not contain images") + await setCurrentAlbum(album) + await setCurrentImages([]) + return +} +let imagePaths = imagePathsOptional.compactMap { $0 } + +// Set the associations to nil and update it +album.imagePaths = nil +let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() + +// Remove the photos +await withTaskGroup(of: Void.self) { group in + for path in imagePaths { + group.addTask { + do { + try await Amplify.Storage.remove(path: .fromString(path)) + } catch { + print("Failed with error:", error) + } + } + } + + for await _ in group { + } +} +``` + + +```ts title="src/App.tsx" +import { generateClient } from "aws-amplify/api"; +import { remove } from "aws-amplify/storage"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +const response = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, +}); + +const photoAlbum = response.data; + +// If the record has no associated files, we can return early. +if (!photoAlbum?.imagePaths) return; + +// Remove associated files from record +const updateResponse = await client.models.PhotoAlbum.update({ + id: photoAlbum.id, + imagePaths: null, // Set the file association to `null` +}); + +const updatedPhotoAlbum = updateResponse.data; + +// Delete the files from S3: +await Promise.all( + photoAlbum?.imagePaths.map(async (imagePath) => { + if (!imagePath) return; + await remove({ path: imagePath }); + }) +); +``` + + +#### Delete record and all associated files + + +```swift title="ContentView" +// Get the album record +guard let album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { + print("Album may have been deleted, no album by id: ", currentAlbum.id) + return +} + +guard let imagePathsOptional = album.imagePaths else { + print("Album does not contain images") + + // Delete the album record + _ = try await Amplify.API.mutate(request: .delete(album)) + + await setCurrentAlbum(nil) + await setCurrentImages([]) + return +} + +let imagePaths = imagePathsOptional.compactMap { $0 } + +// Remove the photos +await withTaskGroup(of: Void.self) { group in + for path in imagePaths { + group.addTask { + do { + try await Amplify.Storage.remove(path: .fromString(path)) + } catch { + print("Failed with error:", error) + } + } + } + + for await _ in group { + } +} + +// Delete the album record +_ = try await Amplify.API.mutate(request: .delete(album)).get() +``` + + + +```ts title="src/App.tsx" + +import { generateClient } from "aws-amplify/api"; +import { remove } from "aws-amplify/storage"; +import type { Schema } from "../amplify/data/resource"; + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +const response = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, +}); + +const photoAlbum = response.data; + +if (!photoAlbum) return; + +await client.models.PhotoAlbum.delete({ + id: photoAlbum.id, +}); + +setCurrentPhotoAlbum(null); + +// If the record has no associated file, we can return early. +if (!photoAlbum?.imagePaths) return; + +await Promise.all( + photoAlbum?.imagePaths.map(async (imagePath) => { + if (!imagePath) return; + await remove({ path: imagePath }); + }) +); + +``` + + +## Data consistency when working with records and files + +The recommended access patterns in these docs attempt to remove deleted files, but favor leaving orphans over leaving records that point to non-existent files. This optimizes for read latency by ensuring clients _rarely_ attempt to fetch a non-existent file from Storage. However, any app that deletes files can inherently cause records _on-device_ to point to non-existent files. + +One example is when we [create an API record, associate the Storage file with that record, and then retrieve the file's signed URL](#create-a-record-with-an-associated-file). "Device A" calls the GraphQL API to create `API_Record_1`, and then associates that record with `First_Photo`. Before "Device A" is about to retrieve the signed URL, "Device B" might query `API_Record_1`, delete `First_Photo`, and update the record accordingly. However, "Device A" is still using the old `API_Record_1`, which is now out-of-date. Even though the shared global state is correctly in sync at every stage, the individual device ("Device A") has an out-of-date record that points to a non-existent file. Similar issues can conceivably occur for updates. Depending on your app, some of these mismatches can be minimized _even more_ with [real-time data / GraphQL subscriptions](/[platform]/build-a-backend/data/subscribe-data/). + +It is important to understand when these mismatches can occur and to add meaningful error handling around these cases. This guide does not include exhaustive error handling, real-time subscriptions, re-querying of outdated records, or attempts to retry failed operations. However, these are all important considerations for a production-level application. + +## Complete examples + + +#### [Main App] +```swift title="AmplifySwiftApp" +import SwiftUI +import Amplify +import AWSAPIPlugin +import AWSCognitoAuthPlugin +import AWSS3StoragePlugin +import Authenticator +import PhotosUI + +@main +struct WorkingWithFilesApp: App { + + init() { + do { + Amplify.Logging.logLevel = .verbose + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels())) + try Amplify.add(plugin: AWSS3StoragePlugin()) + try Amplify.configure(with: .amplifyOutputs) + print("Amplify configured with Auth, API, and Storage plugins") + } catch { + print("Unable to configure Amplify \(error)") + } + } + + var body: some Scene { + WindowGroup { + Authenticator { state in + TabView { + SongView() + .tabItem { + Label("Song", systemImage: "music.note") + } + + PhotoAlbumView() + .tabItem { + Label("PhotoAlbum", systemImage: "photo") + } + } + + } + } + } +} + +struct SignOutButton: View { + var body: some View { + Button("Sign out") { + Task { + await Amplify.Auth.signOut() + } + }.foregroundColor(.black) + } +} + +struct TappedButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .padding(10) + .background(configuration.isPressed ? Color.teal.opacity(0.8) : Color.teal) + .foregroundColor(.white) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } +} + +extension Color { + static let teal = Color(red: 45/255, green: 111/255, blue: 138/255) +} + +struct DimmedBackgroundView: View { + var body: some View { + Color.gray.opacity(0.5) + .ignoresSafeArea() + } +} + +struct ImagePicker: UIViewControllerRepresentable { + @Binding var selectedImage: UIImage? + @Environment(\.presentationMode) var presentationMode + + class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { + let parent: ImagePicker + + init(_ parent: ImagePicker) { + self.parent = parent + } + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { + if let uiImage = info[.originalImage] as? UIImage { + parent.selectedImage = uiImage + } + parent.presentationMode.wrappedValue.dismiss() + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + parent.presentationMode.wrappedValue.dismiss() + } + } + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIImagePickerController { + let imagePicker = UIImagePickerController() + imagePicker.delegate = context.coordinator + return imagePicker + } + + func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext) { + } +} + +struct MultiImagePicker: UIViewControllerRepresentable { + @Binding var selectedImages: [UIImage] + + func makeUIViewController(context: Context) -> PHPickerViewController { + var configuration = PHPickerConfiguration() + configuration.filter = .images + configuration.selectionLimit = 0 + + let picker = PHPickerViewController(configuration: configuration) + picker.delegate = context.coordinator + return picker + } + + func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { + // No need for updates in this case + } + + func makeCoordinator() -> Coordinator { + Coordinator(parent: self) + } + + class Coordinator: PHPickerViewControllerDelegate { + private let parent: MultiImagePicker + + init(parent: MultiImagePicker) { + self.parent = parent + } + + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + picker.dismiss(animated: true, completion: nil) + DispatchQueue.main.async { + self.parent.selectedImages = [] + } + for result in results { + if result.itemProvider.canLoadObject(ofClass: UIImage.self) { + result.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in + if let image = image as? UIImage { + DispatchQueue.main.async { + self.parent.selectedImages.append(image) + } + } + } + } + } + } + } +} +``` + +#### [Song] +```swift title="SongView" +import SwiftUI +import Amplify + +class SongViewModel: ObservableObject { + + @Published var currentSong: Song? = nil + @Published var currentImage: UIImage? = nil + @Published var isLoading: Bool = false + + // Create a song with an associated image + func createSong(name: String, artCover: UIImage) async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + let song = Song(name: name) + + guard let imageData = artCover.pngData() else { + print("Could not get data from image.") + return + } + + // Create the song record + var createdSong = try await Amplify.API.mutate(request: .create(song)).get() + let coverArtPath = "images/\(createdSong.id)" + + // Upload the art cover image + _ = try await Amplify.Storage.uploadData(path: .fromString(coverArtPath), data: imageData).value + + // Update the song record with the image path + createdSong.coverArtPath = coverArtPath + let updatedSong = try await Amplify.API.mutate(request: .update(createdSong)).get() + + await setCurrentSong(updatedSong) + } + + func getSongAndFile(currentSong: Song, imageData: Data) async throws { + // Get the song record + guard var song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { + print("Song may have been deleted, no song by id: ", currentSong.id) + return + } + + guard let coverArtPath = song.coverArtPath else { + print("There is no cover art path to retrieve image") + return + } + + // Download the art cover + let imageData = try await Amplify.Storage.downloadData(path: .fromString(coverArtPath)).value + + let image = UIImage(data: imageData) + } + + // Add or update an image for an associated record + func updateArtCover(artCover: UIImage) async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + + guard var currentSong = currentSong else { + print("There is no song to associated the image with. Create a Song first.") + return + } + + guard let imageData = artCover.pngData() else { + print("Could not get data from UIImage.") + return + } + + let coverArtPath = "images/\(currentSong.id)" + + // Upload the new art image + _ = try await Amplify.Storage.uploadData(path: .fromString(coverArtPath), data: imageData).value + + // Update the song record + currentSong.coverArtPath = coverArtPath + let updatedSong = try await Amplify.API.mutate(request: .update(currentSong)).get() + + await setCurrentSong(updatedSong) + } + + func refreshSongAndArtCover() async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + guard let currentSong = currentSong else { + print("There is no song to refresh the record and image. Create a song first.") + return + } + await setCurrentSong(nil) + await setCurrentImage(nil) + + // Get the song record + guard let song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { + print("Song may have been deleted, no song by id: ", currentSong.id) + return + } + + guard let coverArtPath = song.coverArtPath else { + print("Song does not contain cover art") + await setCurrentSong(song) + await setCurrentImage(nil) + return + } + + // Download the art cover + let imageData = try await Amplify.Storage.downloadData(path: .fromString(coverArtPath)).value + + let image = UIImage(data: imageData) + + await setCurrentSong(song) + await setCurrentImage(image) + } + + func removeImageAssociationFromSong() async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + guard let currentSong = currentSong else { + print("There is no song to remove art cover from it. Create a song first.") + return + } + + // Get the song record + guard var song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { + print("Song may have been deleted, no song by id: ", currentSong.id) + return + } + + guard song.coverArtPath != nil else { + print("There is no cover art path to remove image association") + return + } + + // Set the association to nil and update it + song.coverArtPath = nil + + let updatedSong = try await Amplify.API.mutate(request: .update(song)).get() + + await setCurrentSong(updatedSong) + } + + func removeImageAssociationAndDeleteImage() async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + guard let currentSong = currentSong else { + print("There is no song to remove art cover from it. Create a song first.") + return + } + + // Get the song record + guard var song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { + print("Song may have been deleted, no song by id: ", currentSong.id) + return + } + + guard let coverArtPath = song.coverArtPath else { + print("There is no cover art path to remove image association") + return + } + + // Set the association to nil and update it + song.coverArtPath = nil + let updatedSong = try await Amplify.API.mutate(request: .update(song)).get() + + // Remove the image + try await Amplify.Storage.remove(path: .fromString(coverArtPath)) + + await setCurrentSong(updatedSong) + await setCurrentImage(nil) + } + + func deleteSongAndArtCover() async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + guard let currentSong = currentSong else { + print("There is no song to delete. Create a song first.") + return + } + + // Get the song record + guard var song = try await Amplify.API.query(request: .get(Song.self, byId: currentSong.id)).get() else { + print("Song may have been deleted, no song by id: ", currentSong.id) + return + } + + if let coverArt = song.coverArtPath { + // Remove the image + try await Amplify.Storage.remove(path: .fromString(coverArt)) + } + + // Delete the song record + _ = try await Amplify.API.mutate(request: .delete(song)).get() + + await setCurrentSong(nil) + await setCurrentImage(nil) + } + + @MainActor + func setCurrentSong(_ song: Song?) { + self.currentSong = song + } + + @MainActor + func setCurrentImage(_ image: UIImage?) { + self.currentImage = image + } + + @MainActor + func setIsLoading(_ isLoading: Bool) { + self.isLoading = isLoading + } +} + +struct SongView: View { + + @State private var isImagePickerPresented = false + @State private var songName: String = "" + + @StateObject var viewModel = SongViewModel() + + var body: some View { + NavigationView { + ZStack { + VStack { + SongInformation() + DisplayImage() + OpenImagePickerButton() + SongNameTextField() + CreateOrUpdateSongButton() + AdditionalOperations() + Spacer() + } + .padding() + .sheet(isPresented: $isImagePickerPresented) { + ImagePicker(selectedImage: $viewModel.currentImage) + } + VStack { + IsLoadingView() + } + } + .navigationBarItems(trailing: SignOutButton()) + } + } + + @ViewBuilder + func SongInformation() -> some View { + if let song = viewModel.currentSong { + Text("Song Id: \(song.id)").font(.caption) + if song.name != "" { + Text("Song Name: \(song.name)").font(.caption) + } + } + } + + @ViewBuilder + func DisplayImage() -> some View { + if let image = viewModel.currentImage { + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fit) + } else { + Text("No Image Selected") + .foregroundColor(.gray) + } + + } + + func OpenImagePickerButton() -> some View { + Button("Select \(viewModel.currentImage != nil ? "a new ": "" )song album cover") { + isImagePickerPresented.toggle() + }.buttonStyle(TappedButtonStyle()) + } + + @ViewBuilder + func SongNameTextField() -> some View { + TextField("\(viewModel.currentSong != nil ? "Update": "Enter") song name", text: $songName) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .multilineTextAlignment(.center) + } + + @ViewBuilder + func CreateOrUpdateSongButton() -> some View { + if viewModel.currentSong == nil, let image = viewModel.currentImage { + Button("Save") { + Task { + try? await viewModel.createSong(name: songName, + artCover: image) + } + } + .buttonStyle(TappedButtonStyle()) + .disabled(viewModel.isLoading) + } else if viewModel.currentSong != nil, let image = viewModel.currentImage { + Button("Update") { + Task { + try? await viewModel.updateArtCover(artCover: image) + } + } + .buttonStyle(TappedButtonStyle()) + .disabled(viewModel.isLoading) + } + } + + @ViewBuilder + func AdditionalOperations() -> some View { + if viewModel.currentSong != nil { + VStack { + Button("Refresh") { + Task { + try? await viewModel.refreshSongAndArtCover() + } + }.buttonStyle(TappedButtonStyle()) + Button("Remove association from song") { + Task { + try? await viewModel.removeImageAssociationFromSong() + } + }.buttonStyle(TappedButtonStyle()) + Button("Remove association and delete image") { + Task { + try? await viewModel.removeImageAssociationAndDeleteImage() + } + }.buttonStyle(TappedButtonStyle()) + Button("Delete song and art cover") { + Task { + try? await viewModel.deleteSongAndArtCover() + } + songName = "" + }.buttonStyle(TappedButtonStyle()) + }.disabled(viewModel.isLoading) + } + } + + @ViewBuilder + func IsLoadingView() -> some View { + if viewModel.isLoading { + ZStack { + DimmedBackgroundView() + ProgressView() + } + } + } +} + +struct SongView_Previews: PreviewProvider { + static var previews: some View { + SongView() + } +} +``` + +#### [Photo Album] +```swift title="PhotoAlbumView" +import SwiftUI +import Amplify +import Photos + +class PhotoAlbumViewModel: ObservableObject { + @Published var currentImages: [UIImage] = [] + @Published var currentAlbum: PhotoAlbum? = nil + @Published var isLoading: Bool = false + + // Create a record with multiple associated files + func createPhotoAlbum(name: String, photos: [UIImage]) async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + + let imagesData = photos.compactMap { $0.pngData() } + guard !imagesData.isEmpty else { + print("Could not get data from [UIImage]") + return + } + + // Create the photo album record + let album = PhotoAlbum(name: name) + var createdAlbum = try await Amplify.API.mutate(request: .create(album)).get() + + // Upload the photo album images + let imagePaths = await withTaskGroup(of: String?.self) { group in + for imageData in imagesData { + group.addTask { + let path = "images/\(album.id)-\(UUID().uuidString)" + do { + _ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value + return path + } catch { + print("Failed with error:", error) + return nil + } + } + } + + var imagePaths: [String?] = [] + for await imagePath in group { + imagePaths.append(imagePath) + } + return imagePaths.compactMap { $0 } + } + + // Update the album with the image paths + createdAlbum.imagePaths = imagePaths + let updatedAlbum = try await Amplify.API.mutate(request: .update(createdAlbum)).get() + + await setCurrentAlbum(updatedAlbum) + } + + // Create a record with a single associated file + func createPhotoAlbum(name: String, photo: UIImage) async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + + guard let imageData = photo.pngData() else { + print("Could not get data from UIImage") + return + } + + // Create the photo album record + let album = PhotoAlbum(name: name) + var createdAlbum = try await Amplify.API.mutate(request: .create(album)).get() + + // Upload the photo album image + let path = "images/\(album.id)-\(UUID().uuidString)" + _ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value + + // Update the album with the image path + createdAlbum.imagePaths = [path] + let updatedAlbum = try await Amplify.API.mutate(request: .update(createdAlbum)).get() + + await setCurrentAlbum(updatedAlbum) + } + + // Add new file to an associated record + func addAdditionalPhotos(_ photo: UIImage) async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + + guard let currentAlbum = currentAlbum else { + print("There is no album to associated the images with. Create an Album first.") + return + } + + guard let imageData = photo.pngData() else { + print("Could not get data from UIImage.") + return + } + + // Upload the new photo album image + let path = "images/\(currentAlbum.id)-\(UUID().uuidString)" + _ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value + + // Get the latest album + guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { + print("Album may have been deleted, no album by id: ", currentAlbum.id) + return + } + + guard var imagePaths = album.imagePaths else { + print("Album does not contain images") + await setCurrentAlbum(album) + await setCurrentImages([]) + return + } + + // Add new to the existing paths + imagePaths.append(path) + + // Update the album with the image paths + album.imagePaths = imagePaths + let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() + + await setCurrentAlbum(updatedAlbum) + } + + func replaceLastImage(_ photo: UIImage) async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + + guard let currentAlbum = currentAlbum else { + print("There is no album to associated the images with. Create an Album first.") + return + } + + guard let imageData = photo.pngData() else { + print("Could not get data from UIImage") + return + } + + // Upload the new photo album image + let path = "images/\(currentAlbum.id)-\(UUID().uuidString)" + _ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value + + // Update the album with the image paths + var album = currentAlbum + if var imagePaths = album.imagePaths { + imagePaths.removeLast() + imagePaths.append(path) + album.imagePaths = imagePaths + } else { + album.imagePaths = [path] + } + + let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() + + await setCurrentAlbum(updatedAlbum) + } + + // Query a record and retrieve the associated files + func refreshAlbumAndPhotos() async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + guard let currentAlbum = currentAlbum else { + print("There is no album to associate the images with. Create an Album first.") + return + } + + await setCurrentAlbum(nil) + await setCurrentImages([]) + + // Get the song record + guard let album = try await Amplify.API.query( + request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { + print("Album may have been deleted, no album by id: ", currentAlbum.id) + return + } + + guard let imagePathsOptional = album.imagePaths else { + print("Album does not contain images") + await setCurrentAlbum(album) + await setCurrentImages([]) + return + } + + let imagePaths = imagePathsOptional.compactMap { $0 } + + // Download the photos + let images = await withTaskGroup(of: UIImage?.self) { group in + for path in imagePaths { + group.addTask { + do { + let imageData = try await Amplify.Storage.downloadData(path: .fromString(path)).value + return UIImage(data: imageData) + } catch { + print("Failed with error:", error) + return nil + } + } + } + + var images: [UIImage?] = [] + for await image in group { + images.append(image) + } + return images.compactMap { $0 } + } + + await setCurrentAlbum(album) + await setCurrentImages(images) + } + + // Remove the file association + func removeStorageAssociationsFromAlbum() async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + guard let currentAlbum = currentAlbum else { + print("There is no album to associated the images with. Create an Album first.") + return + } + + // Get the album record + guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { + print("Album may have been deleted, no album by id: ", currentAlbum.id) + return + } + + guard let imagePaths = album.imagePaths, !imagePaths.isEmpty else { + print("There are no images to remove association") + return + } + + // Set the association to nil and update it + album.imagePaths = nil + let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() + + await setCurrentAlbum(updatedAlbum) + } + + // Remove the record association and delete the files + func removeStorageAssociationsAndDeletePhotos() async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + + guard let currentAlbum = currentAlbum else { + print("There is no album to associated the images with. Create an Album first.") + return + } + + // Get the album record + guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { + print("Album may have been deleted, no album by id: ", currentAlbum.id) + return + } + + guard let imagePathsOptional = album.imagePaths else { + print("Album does not contain images") + await setCurrentAlbum(album) + await setCurrentImages([]) + return + } + let imagePaths = imagePathsOptional.compactMap { $0 } + + // Set the associations to nil and update it + album.imagePaths = nil + let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() + + // Remove the photos + await withTaskGroup(of: Void.self) { group in + for path in imagePaths { + group.addTask { + do { + try await Amplify.Storage.remove(path: .fromString(path)) + } catch { + print("Failed with error:", error) + } + } + } + + for await _ in group { + } + } + + await setCurrentAlbum(updatedAlbum) + await setCurrentImages([]) + } + + // Delete record and all associated files + func deleteAlbumAndPhotos() async throws { + await setIsLoading(true) + defer { + Task { + await setIsLoading(false) + } + } + + guard let currentAlbum = currentAlbum else { + print("There is no album to associated the images with. Create an Album first.") + return + } + + // Get the album record + guard let album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { + print("Album may have been deleted, no album by id: ", currentAlbum.id) + return + } + + guard let imagePathsOptional = album.imagePaths else { + print("Album does not contain images") + + // Delete the album record + _ = try await Amplify.API.mutate(request: .delete(album)) + + await setCurrentAlbum(nil) + await setCurrentImages([]) + return + } + + let imagePaths = imagePathsOptional.compactMap { $0 } + + // Remove the photos + await withTaskGroup(of: Void.self) { group in + for path in imagePaths { + group.addTask { + do { + try await Amplify.Storage.remove(path: .fromString(path)) + } catch { + print("Failed with error:", error) + } + } + } + + for await _ in group { + } + } + + // Delete the album record + _ = try await Amplify.API.mutate(request: .delete(album)).get() + + await setCurrentAlbum(nil) + await setCurrentImages([]) + } + + @MainActor + func setCurrentAlbum(_ album: PhotoAlbum?) { + self.currentAlbum = album + } + + @MainActor + func setCurrentImages(_ images: [UIImage]) { + self.currentImages = images + } + + @MainActor + func setIsLoading(_ isLoading: Bool) { + self.isLoading = isLoading + } +} + +struct PhotoAlbumView: View { + @State private var isImagePickerPresented: Bool = false + @State private var albumName: String = "" + @State private var isLastImagePickerPresented = false + @State private var lastImage: UIImage? = nil + @StateObject var viewModel = PhotoAlbumViewModel() + + var body: some View { + NavigationView { + ZStack { + VStack { + AlbumInformation() + DisplayImages() + OpenImagePickerButton() + PhotoAlbumNameTextField() + CreateOrUpdateAlbumButton() + AdditionalOperations() + } + .padding() + .sheet(isPresented: $isImagePickerPresented) { + MultiImagePicker(selectedImages: $viewModel.currentImages) + } + .sheet(isPresented: $isLastImagePickerPresented) { + ImagePicker(selectedImage: $lastImage) + } + VStack { + IsLoadingView() + } + } + .navigationBarItems(trailing: SignOutButton()) + } + } + + @ViewBuilder + func AlbumInformation() -> some View { + if let album = viewModel.currentAlbum { + Text("Album Id: \(album.id)").font(.caption) + if album.name != "" { + Text("Album Name: \(album.name)").font(.caption) + } + } + } + + @ViewBuilder + func DisplayImages() -> some View { + // Display selected images + ScrollView(.horizontal) { + HStack { + ForEach($viewModel.currentImages, id: \.self) { image in + Image(uiImage: image.wrappedValue) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 100, height: 100) + } + } + } + if $viewModel.currentImages.isEmpty { + Text("No Images Selected") + .foregroundColor(.gray) + } + } + + func OpenImagePickerButton() -> some View { + // Button to open the image picker + Button("Select \(!viewModel.currentImages.isEmpty ? "new " : "")photo album images") { + isImagePickerPresented.toggle() + }.buttonStyle(TappedButtonStyle()) + } + + @ViewBuilder + func PhotoAlbumNameTextField() -> some View { + TextField("\(viewModel.currentAlbum != nil ? "Update": "Enter") album name", text: $albumName) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .multilineTextAlignment(.center) + } + + @ViewBuilder + func CreateOrUpdateAlbumButton() -> some View { + if viewModel.currentAlbum == nil, !viewModel.currentImages.isEmpty { + Button("Save") { + Task { + try? await viewModel.createPhotoAlbum(name: albumName, + photos: viewModel.currentImages) + } + } + .buttonStyle(TappedButtonStyle()) + .disabled(viewModel.isLoading) + } else if viewModel.currentAlbum != nil { + Button("Select \(lastImage != nil ? "another ": "")photo to replace last photo in the album") { + isLastImagePickerPresented.toggle() + } + .buttonStyle(TappedButtonStyle()) + .disabled(viewModel.isLoading) + + if let lastImage = lastImage { + Image(uiImage: lastImage) + .resizable() + .aspectRatio(contentMode: .fit) + Button("Replace last image in album with above") { + Task { + try? await viewModel.replaceLastImage(lastImage) + self.lastImage = nil + try? await viewModel.refreshAlbumAndPhotos() + } + } + .buttonStyle(TappedButtonStyle()) + .disabled(viewModel.isLoading) + Button("Append above image to album") { + Task { + try? await viewModel.addAdditionalPhotos(lastImage) + self.lastImage = nil + try? await viewModel.refreshAlbumAndPhotos() + } + } + .buttonStyle(TappedButtonStyle()) + .disabled(viewModel.isLoading) + } + } + } + + @ViewBuilder + func AdditionalOperations() -> some View { + if viewModel.currentAlbum != nil { + VStack { + Button("Refresh") { + Task { + try? await viewModel.refreshAlbumAndPhotos() + } + }.buttonStyle(TappedButtonStyle()) + Button("Remove associations from album") { + Task { + try? await viewModel.removeStorageAssociationsFromAlbum() + try? await viewModel.refreshAlbumAndPhotos() + } + }.buttonStyle(TappedButtonStyle()) + Button("Remove association and delete photos") { + Task { + try? await viewModel.removeStorageAssociationsAndDeletePhotos() + try? await viewModel.refreshAlbumAndPhotos() + } + }.buttonStyle(TappedButtonStyle()) + Button("Delete album and images") { + Task { + try? await viewModel.deleteAlbumAndPhotos() + } + albumName = "" + }.buttonStyle(TappedButtonStyle()) + }.disabled(viewModel.isLoading) + } + } + + @ViewBuilder + func IsLoadingView() -> some View { + if viewModel.isLoading { + ZStack { + DimmedBackgroundView() + ProgressView() + } + } + } +} + +struct PhotoAlbumView_Previews: PreviewProvider { + static var previews: some View { + PhotoAlbumView() + } +} +``` + + + + + +#### [Single File (TS)] + +```ts title="src/App.tsx" + +import "./App.css"; +import { generateClient } from "aws-amplify/api"; +import { uploadData, getUrl, remove } from "aws-amplify/storage"; +import React, { useState } from "react"; +import type { Schema } from "../amplify/data/resource"; +import "@aws-amplify/ui-react/styles.css"; +import { + type WithAuthenticatorProps, + withAuthenticator, +} from "@aws-amplify/ui-react"; +import { Amplify } from "aws-amplify"; +import outputs from "../amplify_outputs.json"; + +Amplify.configure(outputs); + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +type Song = Schema["Song"]["type"]; + +function App({ signOut, user }: WithAuthenticatorProps) { + + const [currentSong, setCurrentSong] = useState(null); + + // Used to display image for current song: + const [currentImageUrl, setCurrentImageUrl] = useState< + string | null | undefined + >(""); + + async function createSongWithImage(e: React.ChangeEvent) { + if (!e.target.files) return; + const file = e.target.files[0]; + try { + + // Create the API record: + const response = await client.models.Song.create({ + name: `My first song`, + }); + + const song = response.data; + + if (!song) return; + + // Upload the Storage file: + const result = await uploadData({ + path: `images/${song.id}-${file.name}`, + data: file, + options: { + contentType: "image/png", // contentType is optional + }, + }).result; + + // Add the file association to the record: + const updateResponse = await client.models.Song.update({ + id: song.id, + coverArtPath: result?.path, + }); + + const updatedSong = updateResponse.data; + setCurrentSong(updatedSong); + + // If the record has no associated file, we can return early. + if (!updatedSong?.coverArtPath) return; + + // Retrieve the file's signed URL: + const signedURL = await getUrl({ path: updatedSong.coverArtPath }); + + setCurrentImageUrl(signedURL.url.toString()); + } catch (error) { + console.error("Error create song / file:", error); + } + } + + // Upload image, add to song, retrieve signed URL and retrieve the image. + // Also updates image if one already exists. + async function addNewImageToSong(e: React.ChangeEvent) { + + if (!currentSong) return; + + if (!e.target.files) return; + + const file = e.target.files[0]; + + try { + // Upload the Storage file: + const result = await uploadData({ + path: `images/${currentSong.id}-${file.name}`, + data: file, + options: { + contentType: "image/png", // contentType is optional + }, + }).result; + + // Add the file association to the record: + const response = await client.models.Song.update({ + id: currentSong.id, + coverArtPath: result?.path, + }); + + const updatedSong = response.data; + + setCurrentSong(updatedSong); + + // If the record has no associated file, we can return early. + if (!updatedSong?.coverArtPath) return; + + // Retrieve the file's signed URL: + const signedURL = await getUrl({ path: updatedSong.coverArtPath }); + setCurrentImageUrl(signedURL.url.toString()); + + } catch (error) { + console.error("Error uploading image / adding image to song: ", error); + } + } + + async function getImageForCurrentSong() { + if (!currentSong) return; + + try { + // Query the record to get the file path: + const response = await client.models.Song.get({ + id: currentSong.id, + }); + + const song = response.data; + + // If the record has no associated file, we can return early. + if (!song?.coverArtPath) return; + + // Retrieve the signed URL: + const signedURL = await getUrl({ path: song.coverArtPath }); + setCurrentImageUrl(signedURL.url.toString()); + } catch (error) { + console.error("Error getting song / image:", error); + } + } + + // Remove the file association, continue to persist both file and record + async function removeImageFromSong() { + + if (!currentSong) return; + + try { + const response = await client.models.Song.get({ + id: currentSong.id, + }); + + const song = response.data; + + // If the record has no associated file, we can return early. + if (!song?.coverArtPath) return; + + const updatedSong = await client.models.Song.update({ + id: song.id, + coverArtPath: null, + }); + + // If successful, the response here will be `null`: + setCurrentSong(updatedSong.data); + + setCurrentImageUrl(updatedSong.data?.coverArtPath); + + } catch (error) { + console.error("Error removing image from song: ", error); + } + } + + // Remove the record association and delete the file + async function deleteImageForCurrentSong() { + + if (!currentSong) return; + + try { + const response = await client.models.Song.get({ + id: currentSong.id, + }); + + const song = response?.data; + + // If the record has no associated file, we can return early. + if (!song?.coverArtPath) return; + + // Remove associated file from record + const updatedSong = await client.models.Song.update({ + id: song.id, + coverArtPath: null, + }); + + // Delete the file from S3: + await remove({ path: song.coverArtPath }); + + // If successful, the response here will be `null`: + setCurrentSong(updatedSong.data); + + setCurrentImageUrl(updatedSong.data?.coverArtPath); + + } catch (error) { + console.error("Error deleting image: ", error); + } + } + + // Delete both file and record + async function deleteCurrentSongAndImage() { + + if (!currentSong) return; + try { + const response = await client.models.Song.get({ + id: currentSong.id, + }); + const song = response.data; + + // If the record has no associated file, we can return early. + if (!song?.coverArtPath) return; + + await remove({ path: song.coverArtPath }); + + // Delete the record from the API: + await client.models.Song.delete({ id: song.id }); + + clearLocalState(); + + } catch (error) { + console.error("Error deleting song: ", error); + } + } + + function clearLocalState() { + setCurrentSong(null); + setCurrentImageUrl(""); + } + + return ( + <> +

    Hello {user?.username}

    + +
    + + + + + + + +
    + + ); +} + +export default withAuthenticator(App); + +``` + +#### [Multi-File (TS)] + +```ts title="src/App.tsx" + +import "./App.css"; +import { generateClient } from "aws-amplify/api"; +import { uploadData, getUrl, remove } from "aws-amplify/storage"; +import React, { useState } from "react"; +import type { Schema } from "../amplify/data/resource"; +import "@aws-amplify/ui-react/styles.css"; +import { + type WithAuthenticatorProps, + withAuthenticator, +} from "@aws-amplify/ui-react"; +import { Amplify } from "aws-amplify"; +import outputs from "../amplify_outputs.json"; + +Amplify.configure(outputs); + +// Generating the client +const client = generateClient({ + authMode: "apiKey", +}); + +type PhotoAlbum = Schema["PhotoAlbum"]["type"]; + +function App({ signOut, user }: WithAuthenticatorProps) { + // State to hold the recognized text + const [currentPhotoAlbum, setCurrentPhotoAlbum] = useState( + null + ); + + // Used to display images for current photoAlbum: + const [currentImages, setCurrentImages] = useState< + (string | null | undefined)[] | null | undefined + >([]); + + async function createPhotoAlbumWithFirstImage( + e: React.ChangeEvent + ) { + if (!e.target.files) return; + + const file = e.target.files[0]; + + try { + // Create the API record: + const response = await client.models.PhotoAlbum.create({ + name: `My first photoAlbum`, + }); + + const photoAlbum = response.data; + + if (!photoAlbum) return; + + // Upload the Storage file: + const result = await uploadData({ + path: `images/${photoAlbum.id}-${file.name}`, + data: file, + options: { + contentType: "image/png", // contentType is optional + }, + }).result; + + const updatePhotoAlbumDetails = { + id: photoAlbum.id, + imagePaths: [result.path], + }; + + // Add the file association to the record: + const updateResponse = await client.models.PhotoAlbum.update({ + id: photoAlbum.id, + imagePaths: [result.path], + }); + + const updatedPhotoAlbum = updateResponse.data; + + setCurrentPhotoAlbum(updatedPhotoAlbum); + + // If the record has no associated file, we can return early. + if (!updatedPhotoAlbum?.imagePaths?.length) return; + + // Retrieve the file's signed URL: + const signedURL = await getUrl({ + path: updatedPhotoAlbum.imagePaths[0]!, + }); + setCurrentImages([signedURL.url.toString()]); + } catch (error) { + console.error("Error create photoAlbum / file:", error); + } + } + + async function createPhotoAlbumWithMultipleImages( + e: React.ChangeEvent + ) { + if (!e.target.files) return; + + try { + const photoAlbumDetails = { + name: `My first photoAlbum`, + }; + + // Create the API record: + const response = await client.models.PhotoAlbum.create({ + name: `My first photoAlbum`, + }); + + const photoAlbum = response.data; + + if (!photoAlbum) return; + + // Upload all files to Storage: + const imagePaths = await Promise.all( + Array.from(e.target.files).map(async (file) => { + const result = await uploadData({ + path: `images/${photoAlbum.id}-${file.name}`, + data: file, + options: { + contentType: "image/png", // contentType is optional + }, + }).result; + + return result.path; + }) + ); + + // Add the file association to the record: + const updateResponse = await client.models.PhotoAlbum.update({ + id: photoAlbum.id, + imagePaths: imagePaths, + }); + const updatedPhotoAlbum = updateResponse.data; + + setCurrentPhotoAlbum(updatedPhotoAlbum); + + // If the record has no associated file, we can return early. + if (!updatedPhotoAlbum?.imagePaths?.length) return; + + // Retrieve signed urls for all files: + const signedUrls = await Promise.all( + updatedPhotoAlbum.imagePaths.map( + async (path) => await getUrl({ path: path! }) + ) + ); + + if (!signedUrls) return; + setCurrentImages(signedUrls.map((signedUrl) => signedUrl.url.toString())); + } catch (error) { + console.error("Error create photoAlbum / file:", error); + } + } + + async function addNewImagesToPhotoAlbum( + e: React.ChangeEvent + ) { + if (!currentPhotoAlbum) return; + + if (!e.target.files) return; + + try { + // Upload all files to Storage: + const newimagePaths = await Promise.all( + Array.from(e.target.files).map(async (file) => { + const result = await uploadData({ + path: `images/${currentPhotoAlbum.id}-${file.name}`, + data: file, + options: { + contentType: "image/png", // contentType is optional + }, + }).result; + + return result.path; + }) + ); + + // Query existing record to retrieve currently associated files: + const queriedResponse = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, + }); + + const photoAlbum = queriedResponse.data; + + if (!photoAlbum?.imagePaths) return; + + // Merge existing and new file paths: + const updatedimagePaths = [...newimagePaths, ...photoAlbum.imagePaths]; + + // Update record with merged file associations: + const response = await client.models.PhotoAlbum.update({ + id: currentPhotoAlbum.id, + imagePaths: updatedimagePaths, + }); + + const updatedPhotoAlbum = response.data; + setCurrentPhotoAlbum(updatedPhotoAlbum); + + // If the record has no associated file, we can return early. + if (!updatedPhotoAlbum?.imagePaths) return; + + // Retrieve signed urls for merged image paths: + const signedUrls = await Promise.all( + updatedPhotoAlbum?.imagePaths.map( + async (path) => await getUrl({ path: path! }) + ) + ); + + if (!signedUrls) return; + + setCurrentImages(signedUrls.map((signedUrl) => signedUrl.url.toString())); + } catch (error) { + console.error( + "Error uploading image / adding image to photoAlbum: ", + error + ); + } + } + + // Replace last image associated with current photoAlbum: + async function updateLastImage(e: React.ChangeEvent) { + if (!currentPhotoAlbum) return; + + if (!e.target.files) return; + + const file = e.target.files[0]; + + try { + // Upload new file to Storage: + const result = await uploadData({ + path: `images/${currentPhotoAlbum.id}-${file.name}`, + data: file, + options: { + contentType: "image/png", // contentType is optional + }, + }).result; + + const newFilePath = result.path; + + // Query existing record to retrieve currently associated files: + const queriedResponse = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, + }); + + const photoAlbum = queriedResponse.data; + + if (!photoAlbum?.imagePaths?.length) return; + + // Retrieve last image path: + const [lastImagePath] = photoAlbum.imagePaths.slice(-1); + + // Remove last file association by path + const updatedimagePaths = [ + ...photoAlbum.imagePaths.filter((path) => path !== lastImagePath), + newFilePath, + ]; + + // Update record with updated file associations: + const response = await client.models.PhotoAlbum.update({ + id: currentPhotoAlbum.id, + imagePaths: updatedimagePaths, + }); + + const updatedPhotoAlbum = response.data; + + setCurrentPhotoAlbum(updatedPhotoAlbum); + + // If the record has no associated file, we can return early. + if (!updatedPhotoAlbum?.imagePaths) return; + + // Retrieve signed urls for merged image paths: + const signedUrls = await Promise.all( + updatedPhotoAlbum?.imagePaths.map( + async (path) => await getUrl({ path: path! }) + ) + ); + + if (!signedUrls) return; + + setCurrentImages(signedUrls.map((signedUrl) => signedUrl.url.toString())); + } catch (error) { + console.error( + "Error uploading image / adding image to photoAlbum: ", + error + ); + } + } + + async function getImagesForPhotoAlbum() { + if (!currentPhotoAlbum) { + return; + } + try { + // Query the record to get the file paths: + const response = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, + }); + const photoAlbum = response.data; + + // If the record has no associated files, we can return early. + if (!photoAlbum?.imagePaths) return; + + // Retrieve the signed URLs for the associated images: + const signedUrls = await Promise.all( + photoAlbum.imagePaths.map(async (imagePath) => { + if (!imagePath) return; + return await getUrl({ path: imagePath }); + }) + ); + + setCurrentImages( + signedUrls.map((signedUrl) => signedUrl?.url.toString()) + ); + } catch (error) { + console.error("Error getting photoAlbum / image:", error); + } + } + + // Remove the file associations, continue to persist both files and record + async function removeImagesFromPhotoAlbum() { + if (!currentPhotoAlbum) return; + + try { + const response = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, + }); + + const photoAlbum = response.data; + + // If the record has no associated file, we can return early. + if (!photoAlbum?.imagePaths) return; + + const updatedPhotoAlbum = await client.models.PhotoAlbum.update({ + id: photoAlbum.id, + imagePaths: null, + }); + + // If successful, the response here will be `null`: + setCurrentPhotoAlbum(updatedPhotoAlbum.data); + setCurrentImages(updatedPhotoAlbum.data?.imagePaths); + } catch (error) { + console.error("Error removing image from photoAlbum: ", error); + } + } + + // Remove the record association and delete the file + async function deleteImagesForCurrentPhotoAlbum() { + if (!currentPhotoAlbum) return; + + try { + const response = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, + }); + + const photoAlbum = response.data; + + // If the record has no associated files, we can return early. + if (!photoAlbum?.imagePaths) return; + + // Remove associated files from record + const updateResponse = await client.models.PhotoAlbum.update({ + id: photoAlbum.id, + imagePaths: null, // Set the file association to `null` + }); + + const updatedPhotoAlbum = updateResponse.data; + + // Delete the files from S3: + await Promise.all( + photoAlbum?.imagePaths.map(async (imagePath) => { + if (!imagePath) return; + await remove({ path: imagePath }); + }) + ); + + // If successful, the response here will be `null`: + setCurrentPhotoAlbum(updatedPhotoAlbum); + setCurrentImages(null); + } catch (error) { + console.error("Error deleting image: ", error); + } + } + + // Delete both files and record + async function deleteCurrentPhotoAlbumAndImages() { + if (!currentPhotoAlbum) return; + + try { + const response = await client.models.PhotoAlbum.get({ + id: currentPhotoAlbum.id, + }); + + const photoAlbum = response.data; + + if (!photoAlbum) return; + + await client.models.PhotoAlbum.delete({ + id: photoAlbum.id, + }); + + setCurrentPhotoAlbum(null); + + // If the record has no associated file, we can return early. + if (!photoAlbum?.imagePaths) return; + + await Promise.all( + photoAlbum?.imagePaths.map(async (imagePath) => { + if (!imagePath) return; + await remove({ path: imagePath }); + }) + ); + + clearLocalState(); + } catch (error) { + console.error("Error deleting photoAlbum: ", error); + } + } + + function clearLocalState() { + setCurrentPhotoAlbum(null); + setCurrentImages([]); + } + + return ( +
    +

    Hello {user?.username}!

    +

    + Current PhotoAlbum: {currentPhotoAlbum?.id} +

    + +
    + + + + + + + +
    + +
    + + + + + +
    + +
    + {currentImages && + currentImages.map((url, idx) => { + if (!url) return undefined; + return ( + Storage file + ); + })} +
    +
    + ); +} + +export default withAuthenticator(App); + +``` + +
    + +--- + +--- +title: "Add custom real-time subscriptions" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-07-16T16:09:37.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/custom-subscription/" +--- + +Create a custom real-time subscription for any mutation to enable PubSub use cases. + +## Define a custom subscription + +For every custom subscription, you need to set: +1. the mutation(s) that should trigger a subscription event, +2. a return type that matches the subscribed mutations' return type, +3. authorization rules. + +Optionally, you can set filter arguments to customize the server-side subscription filter rules. + +Use `a.subscription()` to define your custom subscription in your **amplify/data/resource.ts** file: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + // Message type that's used for this PubSub sample + Message: a.customType({ + content: a.string().required(), + channelName: a.string().required() + }), + + // Message publish mutation + publish: a.mutation() + .arguments({ + channelName: a.string().required(), + content: a.string().required() + }) + .returns(a.ref('Message')) + .handler(a.handler.custom({ entry: './publish.js' })) + .authorization(allow => [allow.publicApiKey()]), + + // highlight-start + // Subscribe to incoming messages + receive: a.subscription() + // subscribes to the 'publish' mutation + .for(a.ref('publish')) + // subscription handler to set custom filters + .handler(a.handler.custom({entry: './receive.js'})) + // authorization rules as to who can subscribe to the data + .authorization(allow => [allow.publicApiKey()]), + // highlight-end + + // A data model to manage channels + Channel: a.model({ + name: a.string(), + }).authorization(allow => [allow.publicApiKey()]), +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema +}); +``` + +For this example, we're building a generic PubSub capability. This requires us to convert the arguments for `publish` into the `Channel`'s format. Create a new `publish.js` file in your **amplify/data/** folder with the following contents: + +```js title="amplify/data/publish.js" +// This handler simply passes through the arguments of the mutation through as the result +export function request() { + return {} +} + +/** + * @param {import('@aws-appsync/utils').Context} ctx + */ +export function response(ctx) { + return ctx.args +} +``` + +Next, create a new `receive.js` file in your **amplify/data/** folder to define handlers for your subscription. In this case, it'll just be a simple passthrough. In the next section, we'll explore how to use this handler to construct more advanced subscription filters. + +> **Info:** **Note:** We're planning a developer experience enhancement in the near future that'll create this passthrough under the hood. + +```ts title="amplify/data/receive.js" +export function request() { + return {}; +} + +export const response = (ctx) => { + return ctx.result; +}; +``` + +## Subscribe to custom subscriptions client-side + +From your generated Data client, you can find all your custom subscriptions under `client.subscriptions`. Subscribe using the `.subscribe()` function and then use the `next` function to handle incoming events. + +```ts +import { generateClient } from 'aws-amplify/data' +import type { Schema } from '../amplify/data/resource' + +const client = generateClient() + +const sub = client.subscriptions.receive() + .subscribe({ + next: event => { + console.log(event) + } + } +) +``` + +You can try publishing an event using the custom mutation to test the real-time subscription. + +```ts +client.mutations.publish({ + channelName: "world", + content: "My first message!" +}) +``` + +Your subscription event should be received and logs the payload into your app's developer console. Unsubscribe your subscription to disconnect using the `unsubscribe()` function. + +```ts +sub.unsubscribe() +``` + +## (Optionally) Add server-side subscription filters + +> **Info:** **Note:** The custom subscription `.authorization()` modifier does not support the `owner` strategy. This differs from model subscriptions and may result in a subscription event being broadcast to a larger audience. + +You can add subscription filters by adding arguments to the custom subscriptions. + +If you want to customize the filters, modify the subscription handler. For this example, we'll allow a customer to pass in a `namePrefix` parameter that allows the end users to only receive channel events in channels that start with the `namePrefix`. + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; + +const schema = a.schema({ + Channel: a.model({ + name: a.string(), + }).authorization(allow => [allow.publicApiKey()]), + + Message: a.customType({ + content: a.string().required(), + channelName: a.string().required() + }), + + publish: a.mutation() + .arguments({ + channelName: a.string().required(), + content: a.string().required() + }) + .returns(a.ref('Message')) + .handler(a.handler.custom({ entry: './publish.js' })) + .authorization(allow => [allow.publicApiKey()]), + + receive: a.subscription() + .for(a.ref('publish')) + // highlight-next-line + .arguments({ namePrefix: a.string() }) + .handler(a.handler.custom({entry: './receive.js'})) + .authorization(allow => [allow.publicApiKey()]) +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema +}); +``` + +In your handler, you can set custom subscription filters based on arguments passed into the custom subscription. For this example, create a new **receive.js** file alongside the **amplify/data/resource.ts** file: + +```js +import { util, extensions } from "@aws-appsync/utils" + +// Subscription handlers must return a `null` payload on the request +export function request() { return { payload: null } } + +/** + * @param {import('@aws-appsync/utils').Context} ctx + */ +export function response(ctx) { + const filter = { + channelName: { + beginsWith: ctx.args.namePrefix + } + } + + extensions.setSubscriptionFilter(util.transform.toSubscriptionFilter(filter)) + + return null +} +``` + +--- + +--- +title: "Connect to existing data sources" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-06T18:10:09.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/connect-to-existing-data-sources/" +--- + + + +--- + +--- +title: "Connect your app to existing MySQL and PostgreSQL database" +section: "build-a-backend/data/connect-to-existing-data-sources" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-14T20:52:33.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/connect-to-existing-data-sources/connect-postgres-mysql-database/" +--- + +Amplify's native integration supports any MySQL or Postgres database, no matter if they're hosted on AWS within a VPC or outside of AWS with a 3rd party hosted database provider. + +You must create a connection string using the following database information to get started: + +- Database **hostname** +- Database **port** +- Database **username** +- Database **user password** +- Database **name** + +## Step 1 - Set secrets for database connection + +First, provide all the database connection information as secrets, you can use the Amplify sandbox's secret functionality to set them or go to the Amplify console to set secrets in a shared environment: + +```bash showLineNumbers={false} +npx ampx sandbox secret set SQL_CONNECTION_STRING +``` + +#### [MySQL] +**Connection string format for MySQL** +```console showLineNumbers={false} +mysql://user:password@hostname:port/db-name +``` + +#### [PostgreSQL] +**Connection string format for PostgreSQL** +```console showLineNumbers={false} +postgres://user:password@hostname:port/db-name +``` + +## Step 2 - Generate TypeScript representation of your database schema + +Run the following command to generate the Data schema with your database connection information. It'll infer an `a.model()` representation for **all database tables that have primary key specified**. + +```bash +npx ampx generate schema-from-database --connection-uri-secret SQL_CONNECTION_STRING --out amplify/data/schema.sql.ts +``` + + + +If your RDS database exists within a VPC, it must be configured to be `Publicly accessible`. This does not mean the instance needs to be accessible from all IP addresses on the Internet, but this flag is required to allow your local machine to connect via an **Inbound Rule** rather than being inside your VPC or connected to the VPC via VPN. + +**To generate the TypeScript representation of your database schema:** + +If your database is protected by a VPC, you will need to add an **Inbound Rule** for the database port from your local IP address. The `npx ampx generate schema-from-database` command connects to your database from your local workstation to read schema information. + +If you are connecting to an RDS Proxy, the machine you run the `generate schema-from-database` command must be in the same VPC as the proxy itself, or you must connect to it via VPN. Simply opening an **Inbound Rule** for the database port is not sufficient. + +**To connect to your database during runtime:** +When you specify connection information, we'll compare the hostname you supply against a list of RDS instances, clusters, and proxies in your account in the same region as your project. If we find a match, we'll automatically detect the VPC configuration for your database and provision a SQL Lambda function to connect to the database and retrieve the schema. + +The VPC security group in which your database instance, cluster, or proxy is installed must have **Inbound rules** that allow traffic from the following TCP ports: + +1. The specified database port (e.g., 3306 for MySQL databases or 5432 for Postgres databases). +2. Port 443 (HTTPS) to allow the Lambda function to connect to AWS Systems Manager to retrieve configuration parameters. + +Finally, the security group must have **Outbound rules** that allow traffic on those same ports from the security group itself. + + + + + +When creating new DynamoDB-backed data models via `a.model()`, a set of a implicit fields, such as `id`, `createdAt`, and `updatedAt` are added by default. When connecting to an existing SQL database, these fields are not implicitly added as they are not part of the underlying data source. If `createdAt` and `updatedAt` are valid columns in the underlying database table, then Amplify Data will automatically populate those fields with their respective values upon create and update mutations. + + + +
    RDS Proxy for improved connectivity + +Consider adding an RDS Proxy in front of the cluster to manage database connections. + +When using Amplify GraphQL API with a relational database like Amazon RDS, each query from your application needs to open a separate connection to the database. + +If there are a large number of queries occurring concurrently, it can exceed the connection limit on the database and result in errors like "Too many connections". To avoid this, Amplify can use an RDS Proxy when connecting your GraphQL API to a database. + +The RDS Proxy acts as an intermediary sitting in front of your database. Instead of each application query opening a direct connection to the database, they will connect through the Proxy. The Proxy helps manage and pool these connections to avoid overwhelming your database cluster. This improves the availability of your API, allowing more queries to execute concurrently without hitting connection limits. + +However, there is a tradeoff of increased latency - queries may take slightly longer as they wait for an available connection from the Proxy pool. There are also additional costs associated with using RDS Proxy. Please refer to the [pricing page for RDS Proxy](https://aws.amazon.com/rds/proxy/pricing/) to learn more. + +
    + +
    Connecting to a database with a custom SSL certificate + +Amplify creates an [AWS Lambda](https://aws.amazon.com/lambda) function using a Node.js runtime to connect your AppSync API to your SQL database. The Lambda function connects to the database using Secure Socket Layer (SSL) or Transport Layer Security (TLS) to protect data in transit. Amplify automatically uses the correct root certificate authority (CA) certificates for Amazon RDS databases, and the Node.js runtime includes root CAs from [well-known certificate providers](https://github.com/nodejs/node/issues/4175) to connect to non-RDS databases. + +However, if your database uses a custom or self-signed SSL certificate, you can upload the PEM-encoded public CA certificate of 4 KB or less to your Amplify project as a secret when you generate the database configuration, and specify that secret when generating the schema from your database: + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox secret set CUSTOM_SSL_CERT < /path/to/custom/ssl/public-ca-cert.pem +npx ampx generate schema-from-database --connection-uri-secret SQL_CONNECTION_STRING --ssl-cert-secret CUSTOM_SSL_CERT --out amplify/data/schema.sql.ts +``` + +The Lambda function will then use the specified root CA to validate connections to the database. + +When deploying your app to production, you need to [add the PEM-encoded public CA certificate as a secret](/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/#set-secrets). Make sure to add the certificate with the same secret name you used in the sandbox environment. For example, we used `CUSTOM_SSL_CERT` above. Make sure to preserve the newlines and the `------BEGIN CERTIFICATE------` and `------END CERTIFICATE------` delimiters in the value. Finally, make sure the size of the entire value does not exceed 4KB. + +
    + +This creates a new **schema.sql.ts** with a schema reflecting the types of your database. **Do not edit the schema.sql.ts file directly**. Import the schema to your **amplify/data/resource.ts** file and apply any additive changes there. This ensures that you can continuously regenerate the TypeScript schema representation of your SQL database without losing any additive changes that you apply out-of-band. + +```ts +import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; +// highlight-start +import { schema as generatedSqlSchema } from './schema.sql'; + +// Add a global authorization rule +const sqlSchema = generatedSqlSchema.authorization(allow => allow.guest()) +// highlight-end + +// Relational database sources can coexist with DynamoDB tables managed by Amplify. +const schema = a.schema({ + Todo: a.model({ + content: a.string(), + }).authorization(allow => [allow.guest()]) +}); + +// Use the a.combine() operator to stitch together the models backed by DynamoDB +// and the models backed by Postgres or MySQL databases. +// highlight-next-line +const combinedSchema = a.combine([schema, sqlSchema]); + +// Don't forget to update your client types to take into account the types from +// both schemas. +// highlight-next-line +export type Schema = ClientSchema; + +export const data = defineData({ + // Update the data definition to use the combined schema, instead of just + // your DynamoDB-backed schema + // highlight-next-line + schema: combinedSchema +}); +``` + +## Step 3 - Fine-grained authorization rules + +Use the `.setAuthorization()` modifier to set model-level and field-level authorization rules for the SQL-backed data models. Review [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz) for detailed authorization rule options. + +```ts +// Add an authorization rule to the schema +// highlight-start +const sqlSchema = generatedSqlSchema.setAuthorization((models) => [ + // Model-level authorization rules + models.event.authorization((allow) => [allow.publicApiKey()]), + // Field-level authorization rules + models.event.fields.id.authorization(allow => [allow.publicApiKey(), allow.guest()]), + models.event.fields.created_at.authorization(allow => [allow.publicApiKey(), allow.guest()]) +]); +// highlight-end +``` + +## Step 4 - Deploy your Data resources using the cloud sandbox + +Finally, you can now validate your Data resources with your cloud sandbox: + +```bash +npx ampx sandbox +``` + +On the client side, you can now make create, read, update, delete, and subscribe to your SQL-backed data models: + +```ts +const { data: events } = await client.models.event.list() +``` + +## Step 5 - Configuring database connection for production + +When deploying your app to production, you need to [add the database connection string as a secret](/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/#set-secrets). Make sure to add the appropriate database connection string with the same secret name you used in the sandbox environment. For example, we used `SQL_CONNECTION_STRING` above. + + + +## Rename generated models and fields + +To improve the ergonomics of your API, you might want to rename the generate fields or types to better accommodate your use case. Use the `renameModels()` modifier to rename the auto-inferred data models. + +```ts +// Rename models or fields to be more idiomatic for frontend code +const sqlSchema = generatedSqlSchema.authorization(allow => allow.guest()) +// highlight-start + .renameModels(() => [ + //βŒ„βŒ„βŒ„βŒ„βŒ„ existing model name based on table name + ['event', 'Event'] + // ^^^^^^ renamed data model name + ]) + // highlight-end +``` + +## Add relationships between tables + +```ts +const sqlSchema = generatedSqlSchema + .authorization(allow => allow.guest()) +// highlight-start + .setRelationships((models) => [ + models.Note.relationships({ + comments: a.hasMany("Comment", "note_id"), + }), + models.Comment.relationships({ + note: a.belongsTo("Note", "note_id") + }) + ]); +// highlight-end +``` + +## Add custom queries, mutations, subscriptions auto-generated SQL data schema + +Use the `.addToSchema(...)` to add in additional queries, mutations, and subscriptions to your auto-generated SQL data schema. + +> **Info:** Note: you can't add additional data models via `a.model()`. They should be exclusively generated via `npx ampx generate schema-from-database`. + +### Use an inline SQL statement as a query or mutation handler + +```ts +// Add custom mutations or queries that execute SQL statements +const sqlSchema = generatedSqlSchema.authorization(allow => allow.guest()) +// highlight-start + .addToSchema({ + listEventsWithDecodedLatLong: a.query() + // reference custom types added to the schema + .returns(a.ref("EventWithDecodedCoord").array()) + .handler(a.handler.inlineSql( + `SELECT + id, + name, + address, + ST_X(geom) AS longitude, + ST_Y(geom) AS latitude + FROM locations;` + )) + .authorization(allow => [allow.guest()]), + + // Define custom types to provide end-to-end typed results + // for custom queries / mutations + EventWithDecodedCoord: a.customType({ + id: a.integer(), + name: a.string(), + address: a.string(), + longitude: a.float(), + latitude: a.float(), + }) + }) +// highlight-end +``` + +### Reference an existing SQL file as a query or mutation handler + +You can define the different SQL handlers in separate `.sql` files and reference them in your custom queries / mutations. + +First, configure the custom query or mutation in your **amplify/data/resource.ts** file: +```ts +const sqlSchema = generatedSqlSchema.authorization(allow => allow.guest()) + .addToSchema({ + createNewLocationWithLongLat: a.mutation() + .arguments({ + lat: a.float().required(), + long: a.float().required(), + name: a.string().required(), + address: a.string().required() + }) + .returns(a.json().array()) + .authorization(allow => allow.authenticated()) + // highlight-next-line + .handler(a.handler.sqlReference('./createNewLocationWithLongLat.sql')) + }) +``` + +Next, add a corresponding sql file to handle the request: + +#### [MySQL] +```sql title="createNewLocationWithLongLat.sql" +INSERT INTO locations (name, address, geom) +VALUES (:name, :address, ST_GEOMFROMTEXT(CONCAT('POINT (', :long, ' ', :lat, ')'), 4326)); +``` + +#### [PostgreSQL] +```sql title="createNewLocationWithLongLat.sql" +INSERT INTO locations (name, address, geom) +VALUES (:name, :address, ST_SetSRID(ST_MakePoint(:long, :lat), 4326)) +``` + +> **Info:** The return type for custom queries and mutations expecting to return row data from SQL statements must be an array of the corresponding model. This is true even for custom `get` queries, where a single row is expected. +> +> **Example** +> +> ```ts +getPostBySlug: a + .query() + .arguments({ + slug: a.string().required(), + }) + // highlight-start + .returns(a.ref("Post").array()) + // highlight-end + .handler( + a.handler.inlineSql(` + SELECT id, title, slug, content, created_at, updated_at + FROM posts + WHERE slug = :slug; + `) + ) +``` + +## How does it work? + +The Amplify uses AWS Lambda functions to enable features like querying data from your database. To work properly, these Lambda functions need access to common logic and dependencies. + +Amplify provides this shared code in the form of Lambda Layers. You can think of Lambda Layers as a package of reusable runtime code that Lambda functions can reference. + +When you deploy an Amplify API, it will create two Lambda functions: + +### SQL Lambda + +This allows you to query and write data to your database from your API. + + + **NOTE:** If the database is in a VPC, this Lambda function will be deployed + in the same VPC as the database. The usage of Amazon Virtual Private Cloud + (VPC) or VPC peering, with AWS Lambda functions will incur additional charges + as explained, this comes with an additional cost as explained on the [Amazon + Elastic Compute Cloud (EC2) on-demand pricing + page](https://aws.amazon.com/ec2/pricing/on-demand/). + + +### Updater Lambda + +This automatically keeps the SQL Lambda up-to-date by managing its Lambda Layers. + +A Lambda layer that includes all the core SQL connection logic lives within the AWS Amplify service account but is executed within your AWS account, when invoked by the SQL Lambda. This allows the Amplify service team to own the ongoing maintenance and security enhancements of the SQL connection logic. + +This allows the Amplify team to maintain and enhance the SQL Layer without needing direct access to your Lambdas. If updates to the Layer are needed, the Updater Lambda will receive a signal from Amplify and automatically update the SQL Lambda with the latest Layer. + +### Mapping of SQL data types to field types for auto-generated schema + +> **Warning:** **Note:** MySQL does not support time zone offsets in date time or timestamp fields. Instead, we will convert these values to `datetime`, without the offset. Unlike MySQL, PostgreSQL does support date time or timestamp values with an offset. + +| SQL | Mapped field types | +|--------------------|--------------| +| **String** | | +| char | a.string() | +| varchar | a.string() | +| tinytext | a.string() | +| text | a.string() | +| mediumtext | a.string() | +| longtext | a.string() | +| **Geometry** | | +| geometry | a.string() | +| point | a.string() | +| linestring | a.string() | +| geometryCollection | a.string() | +| **Numeric** | | +| smallint | a.integer() | +| mediumint | a.integer() | +| int | a.integer() | +| integer | a.integer() | +| bigint | a.integer() | +| tinyint | a.integer() | +| float | a.float() | +| double | a.float() | +| decimal | a.float() | +| dec | a.float() | +| numeric | a.float() | +| **Date and Time** | | +| date | a.date() | +| datetime | a.datetime() | +| timestamp | a.datetime() | +| time | a.time() | +| year | a.integer() | +| **Binary** | | +| binary | a.string() | +| varbinary | a.string() | +| tinyblob | a.string() | +| blob | a.string() | +| mediumblob | a.string() | +| longblob | a.string() | +| **Others** | | +| bool | a.boolean() | +| boolean | a.boolean() | +| bit | a.integer() | +| json | a.json() | +| enum | a.enum() | + +## Troubleshooting + +### Debug Mode + +To return the actual SQL error instead of a generic error from underlying API responses, an environment variable `DEBUG_MODE` can be set to `true` on the Amplify-generated SQL Lambda function. You can find this Lambda function in the AWS Lambda console with the naming convention of: `--SQLLambdaFunction`. + +### My SQL table doesn't get generated when running `npx ampx generate schema-from-database` + +This is likely because the table doesn't have a designated primary key. A primary key is required for `npx ampx generate schema-from-database` to infer the table structure and create a create, read, update, and delete API. + +--- + +--- +title: "Connect to external Amazon DynamoDB data sources" +section: "build-a-backend/data/connect-to-existing-data-sources" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-11-19T09:34:45.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/connect-to-existing-data-sources/connect-external-ddb-table/" +--- + +The `a.model()` data model allows you to define a GraphQL schema for an AWS AppSync API where models are backed by DynamoDB Tables managed by Amplify. The generated schema also provides queries and mutations to the Amplify Data client. However, you may want to connect to an external DynamoDB table and execute custom business logic against it instead. + +> **Info:** Using an external DynamoDB table as a data source may be useful if you need to leverage patterns such as single table design. + +In the following sections, we walk through the steps to add and use an external DynamoDB table as a data source for your API: + +1. Set up your Amazon DynamoDB table +2. Add your Amazon DynamoDB table as a data source +3. Define custom queries and mutations +4. Configure custom business logic handler code +5. Invoke custom queries or mutations + +## Step 1 - Set up your Amazon DynamoDB table + +For the purpose of this guide we will define a `Post` type and create an external DynamoDB table that will store records for it. In Amplify Gen 2, `customType` adds a type to the schema that is not backed by an Amplify-generated DynamoDB table. + +With the `Post` type defined, it can then be referenced as the return type when defining your custom queries and mutations. + +First, add the `Post` custom type to your schema: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Todo: a + .model({ + content: a.string(), + }) + .authorization(allow => [allow.publicApiKey()]), + // highlight-start + Post: a.customType({ + id: a.id().required(), + author: a.string().required(), + title: a.string(), + content: a.string(), + url: a.string(), + ups: a.integer(), + downs: a.integer(), + version: a.integer(), + }), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +> **Info:** **NOTE:** To comply with the GraphQL spec, at least one query is required for a schema to be valid. Otherwise, deployments will fail with a schema error. The Amplify Data schema is auto-generated with a `Todo` model and corresponding queries under the hood. You can leave the `Todo` model in the schema until you add the first custom query to the schema in the next steps. + +Once the deployment successfully completes, you need to create a DynamoDB table that will serve as your external data source. You can create this table using the AWS Console, AWS CLI, or AWS CDK. For this example, we'll create it using the DynamoDB console: + +1. Navigate to the [DynamoDB console](https://console.aws.amazon.com/dynamodb/). + +2. Choose **Create table**. + +3. For **Table name**, enter `PostTable`. + +4. For **Partition key**, enter `id` and select **String** as the type. + +5. Leave **Sort key** empty (we don't need one for this example). + +6. Keep the default settings for the remaining options and choose **Create table**. + +Alternatively, you can create the table using the AWS CLI: + +```bash +aws dynamodb create-table \ + --table-name PostTable \ + --attribute-definitions \ + AttributeName=id,AttributeType=S \ + --key-schema \ + AttributeName=id,KeyType=HASH \ + --provisioned-throughput \ + ReadCapacityUnits=5,WriteCapacityUnits=5 +``` + +You now have a new DynamoDB table named `PostTable` that exists independently of your Amplify-generated resources. You will use this table as the data source for your custom queries and mutations. + +## Step 2 - Add your Amazon DynamoDB table as a data source + +In your `amplify/backend.ts` file, add your DynamoDB table as a data source for your API: + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend"; +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; +import { aws_dynamodb } from "aws-cdk-lib"; + +export const backend = defineBackend({ + auth, + data, +}); + +// highlight-start +const externalDataSourcesStack = backend.createStack("MyExternalDataSources"); + +const externalTable = aws_dynamodb.Table.fromTableName( + externalDataSourcesStack, + "MyExternalPostTable", + "PostTable" +); + +backend.data.addDynamoDbDataSource( + "ExternalPostTableDataSource", + externalTable +); +// highlight-end +``` + +## Step 3 - Define custom queries and mutations + +Now that your DynamoDB table has been added as a data source, you can reference it in custom queries and mutations using the `a.handler.custom()` modifier which accepts the name of the data source and an entry point for your resolvers. + +Use the following code examples to add `addPost`, `getPost`, `updatePost`, and `deletePost` as custom queries and mutations to your schema: + +#### [addPost] +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Post: a.customType({ + author: a.string().required(), + title: a.string(), + content: a.string(), + url: a.string(), + ups: a.integer(), + downs: a.integer(), + version: a.integer(), + }), + // highlight-start + addPost: a + .mutation() + .arguments({ + id: a.id(), + author: a.string().required(), + title: a.string(), + content: a.string(), + url: a.string(), + }) + .returns(a.ref("Post")) + .authorization(allow => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + dataSource: "ExternalPostTableDataSource", + entry: "./addPost.js", + }) + ), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +#### [getPost] +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Post: a.customType({ + author: a.string().required(), + title: a.string(), + content: a.string(), + url: a.string(), + ups: a.integer(), + downs: a.integer(), + version: a.integer(), + }), + // highlight-start + getPost: a + .query() + .arguments({ id: a.id().required() }) + .returns(a.ref("Post")) + .authorization(allow => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + dataSource: "ExternalPostTableDataSource", + entry: "./getPost.js", + }) + ), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +#### [updatePost] +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Post: a.customType({ + author: a.string().required(), + title: a.string(), + content: a.string(), + url: a.string(), + ups: a.integer(), + downs: a.integer(), + version: a.integer(), + }), + // highlight-start + updatePost: a + .mutation() + .arguments({ + id: a.id().required(), + author: a.string(), + title: a.string(), + content: a.string(), + url: a.string(), + expectedVersion: a.integer().required(), + }) + .returns(a.ref("Post")) + .authorization(allow => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + dataSource: "ExternalPostTableDataSource", + entry: "./updatePost.js", + }) + ), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +#### [deletePost] +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a.schema({ + Post: a.customType({ + author: a.string().required(), + title: a.string(), + content: a.string(), + url: a.string(), + ups: a.integer(), + downs: a.integer(), + version: a.integer(), + }), + // highlight-start + deletePost: a + .mutation() + .arguments({ id: a.id().required(), expectedVersion: a.integer() }) + .returns(a.ref("Post")) + .authorization(allow => [allow.publicApiKey()]) + .handler( + a.handler.custom({ + dataSource: "ExternalPostTableDataSource", + entry: "./deletePost.js", + }) + ), + // highlight-end +}); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +## Step 4 - Configure custom business logic handler code + +Next, create the following files in your `amplify/data` folder and use the code examples to define custom resolvers for the custom queries and mutations added to your schema from the previous step. These are AppSync JavaScript resolvers + +#### [addPost] +```js title="amplify/data/addPost.js" +import { util } from "@aws-appsync/utils"; +import * as ddb from "@aws-appsync/utils/dynamodb"; + +export function request(ctx) { + const item = { ...ctx.arguments, ups: 1, downs: 0, version: 1 }; + const key = { id: ctx.args.id ?? util.autoId() }; + return ddb.put({ key, item }); +} + +export function response(ctx) { + return ctx.result; +} +``` + +#### [getPost] +```js title="amplify/data/getPost.js" +import * as ddb from "@aws-appsync/utils/dynamodb"; + +export function request(ctx) { + return ddb.get({ key: { id: ctx.args.id } }); +} + +export const response = (ctx) => ctx.result; +``` + +#### [updatePost] +```js title="amplify/data/updatePost.js" +import { util } from "@aws-appsync/utils"; +import * as ddb from "@aws-appsync/utils/dynamodb"; + +export function request(ctx) { + const { id, expectedVersion, ...rest } = ctx.args; + const values = Object.entries(rest).reduce((obj, [key, value]) => { + obj[key] = value ?? ddb.operations.remove(); + return obj; + }, {}); + + return ddb.update({ + key: { id }, + condition: { version: { eq: expectedVersion } }, + update: { ...values, version: ddb.operations.increment(1) }, + }); +} + +export function response(ctx) { + const { error, result } = ctx; + if (error) { + util.appendError(error.message, error.type); + } + return result; +} +``` + +#### [deletePost] +```js title="amplify/data/deletePost.js" +import { util } from "@aws-appsync/utils"; +import * as ddb from "@aws-appsync/utils/dynamodb"; + +export function request(ctx) { + let condition = null; + if (ctx.args.expectedVersion) { + condition = { + or: [ + { id: { attributeExists: false } }, + { version: { eq: ctx.args.expectedVersion } }, + ], + }; + } + return ddb.remove({ key: { id: ctx.args.id }, condition }); +} + +export function response(ctx) { + const { error, result } = ctx; + if (error) { + util.appendError(error.message, error.type); + } + return result; +} +``` + +## Step 5 - Invoke custom queries or mutations + +From your generated Data client, you can find all your custom queries and mutations under the client.queries. and client.mutations. APIs respectively. + +#### [addPost] +```ts title="App.tsx" +const { data, errors } = await client.mutations.addPost({ + title: "My Post", + content: "My Content", + author: "Chris", +}); +``` + +#### [getPost] +```ts title="App.tsx" +const { data, errors } = await client.queries.getPost({ + id: "" +}); +``` + +#### [updatePost] +```ts title="App.tsx" +const { data, errors } = await client.mutations.updatePost({ + id: "", + title: "An Updated Post", + expectedVersion: 1, +}); +``` + +#### [deletePost] +```ts title="App.tsx" +const { data, errors } = await client.mutations.deletePost({ + id: "", +}); +``` + +## Conclusion + +In this guide, you’ve added an external DynamoDB table as a data source to an Amplify GraphQL API and defined custom queries and mutations, handled by AppSync JS resolvers, to manipulate Post items in an external DynamoDB table using the Amplify Gen 2 Data client. + +To clean up, you can delete your sandbox by accepting the prompt when terminating the sandbox process in your terminal. Alternatively, you can also use the AWS Amplify console to manage and delete sandbox environments. + +To delete your external DynamoDB table, you can navigate to the AppSync console and click on the name of the table in the data sources list. This takes you to the DynamoDB console where you can delete the table. + +## All DynamoDB operations & example resolvers + +### GetItem + +[Reference](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-getitem) - The `GetItem` request lets you tell the AWS AppSync DynamoDB function to make a `GetItem` request to DynamoDB, and enables you to specify: + +- The key of the item in DynamoDB +- Whether to use a consistent read or not + +**Example:** + +```js +export function request(ctx) { + const { foo, bar } = ctx.args; + return { + operation: 'GetItem', + key: util.dynamodb.toMapValues({ foo, bar }), + consistentRead: true + }; +} +``` + +### PutItem + +[PutItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-putitem) - The `PutItem` request mapping document lets you tell the AWS AppSync DynamoDB function to make a `PutItem` request to DynamoDB, and enables you to specify the following: + +- The key of the item in DynamoDB +- The full contents of the item (composed of key and attributeValues) +- Conditions for the operation to succeed + +**Example:** + +```js +import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const { foo, bar, ...values } = ctx.args; + return { + operation: 'PutItem', + key: util.dynamodb.toMapValues({ foo, bar }), + attributeValues: util.dynamodb.toMapValues(values) + }; +} +``` + +### UpdateItem + +[UpdateItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-updateitem) - The `UpdateItem` request enables you to tell the AWS AppSync DynamoDB function to make a `UpdateItem` request to DynamoDB and allows you to specify the following: + +- The key of the item in DynamoDB +- An update expression describing how to update the item in DynamoDB +- Conditions for the operation to succeed + +**Example:** + +```js +import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const { id } = ctx.args; + return { + operation: 'UpdateItem', + key: util.dynamodb.toMapValues({ id }), + update: { + expression: 'ADD #voteField :plusOne, version :plusOne', + expressionNames: { '#voteField': 'upvotes' }, + expressionValues: { ':plusOne': { N: 1 } } + } + }; +} +``` + +### DeleteItem + +[DeleteItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-deleteitem) - The `DeleteItem` request lets you tell the AWS AppSync DynamoDB function to make a `DeleteItem` request to DynamoDB, and enables you to specify the following: + +- The key of the item in DynamoDB +- Conditions for the operation to succeed + +**Example:** + +```js +import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + return { + operation: 'DeleteItem', + key: util.dynamodb.toMapValues({ id: ctx.args.id }) + }; +} +``` + +### Query + +[Query](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-query) - The Query request object lets you tell the AWS AppSync DynamoDB resolver to make a Query request to DynamoDB, and enables you to specify the following: + +- Key expression +- Which index to use +- Any additional filter +- How many items to return +- Whether to use consistent reads +- query direction (forward or backward) +- Pagination token + +**Example:** + +```js +import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const { owner } = ctx.args; + return { + operation: 'Query', + query: { + expression: 'ownerId = :ownerId', + expressionValues: util.dynamodb.toMapValues({ ':ownerId': owner }) + }, + index: 'owner-index' + }; +} +``` +### Scan + +[Scan](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-scan) - The `Scan` request lets you tell the AWS AppSync DynamoDB function to make a `Scan` request to DynamoDB, and enables you to specify the following: + +- A filter to exclude results +- Which index to use +- How many items to return +- Whether to use consistent reads +- Pagination token +- Parallel scans + +**Example:** + +```js +export function request(ctx) { + return { operation: 'Scan' }; +} +``` +### Sync + +[Sync](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-sync) - The `Sync` request object lets you retrieve all the results from a DynamoDB table and then receive only the data altered since your last query (the delta updates). `Sync` requests can only be made to versioned DynamoDB data sources. You can specify the following: + +- A filter to exclude results + +- How many items to return + +- Pagination Token + +- When your last Sync operation was started + +**Example:** + +```js +export function request(ctx) { + const { nextToken, lastSync } = ctx.args; + return { operation: 'Sync', limit: 100, nextToken, lastSync }; +} +``` + +### BatchGetItem + +[BatchGetItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-batch-get-item) - The `BatchGetItem` request object lets you tell the AWS AppSync DynamoDB function to make a `BatchGetItem` request to DynamoDB to retrieve multiple items, potentially across multiple tables. For this request object, you must specify the following: + +- The table names where to retrieve the items from + +- The keys of the items to retrieve from each table + +The DynamoDB `BatchGetItem` limits apply and **no condition expression** can be provided. + +**Example:** +```js +import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const { authorId, postId } = ctx.args; + return { + operation: 'BatchGetItem', + tables: { + authors: [util.dynamodb.toMapValues({ authorId })], + posts: [util.dynamodb.toMapValues({ authorId, postId })], + }, + }; +} +``` + +### BatchDeleteItem + +[BatchDeleteItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-batch-delete-item) - The BatchDeleteItem request object lets you tell the AWS AppSync DynamoDB function to make a BatchWriteItem request to DynamoDB to delete multiple items, potentially across multiple tables. For this request object, you must specify the following: + +- The table names where to delete the items from + +- The keys of the items to delete from each table + +The DynamoDB `BatchWriteItem` limits apply and **no condition expression** can be provided. + +**Example:** + +```js +import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const { authorId, postId } = ctx.args; + return { + operation: 'BatchDeleteItem', + tables: { + authors: [util.dynamodb.toMapValues({ authorId })], + posts: [util.dynamodb.toMapValues({ authorId, postId })], + }, + }; +} +``` + +### BatchPutItem + +[BatchPutItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-batch-put-item) - The `BatchPutItem` request object lets you tell the AWS AppSync DynamoDB function to make a `BatchWriteItem` request to DynamoDB to put multiple items, potentially across multiple tables. For this request object, you must specify the following: + +- The table names where to put the items in + +- The full items to put in each table + +The DynamoDB `BatchWriteItem` limits apply and **no condition expression** can be provided. + +**Example:** + +```js +import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const { authorId, postId, name, title } = ctx.args; + return { + operation: 'BatchPutItem', + tables: { + authors: [util.dynamodb.toMapValues({ authorId, name })], + posts: [util.dynamodb.toMapValues({ authorId, postId, title })], + }, + }; +} +``` + +### TransactGetItems + +[TransactGetItems](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-transact-get-items) - The `TransactGetItems` request object lets you to tell the AWS AppSync DynamoDB function to make a `TransactGetItems` request to DynamoDB to retrieve multiple items, potentially across multiple tables. For this request object, you must specify the following: + +- The table name of each request item where to retrieve the item from + +- The key of each request item to retrieve from each table + +The DynamoDB `TransactGetItems` limits apply and **no condition expression** can be provided. + +**Example:** + +```js +import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const { authorId, postId } = ctx.args; + return { + operation: 'TransactGetItems', + transactItems: [ + { + table: 'posts', + key: util.dynamodb.toMapValues({ postId }), + }, + { + table: 'authors', + key: util.dynamodb.toMapValues({ authorId }), + }, + ], + }; +} +``` + +### TransactWriteItems + +[TransactWriteItems](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-transact-write-items) - The `TransactWriteItems` request object lets you tell the AWS AppSync DynamoDB function to make a `TransactWriteItems` request to DynamoDB to write multiple items, potentially to multiple tables. For this request object, you must specify the following: + +- The destination table name of each request item + +- The operation of each request item to perform. There are four types of operations that are supported: `PutItem`, `UpdateItem`, `DeleteItem`, and `ConditionCheck` + +- The key of each request item to write + +The DynamoDB `TransactWriteItems` limits apply. + +**Example:** + +```js +import { util } from '@aws-appsync/utils'; + +export function request(ctx) { + const { authorId, postId, title, description, oldTitle, authorName } = ctx.args; + return { + operation: 'TransactWriteItems', + transactItems: [ + { + table: 'posts', + operation: 'PutItem', + key: util.dynamodb.toMapValues({ postId }), + attributeValues: util.dynamodb.toMapValues({ title, description }), + condition: util.transform.toDynamoDBConditionExpression({ + title: { eq: oldTitle }, + }), + }, + { + table: 'authors', + operation: 'UpdateItem', + key: util.dynamodb.toMapValues({ authorId }), + update: { + expression: 'SET authorName = :name', + expressionValues: util.dynamodb.toMapValues({ ':name': authorName }), + }, + }, + ], + }; +} +``` + +--- + +--- +title: "Connect to data from Server-side Runtimes" +section: "build-a-backend/data" +platforms: ["android", "angular", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-06-19T16:13:00.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/connect-from-server-runtime/" +--- + + + +--- + +--- +title: "Next.js server runtime" +section: "build-a-backend/data/connect-from-server-runtime" +platforms: ["android", "angular", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-06-19T16:13:00.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/connect-from-server-runtime/nextjs-server-runtime/" +--- + +This guide walks through how you can connect to Amplify Data from Next.js Server-side Runtimes (SSR). For Next.js applications, Amplify provides first-class support for the [App Router (React Server Components, Route Handlers, and Server Actions)](https://nextjs.org/docs/app), the [Pages Router (Components, API Routes)](https://nextjs.org/docs/pages), and [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware). + +Before you begin, you will need: + +- [A Next.js application created](/[platform]/start/quickstart/) +- [Installed and configured Amplify libraries for Next.js](/nextjs/build-a-backend/server-side-rendering/) +- [Deployed Amplify Data resources](/[platform]/build-a-backend/data/set-up-data/), or directly using [AWS AppSync](https://aws.amazon.com/appsync/) + +## Connect to Amplify Data from a Next.js server runtime + +Connecting to Amplify Data will include choosing the correct data client for Next.js server runtimes, generating the data client, and then calling the API. + +### Step 1 - Choose the correct Data client for Next.js server runtimes + +Amplify offers two specialized data clients for Next.js server runtimes (from `@aws-amplify/adapter-nextjs/data`) that you should use depending whether you retrieve the user tokens using [`cookies`](https://nextjs.org/docs/app/api-reference/functions/cookies) or [`NextRequest`](https://nextjs.org/docs/app/api-reference/functions/next-request) and [`NextResponse`](https://nextjs.org/docs/app/api-reference/functions/next-response): + +- `generateServerClientUsingCookies()` πŸͺ generates a data client with the Next.js `cookies` function from `next/headers`. Each API request dynamically refetches the cookies at runtime. +- `generateServerClientUsingReqRes()` 🌐 generates a data client requiring `NextRequest` and `NextResponse` provided to an `runWithAmplifyServerContext` function to prevent token contamination. + +Choose the correct data client based on your Next.js Router (App or Pages) and then the use case: + +#### [App Router] + +| Use case | Required Data client | +| ---------------------- | --------------------------------------- | +| React Server Component | `generateServerClientUsingCookies()` πŸͺ | +| Server Actions | `generateServerClientUsingCookies()` πŸͺ | +| Route Handler | `generateServerClientUsingCookies()` πŸͺ | +| Middleware | `generateServerClientUsingReqRes()` 🌐 | + +#### [Pages Router] + +**Pages Router** + +| Use case | Required Data client | +| -------------------------- | -------------------------------------- | +| Server-side component code | `generateServerClientUsingReqRes()` 🌐 | +| API Route | `generateServerClientUsingReqRes()` 🌐 | +| Middleware | `generateServerClientUsingReqRes()` 🌐 | + +### Step 2 - Generate the Data client for Next.js server runtimes + +#### [generateServerClientUsingCookies() πŸͺ] + +To generate a Data client for the Next.js server runtime using cookies, you need to provide both your Amplify configuration and the cookies function from Next.js. + +```ts +import { type Schema } from '@/amplify/data/resource'; +import { generateServerClientUsingCookies } from '@aws-amplify/adapter-nextjs/data'; +import outputs from '@/amplify_outputs.json'; +import { cookies } from 'next/headers'; + +export const cookieBasedClient = generateServerClientUsingCookies({ + config: outputs, + cookies, +}); +``` + + + +We recommend you generate Amplify Data's server client in a utility file. Then, import the generated client in your Next.js React Server Components, Server Actions, or Route Handlers. + + + +#### [generateServerClientUsingReqRes() 🌐] + +To generate a data client for the Next.js server runtime using `NextRequest` and `NextResponse`, you only need to provide your Amplify configuration. When making the individual API requests, you will need to pass the config to the [`runWithAmplifyServerContext`](/[platform]/build-a-backend/server-side-rendering) function to pass in the cookies from request and response variables. + +```ts +import { type Schema } from '@/amplify/data/resource'; +import { createServerRunner } from '@aws-amplify/adapter-nextjs'; +import { generateServerClientUsingReqRes } from '@aws-amplify/adapter-nextjs/data'; +import outputs from '@/amplify_outputs.json'; + +export const { runWithAmplifyServerContext } = createServerRunner({ + config: outputs, +}); + +export const reqResBasedClient = generateServerClientUsingReqRes({ + config: outputs, +}); +``` + + + +We recommend you generate the server Data client in a utility file. Then, import the generated client in your Next.js Middleware, component's server runtime code, and API Routes. + + + +### Step 3 - Call API using generated server Data clients + +You can make any available query or mutation request with the generated server data clients; however, note that subscriptions are not available within server runtimes. + +#### [generateServerClientUsingCookies() πŸͺ] + +Import the cookie-based server Data client in your Next.js React Server Component code and make your API requests. + +```ts +import { type Schema } from '@/amplify/data/resource'; +import { generateServerClientUsingCookies } from '@aws-amplify/adapter-nextjs/data'; +import outputs from '@/amplify_outputs.json'; +import { cookies } from 'next/headers'; + +export const cookieBasedClient = generateServerClientUsingCookies({ + config: outputs, + cookies, +}); + +const fetchTodos = async () => { + const { data: todos, errors } = await cookieBasedClient.models.Todo.list(); + + if (!errors) { + return todos; + } +}; +``` + +#### [generateServerClientUsingReqRes() 🌐] + +Import the NextRequest/NextResponse-based server Data client in your Next.js server runtime code and make your API requests within the `runWithAmplifyServerContext` function. Review [Server-side Rendering](/[platform]/build-a-backend/server-side-rendering) to learn more about creating an Amplify server context. + +For example, in a Next.js Pages Router API route, use the `req` and `res` parameters from the `handler` function with `runWithAmplifyServerContext`: + +```ts +import { type Schema } from '@/amplify/data/resource'; +import type { NextApiRequest, NextApiResponse } from 'next'; +import { + runWithAmplifyServerContext, + reqResBasedClient, +} from '@/utils/amplifyServerUtils'; + +type ResponseData = { + todos: Schema['Todo']['type'][]; +}; + +export default async function handler( + request: NextApiRequest, + response: NextApiResponse +) { + const todos = await runWithAmplifyServerContext({ + nextServerContext: { request, response }, + operation: async (contextSpec) => { + const { data: todos } = await reqResBasedClient.models.Todo.list( + contextSpec + ); + return todos; + }, + }); + + response.status(200).json({ todos }); +} +``` + +--- + +--- +title: "Nuxt.js server runtime" +section: "build-a-backend/data/connect-from-server-runtime" +platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] +gen: 2 +last-updated: "2024-06-06T19:49:33.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/connect-from-server-runtime/nuxtjs-server-runtime/" +--- + +This guide walks through how you can connect to Amplify Data from Nuxt.js Server-side Runtime (SSR). For Nuxt.js applications, Amplify provides first-class support for [Routing (Pages)](https://nuxt.com/docs/getting-started/routing) , [API Routes](https://nuxt.com/docs/guide/directory-structure/server#server-routes) , and [Middleware](https://nuxt.com/docs/guide/directory-structure/server#server-middleware). + +Before you begin, you will need: + +- [A Nuxt.js application created](https://nuxt.com/docs/getting-started/installation) +- [Deployed Amplify Data resources](/[platform]/build-a-backend/data/set-up-data/), or directly using [AWS AppSync](https://aws.amazon.com/appsync/) + +## Connect to Amplify Data from a Nuxt.js server runtime + +Connecting to Amplify Data will include setting up the AmplifyAPIs Plugin with the `runWithAmplifyServerContext` adapter, using the `useNuxtApp()` composable, setting up the Amplify server context utility and then using the `runAmplifyApi` function to call the API in an isolated server context. + +### Step 1 - Set up the AmplifyAPIs Plugin + +Nuxt 3 offers universal rendering by default, where your data fetching logic may be executed on both the client and server sides. Amplify offers APIs that are capable of running within a server context to support use cases such as server-side rendering (SSR) and static site generation (SSG), though Amplify's client-side APIs and server-side APIs of Amplify are slightly different. You can set up an AmplifyAPIs plugin to make your data fetching logic run smoothly across the client and server. To learn more about how to use Amplify categories APIs in server side rendering, refer to this [documentation](/[platform]/build-a-backend/server-side-rendering/). + +1. Create a `plugins` directory under the root of your Nuxt project. +2. Create two files `01.amplify-apis.client.ts` and `01.amplify-apis.server.ts` under the `plugins` directory. + +In these files, you will register both client-specific and server-specific Amplify APIs that you will use in your Nuxt project as a plugin. You can then access these APIs via the `useNuxtApp` composable. + + + +**NOTE:** The leading number in the files name indicate the plugin loading order, for more details see https://nuxt.com/docs/guide/directory-structure/plugins#registration-order. The `.client` and `.server` indicate the runtime that the logic contained in the file will run on, client or server. For details see: https://nuxt.com/docs/guide/directory-structure/plugins + + + +Modify the `01.amplify-apis.client.ts` file, with the following code: + + + +```ts title="nuxt-amplify-gen2/plugins/01.amplify-apis.client.ts" +import { + fetchAuthSession, + fetchUserAttributes, + signIn, + signOut, + getCurrentUser, +} from "aws-amplify/auth"; +import { generateClient } from "aws-amplify/data"; +import outputs from "../amplify_outputs.json"; +import type { Schema } from "@/amplify/data/resource"; +import { Amplify } from "aws-amplify"; + +// configure the Amplify client library +if (process.client) { + Amplify.configure(outputs, { ssr: true }); +} + +// generate your data client using the Schema from your backend +const client = generateClient(); + +export default defineNuxtPlugin({ + name: "AmplifyAPIs", + enforce: "pre", + setup() { + return { + provide: { + // You can call the API by via the composable `useNuxtApp()`. For example: + // `useNuxtApp().$Amplify.Auth.fetchAuthSession()` + Amplify: { + Auth: { + fetchAuthSession, + fetchUserAttributes, + getCurrentUser, + signIn, + signOut, + }, + GraphQL: { + client, + }, + }, + }, + }; + }, +}); +``` + + +Make sure you call `Amplify.configure` as early as possible in your application’s life-cycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. Review the [Library Not Configured Troubleshooting guide](/[platform]/build-a-backend/troubleshooting/library-not-configured/) for possible causes of this issue. + + + + + +Next, modify the `01.amplify-apis.server.ts` file, with the following code: + + + +```ts title="nuxt-amplify-gen2/plugins/01.amplify-apis.server.ts" +import type { CookieRef } from "nuxt/app"; +import { + createKeyValueStorageFromCookieStorageAdapter, + createUserPoolsTokenProvider, + createAWSCredentialsAndIdentityIdProvider, + runWithAmplifyServerContext, +} from "aws-amplify/adapter-core"; +import { parseAmplifyConfig } from "aws-amplify/utils"; +import { + fetchAuthSession, + fetchUserAttributes, + getCurrentUser, +} from "aws-amplify/auth/server"; +import { generateClient } from "aws-amplify/data/server"; +import type { + LibraryOptions, + FetchAuthSessionOptions, +} from "@aws-amplify/core"; +import type { + GraphQLOptionsV6, + GraphQLResponseV6, +} from "@aws-amplify/api-graphql"; + +import outputs from "../amplify_outputs.json"; + +// parse the content of `amplify_outputs.json` into the shape of ResourceConfig +const amplifyConfig = parseAmplifyConfig(outputs); + +// create the Amplify used token cookies names array +const userPoolClientId = amplifyConfig.Auth!.Cognito.userPoolClientId; +const lastAuthUserCookieName = `CognitoIdentityServiceProvider.${userPoolClientId}.LastAuthUser`; + +// create a GraphQL client that can be used in a server context +const gqlServerClient = generateClient({ config: amplifyConfig }); + +const getAmplifyAuthKeys = (lastAuthUser: string) => + ["idToken", "accessToken", "refreshToken", "clockDrift"] + .map( + (key) => + `CognitoIdentityServiceProvider.${userPoolClientId}.${lastAuthUser}.${key}` + ) + .concat(lastAuthUserCookieName); + +// define the plugin +export default defineNuxtPlugin({ + name: "AmplifyAPIs", + enforce: "pre", + setup() { + // The Nuxt composable `useCookie` is capable of sending cookies to the + // client via the `SetCookie` header. If the `expires` option is left empty, + // it sets a cookie as a session cookie. If you need to persist the cookie + // on the client side after your end user closes your Web app, you need to + // specify an `expires` value. + // + // We use 30 days here as an example (the default Cognito refreshToken + // expiration time). + const expires = new Date(); + expires.setDate(expires.getDate() + 30); + + // Get the last auth user cookie value + // + // We use `sameSite: 'lax'` in this example, which allows the cookie to be + // sent to your Nuxt server when your end user gets redirected to your Web + // app from a different domain. You should choose an appropriate value for + // your own use cases. + const lastAuthUserCookie = useCookie(lastAuthUserCookieName, { + sameSite: "lax", + expires, + secure: true, + }); + + // Get all Amplify auth token cookie names + const authKeys = lastAuthUserCookie.value + ? getAmplifyAuthKeys(lastAuthUserCookie.value) + : []; + + // Create a key-value map of cookie name => cookie ref + // + // Using the composable `useCookie` here in the plugin setup prevents + // cross-request pollution. + const amplifyCookies = authKeys + .map((name) => ({ + name, + cookieRef: useCookie(name, { sameSite: "lax", expires, secure: true }), + })) + .reduce>>( + (result, current) => ({ + ...result, + [current.name]: current.cookieRef, + }), + {} + ); + + // Create a key value storage based on the cookies + // + // This key value storage is responsible for providing Amplify Auth tokens to + // the APIs that you are calling. + // + // If you implement the `set` method, when Amplify needed to refresh the Auth + // tokens on the server side, the new tokens would be sent back to the client + // side via `SetCookie` header in the response. Otherwise the refresh tokens + // would not be propagate to the client side, and Amplify would refresh + // the tokens when needed on the client side. + // + // In addition, if you decide not to implement the `set` method, you don't + // need to pass any `CookieOptions` to the `useCookie` composable. + const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter({ + get(name) { + const cookieRef = amplifyCookies[name]; + + if (cookieRef && cookieRef.value) { + return { name, value: cookieRef.value }; + } + + return undefined; + }, + getAll() { + return Object.entries(amplifyCookies).map(([name, cookieRef]) => { + return { name, value: cookieRef.value ?? undefined }; + }); + }, + set(name, value) { + const cookieRef = amplifyCookies[name]; + if (cookieRef) { + cookieRef.value = value; + } + }, + delete(name) { + const cookieRef = amplifyCookies[name]; + + if (cookieRef) { + cookieRef.value = null; + } + }, + }); + + // Create a token provider + const tokenProvider = createUserPoolsTokenProvider( + amplifyConfig.Auth!, + keyValueStorage + ); + + // Create a credentials provider + const credentialsProvider = createAWSCredentialsAndIdentityIdProvider( + amplifyConfig.Auth!, + keyValueStorage + ); + + // Create the libraryOptions object + const libraryOptions: LibraryOptions = { + Auth: { + tokenProvider, + credentialsProvider, + }, + }; + + return { + provide: { + // You can add the Amplify APIs that you will use on the server side of + // your Nuxt app here. You must only use the APIs exported from the + // `aws-amplify//server` subpaths. + // + // You can call the API by via the composable `useNuxtApp()`. For example: + // `useNuxtApp().$Amplify.Auth.fetchAuthSession()` + // + // Recall that Amplify server APIs are required to be called in a isolated + // server context that is created by the `runWithAmplifyServerContext` + // function. + Amplify: { + Auth: { + fetchAuthSession: (options: FetchAuthSessionOptions) => + runWithAmplifyServerContext( + amplifyConfig, + libraryOptions, + (contextSpec) => fetchAuthSession(contextSpec, options) + ), + fetchUserAttributes: () => + runWithAmplifyServerContext( + amplifyConfig, + libraryOptions, + (contextSpec) => fetchUserAttributes(contextSpec) + ), + getCurrentUser: () => + runWithAmplifyServerContext( + amplifyConfig, + libraryOptions, + (contextSpec) => getCurrentUser(contextSpec) + ), + }, + GraphQL: { + client: { + // Follow this typing to ensure the`graphql` API return type can + // be inferred correctly according to your queries and mutations + graphql: < + FALLBACK_TYPES = unknown, + TYPED_GQL_STRING extends string = string + >( + options: GraphQLOptionsV6, + additionalHeaders?: Record + ) => + runWithAmplifyServerContext< + GraphQLResponseV6 + >(amplifyConfig, libraryOptions, (contextSpec) => + gqlServerClient.graphql( + contextSpec, + options, + additionalHeaders + ) + ), + }, + }, + }, + }, + }; + }, +}); +``` + + +### Step 2 - Use the `useNuxtApp()` composable + +Using the GraphQL API in `~/app.vue`: + +```ts title="nuxt-amplify-gen2/app.vue" + + + +``` + +The `app.vue` file can be rendered on both the client and server sides by default. The `useNuxtApp().$Amplify` composable will pick up the correct implementation of `01.amplify-apis.client.ts` and `01.amplify-apis.server.ts` to use, depending on the runtime. + +> **Warning:** Only a subset of Amplify APIs are usable on the server side, and as the libraries evolve, `amplify-apis.client` and `amplify-apis.server` may diverge further. You can guard your API calls to ensure an API is available in the context where you use it. E.g., you can use `if (process.client)` to ensure that a client-only API isn't executed on the server. + +### Step 3 - Set up Amplify for API Routes + +Following the specification of Nuxt, your API route handlers will live under `~/server`, which is a separate environment from other parts of your Nuxt app; hence, the plugins created in the previous step are not usable here, and extra work is required. + +#### Setup Amplify Server Context Utility + +1. Create a `utils` directory under the `server` directory of your Nuxt project. +2. Create an `amplifyUtils.ts` file under the `utils` directory. + +In this file, you will create a helper function to call Amplify APIs that are capable of running on the server side with context isolation. Modify the `amplifyUtils.ts` file, with the following code: + + + +```ts title="nuxt-amplify-gen2/server/utils/amplifyUtils.ts" +import type { H3Event, EventHandlerRequest } from "h3"; +import { + createKeyValueStorageFromCookieStorageAdapter, + createUserPoolsTokenProvider, + createAWSCredentialsAndIdentityIdProvider, + runWithAmplifyServerContext, + AmplifyServer, + CookieStorage, +} from "aws-amplify/adapter-core"; +import { parseAmplifyConfig } from "aws-amplify/utils"; + +import type { LibraryOptions } from "@aws-amplify/core"; +import outputs from "~/amplify_outputs.json"; + +const amplifyConfig = parseAmplifyConfig(outputs); + +const createCookieStorageAdapter = ( + event: H3Event +): CookieStorage.Adapter => { + // `parseCookies`, `setCookie` and `deleteCookie` are Nuxt provided functions + const readOnlyCookies = parseCookies(event); + + return { + get(name) { + if (readOnlyCookies[name]) { + return { name, value: readOnlyCookies[name] }; + } + }, + set(name, value, options) { + setCookie(event, name, value, options); + }, + delete(name) { + deleteCookie(event, name); + }, + getAll() { + return Object.entries(readOnlyCookies).map(([name, value]) => { + return { name, value }; + }); + }, + }; +}; + +const createLibraryOptions = ( + event: H3Event +): LibraryOptions => { + const cookieStorage = createCookieStorageAdapter(event); + const keyValueStorage = + createKeyValueStorageFromCookieStorageAdapter(cookieStorage); + const tokenProvider = createUserPoolsTokenProvider( + amplifyConfig.Auth!, + keyValueStorage + ); + const credentialsProvider = createAWSCredentialsAndIdentityIdProvider( + amplifyConfig.Auth!, + keyValueStorage + ); + + return { + Auth: { + tokenProvider, + credentialsProvider, + }, + }; +}; + +export const runAmplifyApi = ( + // we need the event object to create a context accordingly + event: H3Event, + operation: ( + contextSpec: AmplifyServer.ContextSpec + ) => Result | Promise +) => { + return runWithAmplifyServerContext( + amplifyConfig, + createLibraryOptions(event), + operation + ); +}; +``` + + + +Now, you can use the `runAmplifyApi` function to call Amplify APIs in an isolated server context. Create a new API route `/api/current-user` in the `server` directory and modify the `current-user.ts` file, with the following code: + +```ts title="nuxt-amplify-gen2/server/api/current-user.ts" +import { getCurrentUser } from "aws-amplify/auth/server"; +import { runAmplifyApi } from "~/server/utils/amplifyUtils"; + +export default defineEventHandler(async (event) => { + const user = await runAmplifyApi(event, (contextSpec) => + getCurrentUser(contextSpec) + ); + + return user; +}); +``` + +You can then fetch data from this API route, for example: `fetch('http://localhost:3000/api/current-user')` + +--- + +--- +title: "Optimistic UI" +section: "build-a-backend/data" +platforms: ["javascript", "swift", "angular", "nextjs", "react", "vue"] +gen: 2 +last-updated: "2025-02-12T16:04:46.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/optimistic-ui/" +--- + + +Amplify Data can be used with [TanStack Query](https://tanstack.com/query/latest/docs/react/overview) to implement optimistic UI, allowing CRUD operations to be rendered immediately on the UI before the request roundtrip has completed. Using Amplify Data with TanStack additionally makes it easy to render loading and error states, and allows you to rollback changes on the UI when API calls are unsuccessful. + +In the following examples we'll create a list view that optimistically renders newly created items, and a detail view that optimistically renders updates and deletes. + + + +For more details on TanStack Query, including requirements, supported browsers, and advanced usage, see the [TanStack Query documentation](https://tanstack.com/query/latest/docs/react/overview). +For complete guidance on how to implement optimistic updates with TanStack Query, see the [TanStack Query Optimistic UI Documentation](https://tanstack.com/query/latest/docs/react/guides/optimistic-updates). +For more on Amplify Data, see the [API documentation](/[platform]/build-a-backend/data/set-up-data/). + + + +To get started, run the following command in an existing Amplify project with a React frontend: + +```bash title="Terminal" showLineNumbers={false} +npm add @tanstack/react-query && \ +npm add --save-dev @tanstack/react-query-devtools +``` + +Modify your Data schema to use this "Real Estate Property" example: + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + RealEstateProperty: a.model({ + name: a.string().required(), + address: a.string(), + }).authorization(allow => [allow.guest()]) +}) + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'iam', + }, +}); +``` + +Save the file and run `npx ampx sandbox` to deploy the changes to your backend cloud sandbox. For the purposes of this guide, we'll build a Real Estate Property listing application. + +Next, at the root of your project, add the required TanStack Query imports, and create a client: + +```ts title="src/main.tsx" +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' +import { Amplify } from 'aws-amplify' +import outputs from '../amplify_outputs.json' +// highlight-start +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +// highlight-end + +Amplify.configure(outputs) + +const queryClient = new QueryClient() + +ReactDOM.createRoot(document.getElementById('root')!).render( + + // highlight-start + + + + + // highlight-end + , +) +``` + + + +TanStack Query Devtools are not required, but are a useful resource for debugging and understanding how TanStack works under the hood. By default, React Query Devtools are only included in bundles when `process.env.NODE_ENV === 'development'`, meaning that no additional configuration is required to exclude them from a production build. +For more information on the TanStack Query Devtools, visit the [TanStack Query Devtools docs](https://tanstack.com/query/latest/docs/react/devtools) + + + + + +For the complete working example, including required imports and React component state management, see the [Complete Example](#complete-example) below. + + + +## How to use TanStack Query query keys with the Amplify Data API + +TanStack Query manages query caching based on the query keys you specify. A query key must be an array. The array can contain a single string or multiple strings and nested objects. The query key must be serializable, and unique to the query's data. + +When using TanStack to render optimistic UI with Amplify Data, you must use different query keys depending on the API operation. When retrieving a list of items, a single string is used (e.g. `queryKey: ["realEstateProperties"]`). This query key is also used to optimistically render a newly created item. When updating or deleting an item, the query key must also include the unique identifier for the record being deleted or updated (e.g. `queryKey: ["realEstateProperties", newRealEstateProperty.id]`). + +For more detailed information on query keys, see the [TanStack Query documentation](https://tanstack.com/query/v4/docs/react/guides/query-keys). + +## Optimistically rendering a list of records + +To optimistically render a list of items returned from the Amplify Data API, use the TanStack `useQuery` hook, passing in the Data API query as the `queryFn` parameter. The following example creates a query to retrieve all records from the API. We'll use `realEstateProperties` as the query key, which will be the same key we use to optimistically render a newly created item. + +```ts title="src/App.tsx" +// highlight-start +import type { Schema } from '../amplify/data/resource' +import { generateClient } from 'aws-amplify/data' +import { useQuery } from '@tanstack/react-query' + +const client = generateClient(); +// highlight-end + +function App() { + // highlight-start + const { + data: realEstateProperties, + isLoading, + isSuccess, + isError: isErrorQuery, + } = useQuery({ + queryKey: ["realEstateProperties"], + queryFn: async () => { + const response = await client.models.RealEstateProperty.list(); + + const allRealEstateProperties = response.data; + + if (!allRealEstateProperties) return null; + + return allRealEstateProperties; + }, + }); + // highlight-end + // return ... +} +``` + +## Optimistically rendering a newly created record + +To optimistically render a newly created record returned from the Amplify Data API, use the TanStack `useMutation` hook, passing in the Amplify Data API mutation as the `mutationFn` parameter. We'll use the same query key used by the `useQuery` hook (`realEstateProperties`) as the query key to optimistically render a newly created item. +We'll use the `onMutate` function to update the cache directly, as well as the `onError` function to rollback changes when a request fails. + +```ts +import { generateClient } from 'aws-amplify/api' +import type { Schema } from '../amplify/data/resource' +// highlight-next-line +import { useQueryClient, useMutation } from '@tanstack/react-query' + +const client = generateClient() + +function App() { + // highlight-next-line + const queryClient = useQueryClient(); + + // highlight-start + const createMutation = useMutation({ + mutationFn: async (input: { name: string, address: string }) => { + const { data: newRealEstateProperty } = await client.models.RealEstateProperty.create(input) + return newRealEstateProperty; + }, + // When mutate is called: + onMutate: async (newRealEstateProperty) => { + // Cancel any outgoing refetches + // (so they don't overwrite our optimistic update) + await queryClient.cancelQueries({ queryKey: ["realEstateProperties"] }); + + // Snapshot the previous value + const previousRealEstateProperties = queryClient.getQueryData([ + "realEstateProperties", + ]); + + // Optimistically update to the new value + if (previousRealEstateProperties) { + queryClient.setQueryData(["realEstateProperties"], (old: Schema["RealEstateProperty"]["type"][]) => [ + ...old, + newRealEstateProperty, + ]); + } + + // Return a context object with the snapshotted value + return { previousRealEstateProperties }; + }, + // If the mutation fails, + // use the context returned from onMutate to rollback + onError: (err, newRealEstateProperty, context) => { + console.error("Error saving record:", err, newRealEstateProperty); + if (context?.previousRealEstateProperties) { + queryClient.setQueryData( + ["realEstateProperties"], + context.previousRealEstateProperties + ); + } + }, + // Always refetch after error or success: + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["realEstateProperties"] }); + }, + }); + // highlight-end + // return ... +} +``` + +## Querying a single item with TanStack Query + +To optimistically render updates on a single item, we'll first retrieve the item from the API. We'll use the `useQuery` hook, passing in the `get` query as the `queryFn` parameter. For the query key, we'll use a combination of `realEstateProperties` and the record's unique identifier. + +```ts +import { generateClient } from 'aws-amplify/data' +import type { Schema } from '../amplify/data/resource' +import { useQuery } from '@tanstack/react-query' + +const client = generateClient() + +function App() { + const currentRealEstatePropertyId = "SOME_ID" + // highlight-start + const { + data: realEstateProperty, + isLoading, + isSuccess, + isError: isErrorQuery, + } = useQuery({ + queryKey: ["realEstateProperties", currentRealEstatePropertyId], + queryFn: async () => { + if (!currentRealEstatePropertyId) { return } + + const { data: property } = await client.models.RealEstateProperty.get({ + id: currentRealEstatePropertyId, + }); + return property; + }, + }); + // highlight-end +} +``` + +## Optimistically render updates for a record + +To optimistically render Amplify Data updates for a single record, use the TanStack `useMutation` hook, passing in the update mutation as the `mutationFn` parameter. We'll use the same query key combination used by the single record `useQuery` hook (`realEstateProperties` and the record's `id`) as the query key to optimistically render the updates. +We'll use the `onMutate` function to update the cache directly, as well as the `onError` function to rollback changes when a request fails. + + + +When directly interacting with the cache via the `onMutate` function, the `newRealEstateProperty` parameter will only include fields that are being updated. When calling `setQueryData`, include the previous values for all fields in addition to the newly updated fields to avoid only rendering optimistic values for updated fields on the UI. + + + +```ts title="src/App.tsx" +import { generateClient } from 'aws-amplify/data' +import type { Schema } from '../amplify/data/resource' +import { useQueryClient, useMutation } from "@tanstack/react-query"; + +const client = generateClient() + +function App() { + // highlight-next-line + const queryClient = useQueryClient(); + + // highlight-start + const updateMutation = useMutation({ + mutationFn: async (realEstatePropertyDetails: { id: string, name?: string, address?: string }) => { + const { data: updatedProperty } = await client.models.RealEstateProperty.update(realEstatePropertyDetails); + + return updatedProperty; + }, + // When mutate is called: + onMutate: async (newRealEstateProperty: { id: string, name?: string, address?: string }) => { + // Cancel any outgoing refetches + // (so they don't overwrite our optimistic update) + await queryClient.cancelQueries({ + queryKey: ["realEstateProperties", newRealEstateProperty.id], + }); + + await queryClient.cancelQueries({ + queryKey: ["realEstateProperties"], + }); + + // Snapshot the previous value + const previousRealEstateProperty = queryClient.getQueryData([ + "realEstateProperties", + newRealEstateProperty.id, + ]); + + // Optimistically update to the new value + if (previousRealEstateProperty) { + queryClient.setQueryData( + ["realEstateProperties", newRealEstateProperty.id], + /** + * `newRealEstateProperty` will at first only include updated values for + * the record. To avoid only rendering optimistic values for updated + * fields on the UI, include the previous values for all fields: + */ + { ...previousRealEstateProperty, ...newRealEstateProperty } + ); + } + + // Return a context with the previous and new realEstateProperty + return { previousRealEstateProperty, newRealEstateProperty }; + }, + // If the mutation fails, use the context we returned above + onError: (err, newRealEstateProperty, context) => { + console.error("Error updating record:", err, newRealEstateProperty); + if (context?.previousRealEstateProperty) { + queryClient.setQueryData( + ["realEstateProperties", context.newRealEstateProperty.id], + context.previousRealEstateProperty + ); + } + }, + // Always refetch after error or success: + onSettled: (newRealEstateProperty) => { + if (newRealEstateProperty) { + queryClient.invalidateQueries({ + queryKey: ["realEstateProperties", newRealEstateProperty.id], + }); + queryClient.invalidateQueries({ + queryKey: ["realEstateProperties"], + }); + } + }, + }); + // highlight-end +} +``` + +## Optimistically render deleting a record + +To optimistically render a deletion of a single record, use the TanStack `useMutation` hook, passing in the delete mutation as the `mutationFn` parameter. We'll use the same query key combination used by the single record `useQuery` hook (`realEstateProperties` and the record's `id`) as the query key to optimistically render the updates. +We'll use the `onMutate` function to update the cache directly, as well as the `onError` function to rollback changes when a delete fails. + +```ts title="src/App.tsx" +import { generateClient } from 'aws-amplify/data' +import type { Schema } from '../amplify/data/resource' +import { useQueryClient, useMutation } from '@tanstack/react-query' + +const client = generateClient() + +function App() { + // highlight-next-line + const queryClient = useQueryClient(); + + // highlight-start + const deleteMutation = useMutation({ + mutationFn: async (realEstatePropertyDetails: { id: string }) => { + const { data: deletedProperty } = await client.models.RealEstateProperty.delete(realEstatePropertyDetails); + return deletedProperty; + }, + // When mutate is called: + onMutate: async (newRealEstateProperty) => { + // Cancel any outgoing refetches + // (so they don't overwrite our optimistic update) + await queryClient.cancelQueries({ + queryKey: ["realEstateProperties", newRealEstateProperty.id], + }); + + await queryClient.cancelQueries({ + queryKey: ["realEstateProperties"], + }); + + // Snapshot the previous value + const previousRealEstateProperty = queryClient.getQueryData([ + "realEstateProperties", + newRealEstateProperty.id, + ]); + + // Optimistically update to the new value + if (previousRealEstateProperty) { + queryClient.setQueryData( + ["realEstateProperties", newRealEstateProperty.id], + newRealEstateProperty + ); + } + + // Return a context with the previous and new realEstateProperty + return { previousRealEstateProperty, newRealEstateProperty }; + }, + // If the mutation fails, use the context we returned above + onError: (err, newRealEstateProperty, context) => { + console.error("Error deleting record:", err, newRealEstateProperty); + if (context?.previousRealEstateProperty) { + queryClient.setQueryData( + ["realEstateProperties", context.newRealEstateProperty.id], + context.previousRealEstateProperty + ); + } + }, + // Always refetch after error or success: + onSettled: (newRealEstateProperty) => { + if (newRealEstateProperty) { + queryClient.invalidateQueries({ + queryKey: ["realEstateProperties", newRealEstateProperty.id], + }); + queryClient.invalidateQueries({ + queryKey: ["realEstateProperties"], + }); + } + }, + }); + // highlight-end +} +``` + +## Loading and error states for optimistically rendered data + +Both `useQuery` and `useMutation` return `isLoading` and `isError` states that indicate the current state of the query or mutation. You can use these states to render loading and error indicators. + +In addition to operation-specific loading states, TanStack Query provides a [`useIsFetching` hook](https://www.tanstack.com/query/v4/docs/react/guides/background-fetching-indicators#displaying-global-background-fetching-loading-state). For the purposes of this demo, we show a global loading indicator in the [Complete Example](#complete-example) when *any* queries are fetching (including in the background) in order to help visualize what TanStack is doing in the background: + +```js +function GlobalLoadingIndicator() { + const isFetching = useIsFetching(); + return isFetching ?
    : null; +} +``` + +For more details on advanced usage of TanStack Query hooks, see the [TanStack documentation](https://tanstack.com/query/latest/docs/react/guides/mutations). + +The following example demonstrates how to use the state returned by TanStack to render a loading indicator while a mutation is in progress, and an error message if the mutation fails. For additional examples, see the [Complete Example](#complete-example) below. + +```ts +<> + {updateMutation.isError && + updateMutation.error instanceof Error ? ( +
    An error occurred: {updateMutation.error.message}
    + ) : null} + + {updateMutation.isSuccess ? ( +
    Real Estate Property updated!
    + ) : null} + + + +``` + +## Complete example + +```tsx title="src/main.tsx" +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' +import { Amplify } from 'aws-amplify' +import outputs from '../amplify_outputs.json' +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; + +Amplify.configure(outputs) + +export const queryClient = new QueryClient() + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + , +) +``` + +```tsx title="src/App.tsx" +import { generateClient } from 'aws-amplify/data' +import type { Schema } from '../amplify/data/resource' +import './App.css' +import { useIsFetching, useMutation, useQuery } from '@tanstack/react-query' +import { queryClient } from './main' +import { useState } from 'react' + +const client = generateClient({ + authMode: 'iam' +}) + +function GlobalLoadingIndicator() { + const isFetching = useIsFetching(); + + return isFetching ?
    : null; +} + +function App() { + const [currentRealEstatePropertyId, setCurrentRealEstatePropertyId] = + useState(null); + + const { + data: realEstateProperties, + isLoading, + isSuccess, + isError: isErrorQuery, + } = useQuery({ + queryKey: ["realEstateProperties"], + queryFn: async () => { + const response = await client.models.RealEstateProperty.list(); + + const allRealEstateProperties = response.data; + + if (!allRealEstateProperties) return null; + + return allRealEstateProperties; + }, + }); + + const createMutation = useMutation({ + mutationFn: async (input: { name: string, address: string }) => { + const { data: newRealEstateProperty } = await client.models.RealEstateProperty.create(input) + return newRealEstateProperty; + }, + // When mutate is called: + onMutate: async (newRealEstateProperty) => { + // Cancel any outgoing refetches + // (so they don't overwrite our optimistic update) + await queryClient.cancelQueries({ queryKey: ["realEstateProperties"] }); + + // Snapshot the previous value + const previousRealEstateProperties = queryClient.getQueryData([ + "realEstateProperties", + ]); + + // Optimistically update to the new value + if (previousRealEstateProperties) { + queryClient.setQueryData(["realEstateProperties"], (old: Schema["RealEstateProperty"]["type"][]) => [ + ...old, + newRealEstateProperty, + ]); + } + + // Return a context object with the snapshotted value + return { previousRealEstateProperties }; + }, + // If the mutation fails, + // use the context returned from onMutate to rollback + onError: (err, newRealEstateProperty, context) => { + console.error("Error saving record:", err, newRealEstateProperty); + if (context?.previousRealEstateProperties) { + queryClient.setQueryData( + ["realEstateProperties"], + context.previousRealEstateProperties + ); + } + }, + // Always refetch after error or success: + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["realEstateProperties"] }); + }, + }); + + function RealEstatePropertyDetailView() { + + const { + data: realEstateProperty, + isLoading, + isSuccess, + isError: isErrorQuery, + } = useQuery({ + queryKey: ["realEstateProperties", currentRealEstatePropertyId], + queryFn: async () => { + if (!currentRealEstatePropertyId) { return } + + const { data: property } = await client.models.RealEstateProperty.get({ id: currentRealEstatePropertyId }); + return property + }, + }); + + const updateMutation = useMutation({ + mutationFn: async (realEstatePropertyDetails: { id: string, name?: string, address?: string }) => { + const { data: updatedProperty } = await client.models.RealEstateProperty.update(realEstatePropertyDetails); + + return updatedProperty; + }, + // When mutate is called: + onMutate: async (newRealEstateProperty: { id: string, name?: string, address?: string }) => { + // Cancel any outgoing refetches + // (so they don't overwrite our optimistic update) + await queryClient.cancelQueries({ + queryKey: ["realEstateProperties", newRealEstateProperty.id], + }); + + await queryClient.cancelQueries({ + queryKey: ["realEstateProperties"], + }); + + // Snapshot the previous value + const previousRealEstateProperty = queryClient.getQueryData([ + "realEstateProperties", + newRealEstateProperty.id, + ]); + + // Optimistically update to the new value + if (previousRealEstateProperty) { + queryClient.setQueryData( + ["realEstateProperties", newRealEstateProperty.id], + /** + * `newRealEstateProperty` will at first only include updated values for + * the record. To avoid only rendering optimistic values for updated + * fields on the UI, include the previous values for all fields: + */ + { ...previousRealEstateProperty, ...newRealEstateProperty } + ); + } + + // Return a context with the previous and new realEstateProperty + return { previousRealEstateProperty, newRealEstateProperty }; + }, + // If the mutation fails, use the context we returned above + onError: (err, newRealEstateProperty, context) => { + console.error("Error updating record:", err, newRealEstateProperty); + if (context?.previousRealEstateProperty) { + queryClient.setQueryData( + ["realEstateProperties", context.newRealEstateProperty.id], + context.previousRealEstateProperty + ); + } + }, + // Always refetch after error or success: + onSettled: (newRealEstateProperty) => { + if (newRealEstateProperty) { + queryClient.invalidateQueries({ + queryKey: ["realEstateProperties", newRealEstateProperty.id], + }); + queryClient.invalidateQueries({ + queryKey: ["realEstateProperties"], + }); + } + }, + }); + + const deleteMutation = useMutation({ + mutationFn: async (realEstatePropertyDetails: { id: string }) => { + const { data: deletedProperty } = await client.models.RealEstateProperty.delete(realEstatePropertyDetails); + return deletedProperty; + }, + // When mutate is called: + onMutate: async (newRealEstateProperty) => { + // Cancel any outgoing refetches + // (so they don't overwrite our optimistic update) + await queryClient.cancelQueries({ + queryKey: ["realEstateProperties", newRealEstateProperty.id], + }); + + await queryClient.cancelQueries({ + queryKey: ["realEstateProperties"], + }); + + // Snapshot the previous value + const previousRealEstateProperty = queryClient.getQueryData([ + "realEstateProperties", + newRealEstateProperty.id, + ]); + + // Optimistically update to the new value + if (previousRealEstateProperty) { + queryClient.setQueryData( + ["realEstateProperties", newRealEstateProperty.id], + newRealEstateProperty + ); + } + + // Return a context with the previous and new realEstateProperty + return { previousRealEstateProperty, newRealEstateProperty }; + }, + // If the mutation fails, use the context we returned above + onError: (err, newRealEstateProperty, context) => { + console.error("Error deleting record:", err, newRealEstateProperty); + if (context?.previousRealEstateProperty) { + queryClient.setQueryData( + ["realEstateProperties", context.newRealEstateProperty.id], + context.previousRealEstateProperty + ); + } + }, + // Always refetch after error or success: + onSettled: (newRealEstateProperty) => { + if (newRealEstateProperty) { + queryClient.invalidateQueries({ + queryKey: ["realEstateProperties", newRealEstateProperty.id], + }); + queryClient.invalidateQueries({ + queryKey: ["realEstateProperties"], + }); + } + }, + }); + + return ( +
    +

    Real Estate Property Detail View

    + {isErrorQuery &&
    {"Problem loading Real Estate Property"}
    } + {isLoading && ( +
    + {"Loading Real Estate Property..."} +
    + )} + {isSuccess && ( +
    +

    {`Name: ${realEstateProperty?.name}`}

    +

    {`Address: ${realEstateProperty?.address}`}

    +
    + )} + {realEstateProperty && ( +
    +
    + {updateMutation.isPending ? ( + "Updating Real Estate Property..." + ) : ( + <> + {updateMutation.isError && + updateMutation.error instanceof Error ? ( +
    An error occurred: {updateMutation.error.message}
    + ) : null} + + {updateMutation.isSuccess ? ( +
    Real Estate Property updated!
    + ) : null} + + + + + )} +
    + +
    + {deleteMutation.isPending ? ( + "Deleting Real Estate Property..." + ) : ( + <> + {deleteMutation.isError && + deleteMutation.error instanceof Error ? ( +
    An error occurred: {deleteMutation.error.message}
    + ) : null} + + {deleteMutation.isSuccess ? ( +
    Real Estate Property deleted!
    + ) : null} + + + + )} +
    +
    + )} + +
    + ); + + } + return ( +
    + {!currentRealEstatePropertyId && ( +
    +

    Real Estate Properties:

    +
    + {createMutation.isPending ? ( + "Adding Real Estate Property..." + ) : ( + <> + {createMutation.isError && + createMutation.error instanceof Error ? ( +
    An error occurred: {createMutation.error.message}
    + ) : null} + + {createMutation.isSuccess ? ( +
    Real Estate Property added!
    + ) : null} + + + + )} +
    +
      + {isLoading && ( +
      + {"Loading Real Estate Properties..."} +
      + )} + {isErrorQuery && ( +
      {"Problem loading Real Estate Properties"}
      + )} + {isSuccess && + realEstateProperties?.map((realEstateProperty, idx) => { + if (!realEstateProperty) return null; + return ( +
    • +

      {realEstateProperty.name}

      + +
    • + ); + })} +
    +
    + )} + {currentRealEstatePropertyId && } + +
    + ); + +} + +export default App + +const styles = { + appContainer: { + display: "flex", + flexDirection: "column", + alignItems: "center", + }, + detailViewButton: { marginLeft: "1rem" }, + detailViewContainer: { border: "1px solid black", padding: "3rem" }, + globalLoadingIndicator: { + position: "fixed", + top: 0, + left: 0, + width: "100%", + height: "100%", + border: "4px solid blue", + pointerEvents: "none", + }, + listItem: { + display: "flex", + justifyContent: "space-between", + border: "1px dotted grey", + padding: ".5rem", + margin: ".1rem", + }, + loadingIndicator: { + border: "1px solid black", + padding: "1rem", + margin: "1rem", + }, + propertiesList: { + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "start", + width: "50%", + border: "1px solid black", + padding: "1rem", + listStyleType: "none", + }, +} as const; +``` + + + +Implementing optimistic UI with Amplify Data allows CRUD operations to be rendered immediately on the UI before the request roundtrip has completed, and allows you to rollback changes on the UI when API calls are unsuccessful. + +In the following example, we'll create a list view that optimistically renders newly created items, updates and deletes. Modify your Data schema to use this "Real Estate Property" example: + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + RealEstateProperty: a.model({ + name: a.string().required(), + address: a.string(), + }).authorization(allow => [allow.guest()]) +}) + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'iam', + }, +}); +``` + +Save the file and run `npx ampx sandbox` to deploy the changes to your backend cloud sandbox. For the purposes of this guide, we'll build a Real Estate Property listing application. + +Once the backend has been provisioned, run `npx ampx generate graphql-client-code --format modelgen --model-target swift --out /AmplifyModels` to generate the Swift model types for the app. + +Next, add the Amplify(`https://github.com/aws-amplify/amplify-swift.git`) package to your Xcode project and select the following modules to import when prompted: + +- AWSAPIPlugin +- AWSCognitoAuthPlugin +- AWSS3StoragePlugin +- Amplify + + + +For the complete working example see the [Complete Example](#complete-example) below. + + + +## How to use a Swift Actor to perform optimistic UI updates + +A Swift actor serializes access to its underlying properties. In this example, the actor will hold a list of items that will be published to the UI through a Combine publisher whenever the list is accessed. On a high level, the methods on the actor will perform the following: + +- create a new model, add it to the list, remove the newly added item from the list if the API request is unsuccessful +- update the existing model in the list, revert the update on the list if the API request is unsuccessful +- delete the existing model from the list, add the item back into the list if the API request is unsuccessful + +By providing these methods through an actor object, the underlying list will be accessed serially so that the entire operation can be rolled back if needed. + +To create an actor object that allows optimistic UI updates, create a new file and add the following code. + +```swift +import Amplify +import SwiftUI +import Combine + +actor RealEstatePropertyList { + + private var properties: [RealEstateProperty?] = [] { + didSet { + subject.send(properties.compactMap { $0 }) + } + } + + private let subject = PassthroughSubject<[RealEstateProperty], Never>() + var publisher: AnyPublisher<[RealEstateProperty], Never> { + subject.eraseToAnyPublisher() + } + + func listProperties() async throws { + let result = try await Amplify.API.query(request: .list(RealEstateProperty.self)) + guard case .success(let propertyList) = result else { + print("Failed with error: ", result) + return + } + properties = propertyList.elements + } +} +``` + +Calling the `listProperties()` method will perform a query with Amplify Data API and store the results in the `properties` property. When this property is set, the list is sent back to the subscribers. In your UI, create a view model and subscribe to updates: + +```swift +class RealEstatePropertyContainerViewModel: ObservableObject { + @Published var properties: [RealEstateProperty] = [] + var sink: AnyCancellable? + + var propertyList = RealEstatePropertyList() + init() { + Task { + sink = await propertyList.publisher + .receive(on: DispatchQueue.main) + .sink { properties in + print("Updating property list") + self.properties = properties + } + } + } + + func loadList() { + Task { + try? await propertyList.listProperties() + } + } +} + +struct RealEstatePropertyContainerView: View { + @StateObject var vm = RealEstatePropertyContainerViewModel() + @State private var propertyName: String = "" + + var body: some View { + Text("Hello") + } +} +``` + +## Optimistically rendering a newly created record + +To optimistically render a newly created record returned from the Amplify Data API, add a method to the `actor RealEstatePropertyList`: + +```swift +func createProperty(name: String, address: String? = nil) { + let property = RealEstateProperty(name: name, address: address) + // Optimistically send the newly created property, for the UI to render. + properties.append(property) + + Task { + do { + // Create the property record + let result = try await Amplify.API.mutate(request: .create(property)) + guard case .failure(let graphQLResponse) = result else { + return + } + print("Failed with error: ", graphQLResponse) + // Remove the newly created property + if let index = properties.firstIndex(where: { $0?.id == property.id }) { + properties.remove(at: index) + } + } catch { + print("Failed with error: ", error) + // Remove the newly created property + if let index = properties.firstIndex(where: { $0?.id == property.id }) { + properties.remove(at: index) + } + } + } +} +``` + +## Optimistically rendering a record update + +To optimistically render updates on a single item, use the code snippet like below: + +```swift +func updateProperty(_ property: RealEstateProperty) async { + guard let index = properties.firstIndex(where: { $0?.id == property.id }) else { + print("No property to update") + return + } + + // Optimistically update the property, for the UI to render. + let rollbackProperty = properties[index] + properties[index] = property + + do { + // Update the property record + let result = try await Amplify.API.mutate(request: .update(property)) + guard case .failure(let graphQLResponse) = result else { + return + } + print("Failed with error: ", graphQLResponse) + properties[index] = rollbackProperty + } catch { + print("Failed with error: ", error) + properties[index] = rollbackProperty + } +} +``` + +## Optimistically render deleting a record + +To optimistically render a Amplify Data API delete, use the code snippet like below: + +```swift +func deleteProperty(_ property: RealEstateProperty) async { + guard let index = properties.firstIndex(where: { $0?.id == property.id }) else { + print("No property to remove") + return + } + + // Optimistically remove the property, for the UI to render. + let rollbackProperty = properties[index] + properties[index] = nil + + do { + // Delete the property record + let result = try await Amplify.API.mutate(request: .delete(property)) + switch result { + case .success: + // Finalize the removal + properties.remove(at: index) + case .failure(let graphQLResponse): + print("Failed with error: ", graphQLResponse) + // Undo the removal + properties[index] = rollbackProperty + } + + } catch { + print("Failed with error: ", error) + // Undo the removal + properties[index] = rollbackProperty + } +} +``` + +## Complete example + +#### [Main] + +```swift +import SwiftUI +import Amplify +import AWSAPIPlugin + +@main +struct OptimisticUIApp: App { + + init() { + do { + Amplify.Logging.logLevel = .verbose + try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels())) + try Amplify.configure(with: .amplifyOutputs) + print("Amplify configured with API, Storage, and Auth plugins!") + } catch { + print("Failed to initialize Amplify with \(error)") + } + } + + var body: some Scene { + WindowGroup { + RealEstatePropertyContainerView() + } + } +} + +// Extend the model to Identifiable to make it compatible with SwiftUI's `ForEach`. +extension RealEstateProperty: Identifiable { } + +struct TappedButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .padding(10) + .background(configuration.isPressed ? Color.teal.opacity(0.8) : Color.teal) + .foregroundColor(.white) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } +} +``` + +#### [Actor] + +```swift +actor RealEstatePropertyList { + + private var properties: [RealEstateProperty?] = [] { + didSet { + subject.send(properties.compactMap { $0 }) + } + } + + private let subject = PassthroughSubject<[RealEstateProperty], Never>() + var publisher: AnyPublisher<[RealEstateProperty], Never> { + subject.eraseToAnyPublisher() + } + + func listProperties() async throws { + let result = try await Amplify.API.query(request: .list(RealEstateProperty.self)) + guard case .success(let propertyList) = result else { + print("Failed with error: ", result) + return + } + properties = propertyList.elements + } + + func createProperty(name: String, address: String? = nil) { + let property = RealEstateProperty(name: name, address: address) + // Optimistically send the newly created property, for the UI to render. + properties.append(property) + + Task { + do { + // Create the property record + let result = try await Amplify.API.mutate(request: .create(property)) + guard case .failure(let graphQLResponse) = result else { + return + } + print("Failed with error: ", graphQLResponse) + // Remove the newly created property + if let index = properties.firstIndex(where: { $0?.id == property.id }) { + properties.remove(at: index) + } + } catch { + print("Failed with error: ", error) + // Remove the newly created property + if let index = properties.firstIndex(where: { $0?.id == property.id }) { + properties.remove(at: index) + } + } + } + } + + func updateProperty(_ property: RealEstateProperty) async { + guard let index = properties.firstIndex(where: { $0?.id == property.id }) else { + print("No property to update") + return + } + + // Optimistically update the property, for the UI to render. + let rollbackProperty = properties[index] + properties[index] = property + + do { + // Update the property record + let result = try await Amplify.API.mutate(request: .update(property)) + guard case .failure(let graphQLResponse) = result else { + return + } + print("Failed with error: ", graphQLResponse) + properties[index] = rollbackProperty + } catch { + print("Failed with error: ", error) + properties[index] = rollbackProperty + } + } + + func deleteProperty(_ property: RealEstateProperty) async { + guard let index = properties.firstIndex(where: { $0?.id == property.id }) else { + print("No property to remove") + return + } + + // Optimistically remove the property, for the UI to render. + let rollbackProperty = properties[index] + properties[index] = nil + + do { + // Delete the property record + let result = try await Amplify.API.mutate(request: .delete(property)) + switch result { + case .success: + // Finalize the removal + properties.remove(at: index) + case .failure(let graphQLResponse): + print("Failed with error: ", graphQLResponse) + // Undo the removal + properties[index] = rollbackProperty + } + + } catch { + print("Failed with error: ", error) + // Undo the removal + properties[index] = rollbackProperty + } + } +} + +``` + +#### [View] + +```swift +class RealEstatePropertyContainerViewModel: ObservableObject { + @Published var properties: [RealEstateProperty] = [] + var sink: AnyCancellable? + + var propertyList = RealEstatePropertyList() + init() { + Task { + sink = await propertyList.publisher + .receive(on: DispatchQueue.main) + .sink { properties in + print("Updating property list") + self.properties = properties + } + } + } + + func loadList() { + Task { + try? await propertyList.listProperties() + } + } + func createPropertyButtonTapped(name: String) { + Task { + await propertyList.createProperty(name: name) + } + } + + func updatePropertyButtonTapped(_ property: RealEstateProperty) { + Task { + await propertyList.updateProperty(property) + } + } + + func deletePropertyButtonTapped(_ property: RealEstateProperty) { + Task { + await propertyList.deleteProperty(property) + } + } +} + +struct RealEstatePropertyContainerView: View { + @StateObject var viewModel = RealEstatePropertyContainerViewModel() + @State private var propertyName: String = "" + + var body: some View { + VStack { + ScrollView { + LazyVStack(alignment: .leading) { + ForEach($viewModel.properties) { $property in + HStack { + TextField("Update property name", text: $property.name) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .multilineTextAlignment(.center) + Button("Update") { + viewModel.updatePropertyButtonTapped(property) + } + Button { + viewModel.deletePropertyButtonTapped(property) + } label: { + Image(systemName: "xmark") + .foregroundColor(.red) + } + + }.padding(.horizontal) + } + } + }.refreshable { + viewModel.loadList() + } + TextField("New property name", text: $propertyName) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .multilineTextAlignment(.center) + + Button("Save") { + viewModel.createPropertyButtonTapped(name: propertyName) + self.propertyName = "" + } + .buttonStyle(TappedButtonStyle()) + }.task { + viewModel.loadList() + } + } +} + +struct RealEstatePropertyContainerView_Previews: PreviewProvider { + static var previews: some View { + RealEstatePropertyContainerView() + } +} +``` + + + +--- + +--- +title: "Connect to AWS AppSync Events" +section: "build-a-backend/data" +platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue", "android", "swift"] +gen: 2 +last-updated: "2025-10-03T18:57:52.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/connect-event-api/" +--- + + +This guide walks through how you can connect to AWS AppSync Events using the Amplify library. + + + +This guide walks through how you can connect to AWS AppSync Events, with or without Amplify. + + +AWS AppSync Events lets you create secure and performant serverless WebSocket APIs that can broadcast real-time event data to millions of subscribers, without you having to manage connections or resource scaling. With this feature, you can build multi-user features such as a collaborative document editors, chat apps, and live polling systems. + +Learn more about AWS AppSync Events by visiting the [Developer Guide](https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html). + + +## Install the AWS AppSync Events library + +#### [Without Amplify] + +Add the `aws-sdk-appsync-events` dependency to your app/build.gradle.kts file. + +```kotlin title="app/build.gradle.kts" +dependencies { + // highlight-start + // Adds the AWS AppSync Events library without adding any Amplify dependencies + implementation("com.amazonaws:aws-sdk-appsync-events:ANDROID_APPSYNC_SDK_VERSION") + // highlight-end +} +``` + +#### [With Amplify] + +Add the `aws-sdk-appsync-events` and `aws-sdk-appsync-amplify` dependencies to your app/build.gradle.kts file. + +```kotlin title="app/build.gradle.kts" +dependencies { + // highlight-start + // Adds the AWS AppSync Events library + implementation("com.amazonaws:aws-sdk-appsync-events:ANDROID_APPSYNC_SDK_VERSION") + // Contains AppSync Authorizers which connect to Amplify Auth + implementation("com.amazonaws:aws-sdk-appsync-amplify:ANDROID_APPSYNC_SDK_VERSION") + // highlight-end +} +``` + +### Providing AppSync Authorizers + +#### [Without Amplify] + +The AWS AppSync Events library imports a number of Authorizer classes to match the various authorization strategies that may be used for your Events API. You should choose the appropriate Authorizer type for your authorization strategy. + +* `apiKey` authorization, **APIKeyAuthorizer** +* `identityPool` authorization, **IAMAuthorizer** +* `userPool` authorization, **AuthTokenAuthorizer** + +You can create as many Events clients as necessary if you require multiple authorization types. + +#### API KEY + +An `ApiKeyAuthorizer` can be used with a hardcoded API key or by fetching the key from some source.: + +```kotlin +// highlight-start +// Use a hard-coded API key +val authorizer = ApiKeyAuthorizer("[API_KEY]") +//highlight-end +// or +// highlight-start +// Fetch the API key from some source. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = ApiKeyAuthorizer { fetchApiKey() } +//highlight-end +``` + +#### AMAZON COGNITO USER POOLS + +When working directly with AppSync, you must implement the token fetching yourself. + +```kotlin +// highlight-start +// Use your own token fetching. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = AuthTokenAuthorizer { + fetchLatestAuthToken() +} +//highlight-end +``` + +#### AWS IAM + +When working directly with AppSync, you must implement the request signing yourself. + +```kotlin +// highlight-start +// Provide an implementation of the signing function. This function should implement the +// AWS Sig-v4 signing logic and return the authorization headers containing the token and signature. +val authorizer = IamAuthorizer { appSyncRequest -> signRequestAndReturnHeaders(appSyncRequest) } +// highlight-end +``` + +#### [With Amplify] + +The AWS AppSync Events library imports a number of Authorizer classes to match the various authorization strategies that may be used for your Events API. You should choose the appropriate Authorizer type for your authorization strategy. + +* `apiKey` authorization, **APIKeyAuthorizer** +* `identityPool` authorization, **AmplifyIAMAuthorizer** +* `userPool` authorization, **AmplifyUserPoolAuthorizer** + +You can create as many Events clients as necessary if you require multiple authorization types. + +#### API KEY + +An `ApiKeyAuthorizer` can provide a hardcoded API key, or fetch the API key from some source: + +```kotlin +// highlight-start +// Use a hard-coded API key +val authorizer = ApiKeyAuthorizer("[API_KEY]") +//highlight-end +// or +// highlight-start +// Fetch the API key from some source. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = ApiKeyAuthorizer { fetchApiKey() } +//highlight-end +``` + +#### AMAZON COGNITO USER POOLS + +The `AmplifyUserPoolAuthorizer` uses your configured Amplify instance to fetch AWS Cognito UserPool tokens and attach to the request. + +```kotlin +// highlight-start +// Using the provided Amplify UserPool Authorizer +val authorizer = AmplifyUserPoolAuthorizer() +//highlight-end +``` + +#### AWS IAM + +The `AmplifyIAMAuthorizer` uses your configured Amplify instance to sign a request with the IAM SigV4 protocol. + +```kotlin +// highlight-start +// Using the provided Amplify IAM Authorizer +val authorizer = AmplifyIamAuthorizer("{REGION}") +//highlight-end +``` + + + + +## Install the AWS AppSync Events library + +#### [Without Amplify] + +- Add `AWS AppSync Events Library for Swift` into your project using Swift Package Manager. + +- Enter its Github URL (https://github.com/aws-amplify/aws-appsync-events-swift), select `Up to Next Major Version` and click `Add Package`. + +- Select the following product and add it to your target: + - `AWSAppSyncEvents` + +#### [With Amplify] + +- Add `AWS AppSync Events Library for Swift` into your project using Swift Package Manager. + - Enter its Github URL (https://github.com/aws-amplify/aws-appsync-events-swift), select `Up to Next Major Version` and click `Add Package`. + - Select the following product and add it to your target: + - `AWSAppSyncEvents` + +- Add `Amplify Library for Swift` into your project using Swift Package Manager. + - Enter its Github URL (https://github.com/aws-amplify/amplify-swift), select `Up to Next Major Version` and click `Add Package`. + - Select the following product and add it to your target: + - `Amplify` + - `AWSCognitoAuthPlugin` + +### Providing AppSync Authorizers + +#### [Without Amplify] + +The AWS AppSync Events library imports a number of Authorizer classes to match the various authorization strategies that may be used for your Events API. You should choose the appropriate Authorizer type for your authorization strategy. + +* API KEY authorization, **APIKeyAuthorizer** +* AWS IAM authorization, **IAMAuthorizer** +* AMAZON COGNITO USER POOLS authorization, **AuthTokenAuthorizer** + +You can create as many `Events` clients as necessary if you require multiple authorization types. + +#### API KEY + +An `APIKeyAuthorizer` can be used with a hardcoded API key or by fetching the key from some source. + +```swift +// highlight-start +// Use a hard-coded API key +let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") +//highlight-end +// or +// highlight-start +// Fetch the API key from some source. This function may be called many times, +// so it should implement appropriate caching internally. +let authorizer = APIKeyAuthorizer(fetchAPIKey: { + // fetch your api key + }) +//highlight-end +``` + +#### AMAZON COGNITO USER POOLS + +When working directly with AppSync, you must implement the token fetching yourself. + +```swift +// highlight-start +// Use your own token fetching. This function may be called many times, +// so it should implement appropriate caching internally. +let authorizer = AuthTokenAuthorizer(fetchLatestAuthToken: { + // fetch your auth token +}) +//highlight-end +``` + +#### AWS IAM + +When working directly with AppSync, you must implement the request signing yourself. + +```swift +// highlight-start +// Provide an implementation of the signing function. This function should implement the +// AWS Sig-v4 signing logic and return the authorization headers containing the token and signature. +let authorizer = IAMAuthorizer(signRequest: { + // implement your `URLRequest` signing logic +}) +// highlight-end +``` + +#### [With Amplify] + +The AWS AppSync Events library imports a number of Authorizer classes to match the various authorization strategies that may be used for your Events API. You should choose the appropriate Authorizer type for your authorization strategy. + +* API KEY authorization, **APIKeyAuthorizer** +* AWS IAM authorization, **IAMAuthorizer** +* AMAZON COGNITO USER POOLS authorization, **AuthTokenAuthorizer** + +You can create as many `Events` clients as necessary if you require multiple authorization types. + +#### API KEY + +An `APIKeyAuthorizer` can be used with a hardcoded API key or by fetching the key from some source. + +```swift +// highlight-start +// Use a hard-coded API key +let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") +//highlight-end +// or +// highlight-start +// Fetch the API key from some source. This function may be called many times, +// so it should implement appropriate caching internally. +let authorizer = APIKeyAuthorizer(fetchAPIKey: { + // fetch your api key + }) +//highlight-end +``` + +#### AMAZON COGNITO USER POOLS + +If you are using Amplify Auth, you can create a method that retrieves the Cognito access token. + +```swift +import Amplify + +func getUserPoolAccessToken() async throws -> String { + let authSession = try await Amplify.Auth.fetchAuthSession() + if let result = (authSession as? AuthCognitoTokensProvider)?.getCognitoTokens() { + switch result { + case .success(let tokens): + return tokens.accessToken + case .failure(let error): + throw error + } + } + throw AuthError.unknown("Did not receive a valid response from fetchAuthSession for get token.") +} +``` + +Then create the `AuthTokenAuthorizer` with this method. + +```swift +let authorizer = AuthTokenAuthorizer(fetchLatestAuthToken: getUserPoolAccessToken) +``` + +#### AWS IAM + +If you are using Amplify Auth, you can initialize `IAMAuthorizer` with a helper method from `AWSCognitoAuthPlugin` like below: + +```swift +let authorizer = IAMAuthorizer( + signRequest: AWSCognitoAuthPlugin.createAppSyncSigner(region: "region") +) +``` + + + +## Connect to an Event API without an existing Amplify backend + +Before you begin, you will need: + +- An Event API created via the AWS Console +- Take note of: HTTP endpoint, region, API Key + + +Thats it! Skip to [Client Library Usage Guide](#client-library-usage-guide). + + + +```tsx title="src/App.tsx" +import type { EventsChannel } from 'aws-amplify/data'; +import { useState, useEffect, useRef } from 'react'; +import { Amplify } from 'aws-amplify'; +import { events } from 'aws-amplify/data'; + +Amplify.configure({ + API: { + Events: { + endpoint: + 'https://abcdefghijklmnopqrstuvwxyz.appsync-api.us-east-1.amazonaws.com/event', + region: 'us-east-1', + defaultAuthMode: 'apiKey', + apiKey: 'da2-abcdefghijklmnopqrstuvwxyz', + }, + }, +}); + +export default function App() { + const [myEvents, setMyEvents] = useState([]); + + const sub = useRef>(null); + + useEffect(() => { + let channel: EventsChannel; + + const connectAndSubscribe = async () => { + channel = await events.connect('default/channel'); + + if (!sub.current) { + sub.current = channel.subscribe({ + next: (data) => { + console.log('received', data); + setMyEvents((prev) => [data, ...prev]); + }, + error: (err) => console.error('error', err), + }); + } + }; + + connectAndSubscribe(); + + return () => { + sub.current?.unsubscribe(); + sub.current = null; + return channel?.close(); + }; + }, []); + + async function publishEvent() { + // Publish via HTTP POST + await events.post('default/channel', { some: 'data' }); + + // Alternatively, publish events through the WebSocket channel + const channel = await events.connect('default/channel'); + await channel.publish({ some: 'data' }); + } + + return ( + <> + +
      + {myEvents.map((data, idx) => ( +
    • {JSON.stringify(data.event, null, 2)}
    • + ))} +
    + + ); +} +``` + + +## Add an Event API to an existing Amplify backend + +This guide walks through how you can add an Event API to an existing Amplify backend. We'll be using Cognito User Pools for authenticating with Event API from our frontend application. Any signed in user will be able to subscribe to the Event API and publish events. + +Before you begin, you will need: + +- An existing Amplify backend (see [Quickstart](/[platform]/start/quickstart/)) +- Latest versions of `@aws-amplify/backend` and `@aws-amplify/backend-cli` (`npm add @aws-amplify/backend@latest @aws-amplify/backend-cli@latest`) + +### Update Backend Definition + +First, we'll add a new Event API to our backend definition. + + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +// highlight-start +// import CDK resources: +import { + CfnApi, + CfnChannelNamespace, + AuthorizationType, +} from 'aws-cdk-lib/aws-appsync'; +import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +// highlight-end + +const backend = defineBackend({ + auth, +}); + +// highlight-start +// create a new stack for our Event API resources: +const customResources = backend.createStack('custom-resources'); + +// add a new Event API to the stack: +const cfnEventAPI = new CfnApi(customResources, 'CfnEventAPI', { + name: 'my-event-api', + eventConfig: { + authProviders: [ + { + authType: AuthorizationType.USER_POOL, + cognitoConfig: { + awsRegion: customResources.region, + // configure Event API to use the Cognito User Pool provisioned by Amplify: + userPoolId: backend.auth.resources.userPool.userPoolId, + }, + }, + ], + // configure the User Pool as the auth provider for Connect, Publish, and Subscribe operations: + connectionAuthModes: [{ authType: AuthorizationType.USER_POOL }], + defaultPublishAuthModes: [{ authType: AuthorizationType.USER_POOL }], + defaultSubscribeAuthModes: [{ authType: AuthorizationType.USER_POOL }], + }, +}); + +// create a default namespace for our Event API: +const namespace = new CfnChannelNamespace( + customResources, + 'CfnEventAPINamespace', + { + apiId: cfnEventAPI.attrApiId, + name: 'default', + } +); + +// attach a policy to the authenticated user role in our User Pool to grant access to the Event API: +backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy( + new Policy(customResources, 'AppSyncEventPolicy', { + statements: [ + new PolicyStatement({ + actions: [ + 'appsync:EventConnect', + 'appsync:EventSubscribe', + 'appsync:EventPublish', + ], + resources: [`${cfnEventAPI.attrApiArn}/*`, `${cfnEventAPI.attrApiArn}`], + }), + ], + }) +); + +// finally, add the Event API configuration to amplify_outputs: +backend.addOutput({ + custom: { + events: { + url: `https://${cfnEventAPI.getAtt('Dns.Http').toString()}/event`, + aws_region: customResources.region, + default_authorization_type: AuthorizationType.USER_POOL, + }, + }, +}); +// highlight-end +``` + + + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +// highlight-start +// import CDK resources: +import { + CfnApi, + CfnChannelNamespace, + AuthorizationType, +} from 'aws-cdk-lib/aws-appsync'; +import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +// highlight-end + +const backend = defineBackend({ + auth, +}); + +// highlight-start +// create a new stack for our Event API resources: +const customResources = backend.createStack('custom-resources'); + +// add a new Event API to the stack: +const cfnEventAPI = new CfnApi(customResources, 'CfnEventAPI', { + name: 'my-event-api', + eventConfig: { + authProviders: [ + { + authType: AuthorizationType.USER_POOL, + cognitoConfig: { + awsRegion: customResources.region, + // configure Event API to use the Cognito User Pool provisioned by Amplify: + userPoolId: backend.auth.resources.userPool.userPoolId, + }, + }, + ], + // configure the User Pool as the auth provider for Connect, Publish, and Subscribe operations: + connectionAuthModes: [{ authType: AuthorizationType.USER_POOL }], + defaultPublishAuthModes: [{ authType: AuthorizationType.USER_POOL }], + defaultSubscribeAuthModes: [{ authType: AuthorizationType.USER_POOL }], + }, +}); + +// create a default namespace for our Event API: +const namespace = new CfnChannelNamespace( + customResources, + 'CfnEventAPINamespace', + { + apiId: cfnEventAPI.attrApiId, + name: 'default', + } +); + +// attach a policy to the authenticated user role in our User Pool to grant access to the Event API: +backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy( + new Policy(customResources, 'AppSyncEventPolicy', { + statements: [ + new PolicyStatement({ + actions: [ + 'appsync:EventConnect', + 'appsync:EventSubscribe', + 'appsync:EventPublish', + ], + resources: [`${cfnEventAPI.attrApiArn}/*`, `${cfnEventAPI.attrApiArn}`], + }), + ], + }) +); +// highlight-end +``` + + +### Deploy Backend + +To test your changes, deploy your Amplify Sandbox. + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + + +### Connect your frontend application + +After the sandbox deploys, connect your frontend application to the Event API. We'll be using the [Amplify Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator) to sign in to our Cognito User Pool. + +If you don't already have the Authenticator installed, you can install it by running `npm add @aws-amplify/ui-react`. + +```tsx title="src/App.tsx" +import { useEffect, useState } from 'react'; +import { Amplify } from 'aws-amplify'; +import { events, type EventsChannel } from 'aws-amplify/data'; +import { Authenticator } from '@aws-amplify/ui-react'; +import '@aws-amplify/ui-react/styles.css'; +import outputs from '../amplify_outputs.json'; + +Amplify.configure(outputs); + +export default function App() { + const [myEvents, setMyEvents] = useState[]>([]); + + useEffect(() => { + let channel: EventsChannel; + + const connectAndSubscribe = async () => { + channel = await events.connect('default/channel'); + + channel.subscribe({ + next: (data) => { + console.log('received', data); + setMyEvents((prev) => [data, ...prev]); + }, + error: (err) => console.error('error', err), + }); + }; + + connectAndSubscribe(); + + return () => channel && channel.close(); + }, []); + + async function publishEvent() { + // Publish via HTTP POST + await events.post('default/channel', { some: 'data' }); + + // Alternatively, publish events through the WebSocket channel + const channel = await events.connect('default/channel'); + await channel.publish({ some: 'data' }); + } + + return ( + + {({ signOut, user }) => ( + <> +
    +

    Welcome, {user.username}

    + +
    +
    + +
      + {myEvents.map((data) => ( +
    • {JSON.stringify(data.event)}
    • + ))} +
    +
    + + )} +
    + ); +} +``` + + + +## Client Library Usage Guide + + + +### Create the Events class + +You can find your endpoint in the AWS AppSync Events console. It should start with `https` and end with `/event`. + +```kotlin +val endpoint = "https://abcdefghijklmnopqrstuvwxyz.appsync-api.us-east-1.amazonaws.com/event" +val events = Events(endpoint) +``` + +### Using the REST Client + +An `EventsRestClient` can be created to publish event(s) over REST. It accepts a publish authorizer that will be used by default for any publish calls within the client. + +#### Creating the REST Client + +``` kotlin +val events: Events // Your configured Events +val restClient = events.createRestClient( + publishAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") +) +``` + +Additionally, you can pass custom options to the Rest Client. Current capabilities include modifying the `OkHttp.Builder` the `EventsRestClient` uses, and enabling client library logs. +See [Collecting Client Library Logs](#collecting-client-library-logs) for the AndroidLogger class. + +``` kotlin +val restClient = events.createRestClient( + publishAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz"), + options = Events.Options.Rest( + loggerProvider = { namespace -> AndroidLogger(namespace, logLevel) }, + okHttpConfigurationProvider = { okHttpBuilder -> + // update OkHttp.Builder used by EventsRestClient + } + ) +) +``` + +#### Publish a Single Event + +```kotlin +val restClient: EventsRestClient // Your configured EventsRestClient +// kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] +val jsonEvent = JsonObject(mapOf("some" to JsonPrimitive("data"))) +val publishResult = restClient.publish( + channelName = "default/channel", + event = jsonEvent +) +when(publishResult) { + is PublishResult.Response -> { + val successfulEvents = publishResult.successfulEvents // inspect successful events + val failedEvents = publishResult.failedEvents // inspect failed events + } + is PublishResult.Failure -> { + val error = result.error // publish failure, inspect error + } +} +``` + +#### Publish multiple events + +You can publish up to 5 events at a time. + +```kotlin +val restClient: EventsRestClient // Your configured EventsRestClient +// List of kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] +val jsonEvents = listOf( + JsonObject(mapOf("some" to JsonPrimitive("data1"))), + JsonObject(mapOf("some" to JsonPrimitive("data2"))) +) +val publishResult = restClient.publish( + channelName = "default/channel", + events = jsonEvents +) +when(publishResult) { + is PublishResult.Response -> { + val successfulEvents = publishResult.successfulEvents // inspect successful events + val failedEvents = publishResult.failedEvents // inspect failed events + } + is PublishResult.Failure -> { + val error = result.error // publish failure, inspect error + } +} +``` + +#### Publish with a different authorizer + +```kotlin +restClient.publish( + channelName = "default/channel", + event = JsonObject(mapOf("some" to JsonPrimitive("data"))), + authorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") +) +``` + +### Using the WebSocket Client + +An `EventsWebSocketClient` can be created to publish and subscribe to channels. The WebSocket connection is managed by the library and connects on the first subscribe or publish operation. Once connected, the WebSocket will remain open. You should explicitly disconnect the client when you no longer need to subscribe or publish to channels. + +#### Creating the WebSocket Client + +```kotlin +val events: Events // Your configured Events +val apiKeyAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") +val webSocketClient = events.createWebSocketClient( + connectAuthorizer = apiKeyAuthorizer, // used to connect the websocket + subscribeAuthorizer = apiKeyAuthorizer, // used for subscribe calls + publishAuthorizer = apiKeyAuthorizer // used for publish calls +) +``` + +Additionally, you can pass custom options to the WebSocket Client. Current capabilities include modifying the `OkHttp.Builder` the `EventsWebSocketClient` uses, and enabling client library logs. +See [Collecting Client Library Logs](#collecting-client-library-logs) for the AndroidLogger class. + +```kotlin +val events: Events // Your configured Events +val apiKeyAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") +val webSocketClient = events.createWebSocketClient( + connectAuthorizer = apiKeyAuthorizer, // used to connect the websocket + subscribeAuthorizer = apiKeyAuthorizer, // used for subscribe calls + publishAuthorizer = apiKeyAuthorizer // used for publish calls, + options = Events.Options.WebSocket( + loggerProvider = { namespace -> AndroidLogger(namespace, logLevel) }, + okHttpConfigurationProvider = { okHttpBuilder -> + // update OkHttpBuilder used by EventsWebSocketClient + } + ) +) +``` + +#### Publish a Single Event + +```kotlin +val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient +// kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] +val jsonEvent = JsonObject(mapOf("some" to JsonPrimitive("data"))) +val publishResult = webSocketClient.publish( + channelName = "default/channel", + event = jsonEvent +) +when(publishResult) { + is PublishResult.Response -> { + val successfulEvents = publishResult.successfulEvents // inspect successful events + val failedEvents = publishResult.failedEvents // inspect failed events + } + is PublishResult.Failure -> { + val error = result.error // publish failure, inspect error + } +} +``` + +#### Publish multiple Events + +You can publish up to 5 events at a time. + +```kotlin +val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient +// List of kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] +val jsonEvents = listOf( + JsonObject(mapOf("some" to JsonPrimitive("data1"))), + JsonObject(mapOf("some" to JsonPrimitive("data2"))) +) +val publishResult = webSocketClient.publish( + channelName = "default/channel", + events = jsonEvents +) +when(publishResult) { + is PublishResult.Response -> { + val successfulEvents = publishResult.successfulEvents // inspect successful events + val failedEvents = publishResult.failedEvents // inspect failed events + } + is PublishResult.Failure -> { + val error = result.error // publish failure, inspect error + } +} +``` + +#### Publish with a different authorizer + +```kotlin +val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient +val jsonEvent = JsonObject(mapOf("some" to JsonPrimitive("data"))) +val publishResult = webSocketClient.publish( + channelName = "default/channel", + event = jsonEvent, + authorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") +) +when(publishResult) { + is PublishResult.Response -> { + val successfulEvents = publishResult.successfulEvents // inspect successful events + val failedEvents = publishResult.failedEvents // inspect failed events + } + is PublishResult.Failure -> { + val error = result.error // publish failure, inspect error + } +} +``` + +#### Subscribing to a channel. + +When subscribing to a channel, you can subscribe to a specific namespace/channel (ex: "default/channel"), or you can specify a wildcard (`*`) at the end of a channel path to receive events published to all channels that match (ex: "default/*"). + +Choosing the proper Coroutine Scope for the subscription Flow is critical. You should choose a scope to live for the lifetime in which you need the subscription. For example, if you want a subscription to be tied to a screen, you should consider using `viewModelScope` from an AndroidX `ViewModel`. The subscription Flow would persist configuration changes and be cancelled when `onCleared()` is triggered on the ViewModel. +When the Flow's Coroutine Scope is cancelled, the library will unsubscribe from the channel. + +```kotlin +coroutineScope.launch { + // subscribe returns a cold Flow. The subscription is established once a terminal operator is invoked. + val subscription: Flow = webSocketClient.subscribe("default/channel").onCompletion { + // Subscription has been unsubscribed + }.catch { throwable -> + // Subscription encountered an error and has been unsubscribed + // See throwable for cause + } + + // collect starts the subscription + subscription.collect { eventsMessage -> + // Returns a JsonElement type. + // Use Kotlin Serialization to convert into your preferred data structure. + val jsonData = eventsMessage.data + } +} +``` + +#### Subscribing to a channel with a different authorizer. + +```kotlin +coroutineScope.launch { + // subscribe returns a cold Flow. The subscription is established once a terminal operator is invoked. + val subscription: Flow = webSocketClient.subscribe( + channelName = "default/channel". + authorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") + ).onCompletion { + // Subscription has been unsubscribed + }.catch { throwable -> + // Subscription encountered an error and has been unsubscribed + // See throwable for cause + } + + // collect starts the subscription + subscription.collect { eventsMessage -> + // Returns a JsonElement type. + val jsonData = eventsMessage.data + // Use Kotlin Serialization to convert into your preferred data structure. + } +} +``` + +#### Disconnecting the WebSocket + +When you are done using the WebSocket and do not intend to call publish/subscribe on the client, you should disconnect the WebSocket. This will unsubscribe all channels. + +```kotlin +val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient +// set flushEvents to true if you want to wait for any pending publish operations to post to the WebSocket +// set flushEvents to false to immediately disconnect, discarding any pending posts to the WebSocket +webSocketClient.disconnect(flushEvents = true) // or false to immediately disconnect +``` + + + +### Create the Events class + +You can find your endpoint in the AWS AppSync Events console. It should start with `https` and end with `/event`. + +```swift +let eventsEndpoint = "https://abcdefghijklmnopqrstuvwxyz.appsync-api.us-east-1.amazonaws.com/event" +let events = Events(endpointURL: eventsEndpoint) +``` + +### Using the REST Client + +An `EventsRestClient` can be created to publish event(s) over REST. It accepts a publish authorizer that will be used by default for any publish calls within the client. + +#### Creating the REST Client + +```swift +let events = Events(endpointURL: eventsEndpoint) +let restClient = events.createRestClient( + publishAuthorizer: APIKeyAuthorizer(apiKey: "apiKey") +) +``` + +Additionally, you can pass custom options to the Rest Client. Current capabilities include passing a custom `URLSessionConfiguration` object, a prepend `URLRequestInterceptor` and enabling client library logs. +See [Collecting Client Library Logs](#collecting-client-library-logs) and `RestOptions` class for more details. + +```swift +let restClient = events.createRestClient( + publishAuthorizer: apiKeyAuthorizer, + options: .init( + urlSessionConfiguration: urlSessionConfiguration, // your instance of `URLSessionConfiguration` + logger: AppSyncEventsLogger(), // your implementation of `EventsLogger` + interceptor: AppSyncEventsURLRequestInterceptor() // your implementation of `URLRequestInterceptor` + ) +) +``` + +#### Publish a single event + +```swift +let defaultChannel = "default/channel" +let event = JSONValue(stringLiteral: "123") +do { + let result = try await restClient.publish( + channelName: defaultChannel, + event: event + ) + print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") +} catch { + print("Publish failure with error: \(error)") +} +``` + +#### Publish multiple events + +You can publish up to 5 events at a time. + +```swift +let defaultChannel = "default/channel" +let eventsList = [ + JSONValue(stringLiteral: "123"), + JSONValue(booleanLiteral: true), + JSONValue(floatLiteral: 1.25), + JSONValue(integerLiteral: 37), + JSONValue(dictionaryLiteral: ("key", "value")) +] + +do { + let result = try await restClient.publish( + channelName: defaultChannel, + events: eventsList + ) + print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") +} catch { + print("Publish failure with error: \(error)") +} +``` + +#### Publish with a different authorizer + +```swift +let defaultChannel = "default/channel" +let event = JSONValue(stringLiteral: "123") +let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") +do { + let result = try await restClient.publish( + channelName: defaultChannel, + event: event, + authorizer: apiKeyAuthorizer + ) + print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") +} catch { + print("Publish failure with error: \(error)") +} +``` + +### Using the WebSocket Client + +An `EventsWebSocketClient` can be created to publish and subscribe to channels. The WebSocket connection is managed by the library and connects on the first subscribe or publish operation. Once connected, the WebSocket will remain open. You should explicitly disconnect the client when you no longer need to subscribe or publish to channels. + +#### Creating the WebSocket Client + +```swift +let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") +let webSocketClient = events.createWebSocketClient( + connectAuthorizer: apiKeyAuthorizer, + publishAuthorizer: apiKeyAuthorizer, + subscribeAuthorizer: apiKeyAuthorizer +) +``` + +Additionally, you can pass custom options to the WebSocket Client. Current capabilities include passing a custom `URLSessionConfiguration` object, a prepend `URLRequestInterceptor` and enabling client library logs. +See [Collecting Client Library Logs](#collecting-client-library-logs) and `WebSocketOptions` class for more details. + +```swift +let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") +let webSocketClient = events.createWebSocketClient( + connectAuthorizer: apiKeyAuthorizer, + publishAuthorizer: apiKeyAuthorizer, + subscribeAuthorizer: apiKeyAuthorizer, + options: .init( + urlSessionConfiguration: urlSessionConfiguration, // your instance of `URLSessionConfiguration` + logger: AppSyncEventsLogger(), // your implementation of `EventsLogger` + interceptor: AppSyncEventsURLRequestInterceptor() // your implementation of `URLRequestInterceptor` + ) +) +``` + +#### Publish a Single Event + +```swift +let defaultChannel = "default/channel" +let event = JSONValue(stringLiteral: "123") +do { + let result = try await websocketClient.publish( + channelName: defaultChannel, + event: event + ) + print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") +} catch { + print("Publish failure with error: \(error)") +} +``` + +#### Publish multiple Events + +You can publish up to 5 events at a time. + +```swift +let defaultChannel = "default/channel" +let eventsList = [ + JSONValue(stringLiteral: "123"), + JSONValue(booleanLiteral: true), + JSONValue(floatLiteral: 1.25), + JSONValue(integerLiteral: 37), + JSONValue(dictionaryLiteral: ("key", "value")) +] + +do { + let result = try await websocketClient.publish( + channelName: defaultChannel, + events: eventsList + ) + print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") +} catch { + print("Publish failure with error: \(error)") +} +``` + +#### Publish with a different authorizer + +```swift +let defaultChannel = "default/channel" +let event = JSONValue(stringLiteral: "123") +let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") +do { + let result = try await websocketClient.publish( + channelName: defaultChannel, + event: event, + authorizer: apiKeyAuthorizer + ) + print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") +} catch { + print("Publish failure with error: \(error)") +} +``` + +#### Subscribing to a channel + +When subscribing to a channel, you can subscribe to a specific namespace/channel (e.g. `default/channel`), or you can specify a wildcard (`*`) at the end of a channel path to receive events published to all channels that match (e.g. `default/*`). + +```swift +let defaultChannel = "default/channel" +let subscription = try websocketClient.subscribe(channelName: defaultChannel) +let task = Task { + for try await message in subscription { + print("Subscription received message: \(message))" + } +} +``` +To unsubscribe from the channel, you can cancel the enclosing task for the `AsyncThrowingStream`. + +```swift +task.cancel() +``` + +#### Subscribing to a channel with a different authorizer + +```swift +let defaultChannel = "default/channel" +let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") +let subscription = try websocketClient.subscribe( + channelName: defaultChannel, + authorizer: apiKeyAuthorizer +) +let task = Task { + for try await message in subscription { + print("Subscription received message: \(message))" + } +} +``` + +#### Disconnecting the WebSocket + +When you are done using the WebSocket and do not intend to call publish/subscribe on the client, you should disconnect the WebSocket. This will unsubscribe all channels. + +```swift +// set flushEvents to true if you want to wait for any pending publish operations to post to the WebSocket +// set flushEvents to false to immediately disconnect, discarding any pending posts to the WebSocket +try await webSocketClient.disconnect(flushEvents: true) // or false to immediately disconnect +``` + + + +### Collecting Client Library Logs + + + +In the Rest Client and WebSocket Client examples, we demonstrated logging to a custom logger. Here is an example of a custom logger that writes logs to Android's Logcat. You are free to implement your own `Logger` type. + +```kotlin +class AndroidLogger( + private val namespace: String, + override val thresholdLevel: LogLevel +) : Logger { + + override fun error(message: String) { + if (!thresholdLevel.above(LogLevel.ERROR)) { + Log.e(namespace, message) + } + } + + override fun error(message: String, error: Throwable?) { + if (!thresholdLevel.above(LogLevel.ERROR)) { + Log.e(namespace, message, error) + } + } + + override fun warn(message: String) { + if (!thresholdLevel.above(LogLevel.WARN)) { + Log.w(namespace, message) + } + } + + override fun warn(message: String, issue: Throwable?) { + if (!thresholdLevel.above(LogLevel.WARN)) { + Log.w(namespace, message, issue) + } + } + + override fun info(message: String) { + if (!thresholdLevel.above(LogLevel.INFO)) { + Log.i(namespace, message) + } + } + + override fun debug(message: String) { + if (!thresholdLevel.above(LogLevel.DEBUG)) { + Log.d(namespace, message) + } + } + + override fun verbose(message: String) { + if (!thresholdLevel.above(LogLevel.VERBOSE)) { + Log.v(namespace, message) + } + } +} +``` + + + +In the Rest Client and WebSocket Client examples, we demonstrated logging to a custom logger. Here is an example of a custom logger that writes logs to Xcode console. You are free to implement your own `EventsLogger` type. + +```swift +import os +import Foundation +import AWSAppSyncEvents + +public final class AppSyncEventsLogger: EventsLogger { + static let lock: NSLocking = NSLock() + + static var _logLevel = LogLevel.error + + public init() { } + + public var logLevel: LogLevel { + get { + AppSyncEventsLogger.lock.lock() + defer { + AppSyncEventsLogger.lock.unlock() + } + + return AppSyncEventsLogger._logLevel + } + set { + AppSyncEventsLogger.lock.lock() + defer { + AppSyncEventsLogger.lock.unlock() + } + + AppSyncEventsLogger._logLevel = newValue + } + } + + public func error(_ log: @autoclosure () -> String) { + os_log("%@", type: .error, log()) + } + + public func error(_ error: @autoclosure () -> Error) { + os_log("%@", type: .error, error().localizedDescription) + } + + public func warn(_ log: @autoclosure () -> String) { + guard logLevel.rawValue >= LogLevel.warn.rawValue else { + return + } + + os_log("%@", type: .info, log()) + } + + public func info(_ log: @autoclosure () -> String) { + guard logLevel.rawValue >= LogLevel.info.rawValue else { + return + } + + os_log("%@", type: .info, log()) + } + + public func debug(_ log: @autoclosure () -> String) { + guard logLevel.rawValue >= LogLevel.debug.rawValue else { + return + } + + os_log("%@", type: .debug, log()) + } + + public func verbose(_ log: @autoclosure () -> String) { + guard logLevel.rawValue >= LogLevel.verbose.rawValue else { + return + } + + os_log("%@", type: .debug, log()) + } +} + +``` + + +--- + +--- +title: "Modify Amplify-generated AWS resources" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-10-15T16:14:40.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/override-resources/" +--- + +Amplify GraphQL API uses a variety of auto-generated, underlying AWS services and resources. You can customize these underlying resources to optimize the deployed stack for your specific use case. + +In your Amplify app, you can access every underlying resource using CDK ["L2"](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_using) or ["L1"](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_l1_using) constructs. Access the generated resources as L2 constructs via the `.resources` property on the returned stack or access the generated resources as L1 constructs using the `.resources.cfnResources` property. + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { data } from './data/resource'; + +const backend = defineBackend({ + data +}); + +const { cfnResources } = backend.data.resources; + +for (const table of Object.values(cfnResources.amplifyDynamoDbTables)) { + table.pointInTimeRecoveryEnabled = true; +} +``` + +## Customize Amplify-generated AppSync GraphQL API resources + +Apply all the customizations on `backend.data.resources.graphqlApi` or `backend.data.resources.cfnResources.cfnGraphqlApi`. For example, to enable X-Ray tracing for the AppSync GraphQL API: + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { data } from './data/resource'; + +const backend = defineBackend({ + data +}); + +const { cfnResources } = backend.data.resources; + +cfnResources.cfnGraphqlApi.xrayEnabled = true; +``` + +## Customize Amplify-generated resources for data models + +Pass in the model type name into `backend.data.resources.amplifyDynamoDbTables["MODEL_NAME"]` to modify the resources generated for that particular model type. For example, to enable time-to-live on the Todo `@model` type's DynamoDB table: + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { data } from './data/resource'; + +const backend = defineBackend({ + data +}); + +const { cfnResources } = backend.data.resources; + +cfnResources.amplifyDynamoDbTables["Todo"].timeToLiveAttribute = { + attributeName: "ttl", + enabled: true, +}; +``` + +### Example - Configure billing mode on a DynamoDB table + +Set the [DynamoDB billing mode](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-billingmode) for the DynamoDB table as either "PROVISIONED" or "PAY_PER_REQUEST". + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { BillingMode } from "aws-cdk-lib/aws-dynamodb"; +import { data } from './data/resource'; + +const backend = defineBackend({ + data +}); + +const { cfnResources } = backend.data.resources; + +cfnResources.amplifyDynamoDbTables['Todo'].billingMode = BillingMode.PAY_PER_REQUEST; +``` + +### Example - Configure provisioned throughput for a DynamoDB table + +Override the default [ProvisionedThroughput](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-provisionedthroughput) provisioned for each model table and its Global Secondary Indexes (GSI). This override is only valid if the "DynamoDBBillingMode" is set to "PROVISIONED". + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { data } from './data/resource'; + +const backend = defineBackend({ + data +}); + +const { cfnResources } = backend.data.resources; + +cfnResources.amplifyDynamoDbTables["Todo"].provisionedThroughput = { + readCapacityUnits: 5, + writeCapacityUnits: 5, +}; +``` + +### Example - Enable point-in-time recovery for a DynamoDB table + +Enable/disable [DynamoDB point-in-time recovery](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-pointintimerecoveryspecification.html) for each model table. + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { data } from './data/resource'; + +const backend = defineBackend({ + data +}); + +const { cfnResources } = backend.data.resources; + +cfnResources.amplifyDynamoDbTables['Todo'].pointInTimeRecoveryEnabled = true; +``` + +--- + +--- +title: "Manage Data with Amplify console" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-08-06T19:20:15.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/manage-with-amplify-console/" +--- + +The **Data manager** page in the Amplify Console offers a user-friendly interface for managing the backend GraphQL API data of an application. It enables real-time creation and updates of application data, eliminating the need to build separate admin views. + +If you have not yet created a **data** resource, visit the [Data setup guide](/[platform]/build-a-backend/data/set-up-data/). + +## Access Data manager + +After you've deployed your data resource, you can access the manager on Amplify console. + +1. Log in to the [Amplify console](https://console.aws.amazon.com/amplify/home) and choose your app. +2. Select the branch you would like to access. +3. Select **Data** from the left navigation bar. +4. Then, select **Data manager**. + +### To create a record + +1. On the **Data manager** page, select a table from the **Select table** dropdown. For this example, we are using a *Todo* table. +2. Select **Create Todo**. +3. In the **Add Todo** pane, specify your custom values for the fields in the table. For example, enter *my first todo* for the *Content* field and toggle the *Is done* field. +4. Select **Submit**. + +### To update a record + +1. On the **Data manager** page, select a table from the **Select table** dropdown. +2. From the list of records, select a record you want to update. +3. In the **Edit Todo** pane, make any changes to your record, and then select **Submit**. + +### To delete a record(s) + +1. On the **Data manager** page, select a table from the **Select table** dropdown. +2. From the list of records, select the checkbox to the left of the record(s) you want to delete. +3. Select the **Actions** dropdown, and then select **delete item(s)** . + +### To Seed records + +1. On the **Data manager** page, select a table from the **Select table** dropdown. +2. Select the **Actions** dropdown and then select **Auto-generate data**. +3. In the **Auto-generate data** pane, specify how many rows of data you want to generate and constraints for the generated data. +4. Then select **Generate data** + +You can generate up to 100 records at a time. + +> **Warning:** Seed data cannot be generated for tables that have the following field types: AWSPhone, Enum, Custom Type, or Relationship + +### To download records + +1. On the **Data manager** page, select a table from the **Select table** dropdown. +2. Select the **Actions** dropdown. +3. Here you have two options for downloading data. + - Choose **Download selected items (.csv)** to download only the selected rows of data. + - Choose **Download all items (.csv)** to download all rows of records on the currently selected table. +4. Once you have selected a download option, your data should immediately start downloading as a CSV file. + +--- + +--- +title: "AWS AppSync Apollo Extensions" +section: "build-a-backend/data" +platforms: ["android", "swift"] +gen: 2 +last-updated: "2025-01-29T16:47:20.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/aws-appsync-apollo-extensions/" +--- + +AWS AppSync Apollo Extensions provide a seamless way to connect to your AWS AppSync backend using Apollo client, an open-source GraphQL client. + +To learn more about Apollo, see https://www.apollographql.com/docs/ios/. + + + +To learn more about Apollo, see https://www.apollographql.com/docs/kotlin. + + +## Features + +AWS AppSync Apollo Extensions provide AWS AppSync authorizers to be used with the Apollo client to make it simple to apply the correct authorization payloads to your GraphQL operations. + + +Additionally, we publish an optional Amplify extension that allows Amplify to provide auth tokens and signing logic for the corresponding Authorizers. + + + +Additionally, the included Amplify components allow Amplify to provide auth tokens and signing logic for the corresponding Authorizers. + + +## Install the AWS AppSync Apollo Extensions library + + + +#### [With Amplify] + +Add the `apollo-appsync-amplify` dependency to your app/build.gradle.kts file. + +```kotlin title="app/build.gradle.kts" +dependencies { + // highlight-start + // Connect Apollo to AppSync, delegating some implementation details to Amplify + implementation("com.amplifyframework:apollo-appsync-amplify:1.0.0") + // highlight-end +} +``` + +#### [Without Amplify] + +Add the `apollo-appsync` dependency to your app/build.gradle.kts file. + +```kotlin title="app/build.gradle.kts" +dependencies { + // highlight-start + // Connect Apollo to AppSync without using Amplify + implementation("com.amplifyframework:apollo-appsync:1.0.0") + // highlight-end +} +``` + + + + +Add AWS AppSync Apollo Extensions into your project using Swift Package Manager. + +Enter its GitHub URL (`https://github.com/aws-amplify/aws-appsync-apollo-extensions-swift`), select **Up to Next Major Version** and click **Add Package** + +* Select the following libraries: + * **AWSAppSyncApolloExtensions** + + +## Connecting to AWS AppSync with Apollo client + + +### Creating the ApolloClient + +#### [With Amplify] +Before you begin, you will need an Amplify Data backend deploy. To get started, see [Set up Data](/[platform]/build-a-backend/data/set-up-data/). + +Once you have deployed your backend and created the `amplify_outputs.json` file, you can use Amplify library to read and retrieve your configuration values with the following steps: + +```kotlin +// Use apiKey auth mode, reading configuration from AmplifyOutputs +val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) +val apolloClient = ApolloClient.Builder() + .appSync(connector.endpoint, connector.apiKeyAuthorizer()) + .build() +``` + +#### [Without Amplify] +You can create your Apollo client by using our provided AWS AppSync endpoint and authorizer classes. + +```kotlin +val endpoint = AppSyncEndpoint("") +// Continue Reading to see more authorizer examples +val authorizer = ApiKeyAuthorizer("[API_KEY]") +val apolloClient = ApolloClient.Builder() + .appSync(endpoint, authorizer) + .build() +``` + +### Providing AppSync Authorizers + +#### [With Amplify] + +The AWS AppSync Apollo Extensions library provides a number of Authorizer classes to match the various authorization strategies that may be in use in your schema. You should choose the appropriate Authorizer type for your authorization strategy. To read more about the strategies and their corresponding auth modes, see [Available authorization strategies](/[platform]/build-a-backend/data/customize-authz/#available-authorization-strategies). + +Some common ones are + +* `publicAPIkey` strategy, `apiKey` authMode, **APIKeyAuthorizer** +* `guest` strategy, `identityPool` authMode, **IAMAuthorizer** +* `owner` strategy, `userPool` authMode, **AuthTokenAuthorizer** + +If you define multiple authorization strategies within your schema, you will have to create separate Apollo client instances for each Authorizer that you want to use in your app. + +#### API_KEY + +An `ApiKeyAuthorizer` can read the API key from `amplify_outputs.json`, provide a hardcoded API key, or fetch the API key from some source: + +```kotlin +// highlight-start +// Using ApolloAmplifyConnector to read API key from amplify_outputs.json +val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) +val authorizer = connector.apiKeyAuthorizer() +//highlight-end +// or +// highlight-start +// Use a hard-coded API key +val authorizer = ApiKeyAuthorizer("[API_KEY]") +//highlight-end +// or +// highlight-start +// Fetch the API key from some source. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = ApiKeyAuthorizer { fetchApiKey() } +//highlight-end +``` + +#### AMAZON_COGNITO_USER_POOLS + +You can use `AmplifyApolloConnector` to get an `AuthTokenAuthorizer` instance that supplies the token for the current logged-in Amplify user, or implement the token fetching yourself. + +```kotlin +// highlight-start +// Using ApolloAmplifyConnector to get the authorizer that connects to your +// Amplify instance +val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) +val authorizer = connector.authTokenAuthorizer() +//highlight-end +// or +// highlight-start +// Using the ApolloAmplifyConnector companion function +val authorizer = AuthTokenAuthorizer { + ApolloAmplifyConnector.fetchLatestCognitoAuthToken() +} +//highlight-end +// or +// highlight-start +// Use your own token fetching. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = AuthTokenAuthorizer { + fetchLatestAuthToken() +} +//highlight-end +``` + +You can provide your own custom `fetchLatestAuthToken` provider for **AWS_LAMBDA** and **OPENID_CONNECT** auth modes. + +#### AWS_IAM + +You can use the `ApolloAmplifyConnector` to delegate token fetching and request +signing to Amplify. + +```kotlin +// highlight-start +// Using ApolloAmplifyConnector to get the authorizer that connects to your +// Amplify instance +val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) +val authorizer = connector.iamAuthorizer() +//highlight-end +// or +// highlight-start +// Using the ApolloAmplifyConnector companion function +val authorizer = IamAuthorizer { + ApolloAmplifyConnector.signAppSyncRequest(it, "us-east-1") +} +//highlight-end +``` + +#### [Without Amplify] + +AWS AppSync supports the following [authorization modes](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html). Use the corresponding Authorizer that matches the chosen authorization type. + +Some common ones are + +* API Key Authorization -> **APIKeyAuthorizer** +* IAM Authorization -> **IAMAuthorizer** +* Cognito User Pools -> **AuthTokenAuthorizer** + +If you apply multiple authorization directives in your schema, you will have to create separate Apollo client instances for each Authorizer that you want to use in your app. + +#### API_KEY + +An `ApiKeyAuthorizer` can be used with a hardcoded API key or by fetching the key from some source.: + +```kotlin +// highlight-start +// Use a hard-coded API key +val authorizer = ApiKeyAuthorizer("[API_KEY]") +//highlight-end +// or +// highlight-start +// Fetch the API key from some source. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = ApiKeyAuthorizer { fetchApiKey() } +//highlight-end +``` + +#### AMAZON_COGNITO_USER_POOLS + +When working directly with AppSync, you must implement the token fetching yourself. + +```kotlin +// highlight-start +// Use your own token fetching. This function may be called many times, +// so it should implement appropriate caching internally. +val authorizer = AuthTokenAuthorizer { + fetchLatestAuthToken() +} +//highlight-end +``` + +#### AWS_IAM + +When working directly with AppSync, you must implement the request signing yourself. + +```kotlin +// highlight-start +// Provide an implementation of the signing function. This function should implement the +// AWS Sig-v4 signing logic and return the authorization headers containing the token and signature. +val authorizer = IamAuthorizer { signRequestAndReturnHeaders(it) } +// highlight-end +``` + + + + +AWS AppSync supports the following [authorization modes](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html): + +### API_KEY + +```swift +import AWSAppSyncApolloExtensions + +let authorizer = APIKeyAuthorizer(apiKey: "[API_KEY]") +let interceptor = AppSyncInterceptor(authorizer) +``` + +### AMAZON_COGNITO_USER_POOLS + +If you are using Amplify Auth, you can create a method that retrieves the Cognito access token + +```swift +import Amplify + +func getUserPoolAccessToken() async throws -> String { + let authSession = try await Amplify.Auth.fetchAuthSession() + if let result = (authSession as? AuthCognitoTokensProvider)?.getCognitoTokens() { + switch result { + case .success(let tokens): + return tokens.accessToken + case .failure(let error): + throw error + } + } + throw AuthError.unknown("Did not receive a valid response from fetchAuthSession for get token.") +} +``` + +Then create the AuthTokenAuthorizer with this method. + +```swift +import AWSAppSyncApolloExtensions + +let authorizer = AuthTokenAuthorizer(fetchLatestAuthToken: getUserPoolAccessToken) +let interceptor = AppSyncInterceptor(authorizer) +``` + +### AWS_IAM + +If you are using Amplify Auth, you can use the following method for AWS_IAM auth + +```swift +import AWSCognitoAuthPlugin +import AWSAppSyncApolloExtensions + +let authorizer = IAMAuthorizer( + signRequest: AWSCognitoAuthPlugin.createAppSyncSigner( + region: "[REGION]")) +``` + + + +## Connecting Amplify Data to Apollo client + +Before you begin, you will need an Amplify Data backend deploy. To get started, see [Set up Data](/[platform]/build-a-backend/data/set-up-data/). + +Once you have deployed your backend and created the `amplify_outputs.json` file, you can use Amplify library to read and retrieve your configuration values with the following steps: + +1. Enter its GitHub URL (`https://github.com/aws-amplify/amplify-swift`), select **Up to Next Major Version** and click **Add Package** +2. Select the following libraries: + 1. **AWSPluginsCore** +3. Drag and drop the `amplify_outputs.json` file into your Xcode project. +4. Initialize the configuration with `try AWSAppSyncConfiguration(with: .amplifyOutputs)` + +The resulting configuration object will have the `endpoint`, `region`, and optional `apiKey.` The following example shows reading the `amplify_outputs.json` file from the main bundle to instantiate the configuration and uses it to configure the Apollo client for **API_Key** authorization. + +```swift +import Apollo +import ApolloAPI +import AWSPluginsCore +import AWSAppSyncApolloExtensions + +func createApolloClient() throws -> ApolloClient { + let store = ApolloStore(cache: InMemoryNormalizedCache()) + + // 1. Read AWS AppSync API configuration from `amplify_outputs.json` + let configuration = try AWSAppSyncConfiguration(with: .amplifyOutputs) + + // 2. Use `configuration.apiKey` with APIKeyAuthorizer + let authorizer = APIKeyAuthorizer(apiKey: configuration.apiKey ?? "") + let interceptor = AppSyncInterceptor(authorizer) + let interceptorProvider = DefaultPrependInterceptorProvider(interceptor: interceptor, + store: store) + // 3. Use `configuration.endpoint` with RequestChainNetworkTransport + let transport = RequestChainNetworkTransport(interceptorProvider: interceptorProvider, + endpointURL: configuration.endpoint) + + return ApolloClient(networkTransport: transport, store: store) +} +``` + +The AWS AppSync Apollo Extensions library provides a number of Authorizer classes to match the various authorization strategies that may be in use in your schema. You should choose the appropriate Authorizer type for your authorization strategy. To read more about the strategies and their corresponding auth modes, see [Available authorization strategies](/[platform]/build-a-backend/data/customize-authz/#available-authorization-strategies). + +Some common ones are + +* `publicAPIkey` strategy, `apiKey` authMode, **APIKeyAuthorizer** +* `guest` strategy, `identityPool` authMode, **IAMAuthorizer** +* `owner` strategy, `userPool` authMode, **AuthTokenAuthorizer** + +If you define multiple authorization strategies within your schema, you will have to create separate Apollo client instances for each Authorizer that you want to use in your app. + + +## Downloading the AWS AppSync schema + +The schema is used by Apollo’s code generation tool to generate API code that helps you execute GraphQL operations. The following steps integrate your AppSync schema with Apollo's code generation process: + + +1. Navigate to your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home) +2. On the left side, select Schema +3. Select the "Export schema" dropdown and download the `schema.json` file. +4. Add this file to your project as directed by [Apollo Code Generation documentation](https://www.apollographql.com/docs/ios/code-generation/introduction). + +You can alternatively download the introspection schema using the [`fetch-schema`](https://www.apollographql.com/docs/ios/code-generation/codegen-cli#fetch-schema) command with the `amplify-ios-cli` tool. + + + +1. Navigate to your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home) +2. On the left side, select Schema +3. Select the "Export schema" dropdown and download the `schema.json` file. +4. Add this file to your project as directed by [Apollo documentation](https://www.apollographql.com/docs/kotlin/advanced/plugin-recipes#specifying-the-schema-location) + + +## Generating Queries, Mutations, and Subscriptions for Apollo client + + + +#### [With Amplify] +**Amplify provided .graphql files** +1. Within your Amplify Gen 2 backend, run: `npx ampx generate graphql-client-code --format graphql-codegen --statement-target graphql --out graphql` +2. Copy the generated files (`mutations.graphql`, `queries.graphql`, `subscriptions.graphql`) to your `{app}/src/main/graphql` folder as shown in the [Apollo documentation](https://www.apollographql.com/docs/kotlin#getting-started) + +**Manual** +1. Navigate to the **Queries** tab in your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home). Here, you can test queries, mutations, and subscriptions in the GraphQL playground. +2. Enter your GraphQL operation (query, mutation, or subscription) in the editor and select **Run** to execute it. +3. Observe the request and response structure in the results. This gives you insight into the exact call patterns and structure that Apollo will use. +4. Copy the GraphQL operation(s) from the playground and pass them to to your `{app}/src/main/graphql` folder as shown in the [Apollo documentation](https://www.apollographql.com/docs/kotlin#getting-started) + +#### [Without Amplify] + +1. Navigate to the **Queries** tab in your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home). Here, you can test queries, mutations, and subscriptions in the GraphQL playground. +2. Enter your GraphQL operation (query, mutation, or subscription) in the editor and click **Run** to execute it. +3. Observe the request and response structure in the results. This gives you insight into the exact call patterns and structure that Apollo will use. +4. Copy the GraphQL operation from the playground and pass it to Apollo's code generation tool to automatically generate the corresponding API code for your project. + + + + +1. Navigate to the **Queries** tab in your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home). Here, you can test queries, mutations, and subscriptions in the GraphQL playground. +2. Enter your GraphQL operation (query, mutation, or subscription) in the editor and click **Run** to execute it. +3. Observe the request and response structure in the results. This gives you insight into the exact call patterns and structure that Apollo will use. +4. Copy the GraphQL operation from the playground and pass it to Apollo's code generation tool to automatically generate the corresponding API code for your project. + + + +## Type Mapping AppSync Scalars +By default, [AWS AppSync Scalars](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html#graph-ql-aws-appsync-scalars) will default to the `Any` type. You can map these scalars to more explicit types by editing the `apollo` block in your `app/build.gradle[.kts]` file. In the example below, we are now mapping a few of our AppSync scalar types to `String` instead of `Any`. Additional improvements could be made by writing [custom class adapters](https://www.apollographql.com/docs/kotlin/essentials/custom-scalars#define-class-mapping) to convert date/time scalars into Kotlin date/time class types. + +```kotlin +apollo { + service("{serviceName}") { + packageName.set("{packageName}") + mapScalarToKotlinString("AWSDateTime") + mapScalarToKotlinString("AWSEmail") + } +} +``` + + + +## Connecting to AWS AppSync real-time endpoint + +The following example shows how you can create an Apollo client that allows performing GraphQL subscription operations with AWS AppSync. + +```swift +import Apollo +import ApolloAPI +import ApolloWebSocket +import AWSPluginsCore +import AWSAppSyncApolloExtensions + +func createApolloClient() throws -> ApolloClient { + let store = ApolloStore(cache: InMemoryNormalizedCache()) + let configuration = try AWSAppSyncConfiguration(with: .amplifyOutputs) + + // 1. Create your authorizer + let authorizer = /* your Authorizer */ + let interceptor = AppSyncInterceptor(authorizer) + + let interceptorProvider = DefaultPrependInterceptorProvider(interceptor: interceptor, + store: store) + let transport = RequestChainNetworkTransport(interceptorProvider: interceptorProvider, + endpointURL: configuration.endpoint) + + // 2. Create the AWS AppSync compatible websocket client + let websocket = AppSyncWebSocketClient(endpointURL: configuration.endpoint, + authorizer: authorizer) + // 3. Add it to the WebSocketTransport + let webSocketTransport = WebSocketTransport(websocket: websocket) + // 4. Create a SplitNetworkTransport + let splitTransport = SplitNetworkTransport( + uploadingNetworkTransport: transport, + webSocketNetworkTransport: webSocketTransport + ) + // 5. Pass the SplitNetworkTransport to the ApolloClient + return ApolloClient(networkTransport: splitTransport, store: store) +} +``` + + +--- + +--- +title: "Enable logging" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-13T22:13:21.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/enable-logging/" +--- + +You can enable logging to debug your GraphQL API using Amazon CloudWatch logs. To learn more about logging and monitoring capabilities for your GraphQL API, visit the [AWS AppSync documentation for logging and monitoring](https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html). + +## Enable default logging configuration + +Default logging can be enabled by setting the `logging` property to `true` in the call to `defineData`. For example: + +```ts title="amplify/data/resource.ts" +export const data = defineData({ + // ... + logging: true +}); +``` + +Using `logging: true` applies the default configuration: +- `excludeVerboseContent: true` (see [AppSync's Request-level logs](https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html#cwl)) +- `fieldLogLevel: 'none'` (see [AppSync's Field-level logs](https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html#cwl)) +- `retention: '1 week'` (see [Enum RetentionDays](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_logs.RetentionDays.html)) + +## Customize logging configuration + +You can customize individual configuration values by providing a [`DataLogConfig`](#datalogconfig-fields) object. For example: + +```ts title="amplify/data/resource.ts" +export const data = defineData({ + // ... + logging: { + excludeVerboseContent: false, + fieldLogLevel: 'all', + retention: '1 month' + } +}); +``` + +> **Warning:** **WARNING**: Setting `excludeVerboseContent` to `false` logs full queries and user parameters, which can contain sensitive data. We recommend limiting CloudWatch log access to only those roles or users (e.g., DevOps or developers) who genuinely require it, by carefully scoping your IAM policies. + +## Configuration Properties + +### `logging` +- `true`: Enables default logging. +- `DataLogConfig` object: Overrides one or more default fields. + +### `DataLogConfig` Fields + +- **`excludeVerboseContent?: boolean`** + - Defaults to `true` + - When `false`, logs can contain request-level logs. See [AppSync's Request-Level Logs](https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html#cwl). + +- **`fieldLogLevel?: DataLogLevel`** + - Defaults to `'none'` + - Supported values of [AppSync's Field Log Levels](https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html#cwl): + - `'none'` + - `'error'` + - `'info'` + - `'debug'` + - `'all'` + +- **`retention?: LogRetention`** + - Number of days to keep the logs + - Defaults to `'1 week'` + - Supported values of [Enum RetentionDays](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_logs.RetentionDays.html): + - `'1 day'` + - `'3 days'` + - `'5 days'` + - `'1 week'` + - `'2 weeks'` + - `'1 month'` + - `'2 months'` + - `'3 months'` + - `'4 months'` + - `'5 months'` + - `'6 months'` + - `'1 year'` + - `'13 months'` + - `'18 months'` + - `'2 years'` + - `'5 years'` + - `'10 years'` + - `'infinite'` + +--- + +--- +title: "Field-level validation" +section: "build-a-backend/data" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-03-17T16:14:03.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/data/field-level-validation/" +--- + +You can enable field-level validation in your model schema by chaining a `validate` function to the field. + +## Examples + +```ts title="amplify/data/resource.ts" +const schema = a.schema({ + Todo: a.model({ + content: a.string().validate(v => + v + .minLength(1, 'Content must be at least 1 character long') + .maxLength(100, 'Content must be less than 100 characters') + .matches('^[a-zA-Z0-9\\\\s]+$', 'Content must contain only letters, numbers, and spaces') + ) + }) + .authorization(allow => [allow.publicApiKey()]) +}); +``` + +## Supported validators + +### String Validators +For `string` fields: + +| Validator | Description | Parameters | Example | +| --- | --- | --- | --- | +| `minLength` | Validates that a string field has at least the specified length | β€’ `length`: The minimum length required
    β€’ `errorMessage`: Optional custom error message | `a.string().validate(v => v.minLength(5, 'String must be at least 5 characters'))` | +| `maxLength` | Validates that a string field does not exceed the specified length | β€’ `length`: The maximum length allowed
    β€’ `errorMessage`: Optional custom error message | `a.string().validate(v => v.maxLength(100, 'String must be at most 100 characters'))` | +| `startsWith` | Validates that a string field starts with the specified prefix | β€’ `prefix`: The prefix the string must start with
    β€’ `errorMessage`: Optional custom error message | `a.string().validate(v => v.startsWith("prefix-", 'String must start with prefix-'))` | +| `endsWith` | Validates that a string field ends with the specified suffix | β€’ `suffix`: The suffix the string must end with
    β€’ `errorMessage`: Optional custom error message | `a.string().validate(v => v.endsWith("-suffix", 'String must end with -suffix'))` | +| `matches` | Validates that a string field matches the specified regex pattern using the **Java regex engine**. See notes below. | β€’ `pattern`: The regex pattern the string must match
    β€’ `errorMessage`: Optional custom error message | `a.string().validate(v => v.matches("^[a-zA-Z0-9]+$", 'String must match the pattern'))` | + + + +**Note:** Our schema transformer uses the Java regex engine under the hood. Because of how TypeScript processes string literals, you must quadruple-escape special regex characters in your schema. In a TypeScript string literal, writing `\\\\s` produces the string `\\s`, which is the correct form for the Java regex engine. If you write `\\s`, it produces `\s`, which is invalid. Therefore, for the `matches` validator, ensure you use quadruple-escaping. For example: +`a.string().validate(v => v.matches("^[a-zA-Z0-9\\\\s]+$", 'Content must contain only letters, numbers, and spaces'))` + + + +### Numeric Validators +For `integer` and `float` fields: + +| Validator | Description | Parameters | Example | +| --- | --- | --- | --- | +| `gt` | Validates that a numeric field is greater than the specified value | β€’ `value`: The value the field must be greater than
    β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.gt(10, 'Must be greater than 10'))` | +| `gte` | Validates that a numeric field is greater than or equal to the specified value | β€’ `value`: The value the field must be greater than or equal to
    β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.gte(10, 'Must be at least 10'))` | +| `lt` | Validates that a numeric field is less than the specified value | β€’ `value`: The value the field must be less than
    β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.lt(10, 'Must be less than 10'))` | +| `lte` | Validates that a numeric field is less than or equal to the specified value | β€’ `value`: The value the field must be less than or equal to
    β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.lte(10, 'Must be at most 10'))` | +| `positive` | Validates that a numeric field is positive | β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.positive('Must be positive'))` | +| `negative` | Validates that a numeric field is negative | β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.negative('Must be negative'))` | + + + +**Note:** Currently, we only support validation on **non-array** fields of type `string`, `integer`, and `float`. + + + +--- + +--- +title: "API References" +section: "build-a-backend/data" +platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] +gen: 2 +last-updated: "" +url: "https://docs.amplify.aws/react/build-a-backend/data/reference/" +--- + + + +--- + +--- +title: "Storage" +section: "build-a-backend" +platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] +gen: 2 +last-updated: "2024-05-21T17:56:46.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/" +--- + + + +--- + +--- +title: "Set up Storage" +section: "build-a-backend/storage" +platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] +gen: 2 +last-updated: "2025-11-13T16:29:27.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/set-up-storage/" +--- + +In this guide, you will learn how to set up storage in your Amplify app. You will set up your backend resources, and enable listing, uploading, and downloading files. + +If you have not yet created an Amplify app, visit the [quickstart guide](/[platform]/start/quickstart/). + +Amplify Storage seamlessly integrates file storage and management capabilities into frontend web and mobile apps, built on top of Amazon Simple Storage Service (Amazon S3). It provides intuitive APIs and UI components for core file operations, enabling developers to build scalable and secure file storage solutions without dealing with cloud service complexities. + +## Building your storage backend + +First, create a file `amplify/storage/resource.ts`. This file will be the location where you configure your storage backend. Instantiate storage using the `defineStorage` function and providing a `name` for your storage bucket. This `name` is a friendly name to identify your bucket in your backend configuration. Amplify will generate a unique identifier for your app using a UUID, the name attribute is just for use in your app. + +```ts title="amplify/storage/resource.ts" +import { defineStorage } from '@aws-amplify/backend'; + +export const storage = defineStorage({ + name: 'amplifyTeamDrive' +}); +``` + +Import your storage definition in your `amplify/backend.ts` file that contains your backend definition. Add storage to `defineBackend`. + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +// highlight-next-line +import { storage } from './storage/resource'; + +defineBackend({ + auth, + // highlight-next-line + storage +}); +``` + +Now when you run `npx ampx sandbox` or deploy your app on Amplify, it will configure an Amazon S3 bucket where your files will be stored. Before files can be accessed in your application, you must configure storage access rules. + +To deploy these changes, commit them to git and push the changes upstream. Amplify's CI/CD system will automatically pick up the changes and build and deploy the updates. + +```bash title="Terminal" showLineNumbers={false} +git commit -am "add storage backend" +git push +``` + +### Define File Path Access + +By default, no users or other project resources have access to any files in the storage bucket. Access must be explicitly granted within `defineStorage` using the `access` callback. + +The access callback returns an object where each key in the object is a file path and each value in the object is an array of access rules that apply to that path. + +The following example shows you how you can set up your file storage structure for a generic photo sharing app. Here, + +1. Guests have access to see all profile pictures and only the users that uploaded the profile picture can replace or delete them. Users are identified by their Identity Pool ID in this case i.e. identityID. +2. There's also a general pool where all users can submit pictures. + +[Learn more about customizing access to file path](/[platform]/build-a-backend/storage/authorization/). + +```ts title="amplify/storage/resource.ts" +export const storage = defineStorage({ + name: 'amplifyTeamDrive', + access: (allow) => ({ + 'profile-pictures/{entity_id}/*': [ + allow.guest.to(['read']), + allow.entity('identity').to(['read', 'write', 'delete']) + ], + 'picture-submissions/*': [ + allow.authenticated.to(['read','write']), + allow.guest.to(['read', 'write']) + ], + }) +}); +``` + +### Configure additional storage buckets + +Amplify Storage gives you the flexibility to configure your backend to automatically provision and manage multiple storage resources. + +You can define additional storage buckets by using the same `defineStorage` function and providing a unique, descriptive `name` to identify the storage bucket. You can pass this `name` to the storage APIs to specify the bucket you want to perform the action to. Ensure that this `name` attribute is unique across the defined storage buckets in order to reliably identify the correct bucket and prevent conflicts. + +It's important to note that if additional storage buckets are defined one of them must be marked as default with the `isDefault` flag. + +```ts title="amplify/storage/resource.ts" +export const firstBucket = defineStorage({ + name: 'firstBucket', + isDefault: true, // identify your default storage bucket (required) +}); + +export const secondBucket = defineStorage({ + name: 'secondBucket', + access: (allow) => ({ + 'private/{entity_id}/*': [ + allow.entity('identity').to(['read', 'write', 'delete']) + ] + }) +}) +``` + +Add additional storage resources to the backend definition. + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +import { firstBucket, secondBucket } from './storage/resource'; + +defineBackend({ + auth, + firstBucket, + // highlight-next-line + secondBucket +}); +``` + + +### Storage bucket client usage + +Additional storage buckets can be referenced from application code by passing the `bucket` option to Amplify Storage APIs. You can provide a target bucket's name assigned in Amplify Backend. + +```ts +import { downloadData } from 'aws-amplify/storage'; + +try { + const result = downloadData({ + path: "album/2024/1.jpg", + options: { + // highlight-start + // Specify a target bucket using name assigned in Amplify Backend + bucket: "secondBucket" + // highlight-end + } + }).result; +} catch (error) { + console.log(`Error: ${error}`) +} +``` +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. See each Amplify Storage API page for additional usage examples. + +```ts +import { downloadData } from 'aws-amplify/storage'; + +try { + const result = downloadData({ + path: 'album/2024/1.jpg', + options: { + // highlight-start + // Alternatively, provide bucket name from console and associated region + bucket: { + bucketName: 'second-bucket-name-from-console', + region: 'us-east-2' + } + // highlight-end + } + }).result; +} catch (error) { + console.log(`Error: ${error}`); +} + +``` + + + +### Storage bucket client usage + +Additional storage buckets can be referenced from application code by passing the `bucket` option to Amplify Storage APIs. You can provide a target bucket's name assigned in Amplify Backend. + +#### [Java] + +```java +StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); +StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); +Amplify.Storage.downloadFile( + StoragePath.fromString("public/example"), + new File(getApplicationContext().getFilesDir() + "/download.txt"), + options, + result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), + error -> Log.e("MyAmplifyApp", "Download Failure", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val secondBucket = StorageBucket.fromOutputs("secondBucket") +val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() +val file = File("${applicationContext.filesDir}/download.txt") +Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, option, + { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, + { Log.e("MyAmplifyApp", "Download Failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val secondBucket = StorageBucket.fromOutputs("secondBucket") +val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() +val file = File("${applicationContext.filesDir}/download.txt") +val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options) +try { + val fileName = download.result().file.name + Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "Download Failure", error) +} +``` + +#### [RxJava] + +```java +StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); +StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); +RxProgressAwareSingleOperation download = + RxAmplify.Storage.downloadFile( + StoragePath.fromString("public/example"), + new File(getApplicationContext().getFilesDir() + "/download.txt"), + options + ); + +download + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), + error -> Log.e("MyAmplifyApp", "Download Failure", error) + ); +``` + +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. See each Amplify Storage API page for additional usage examples. + +#### [Java] + +```java +BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); +StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); +StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); +Amplify.Storage.downloadFile( + StoragePath.fromString("public/example"), + new File(getApplicationContext().getFilesDir() + "/download.txt"), + options, + result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), + error -> Log.e("MyAmplifyApp", "Download Failure", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") +val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) +val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() +val file = File("${applicationContext.filesDir}/download.txt") +Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options, + { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, + { Log.e("MyAmplifyApp", "Download Failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") +val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) +val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() +val file = File("${applicationContext.filesDir}/download.txt") +val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options) +try { + val fileName = download.result().file.name + Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "Download Failure", error) +} +``` + +#### [RxJava] + +```java +BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); +StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); +StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); +RxProgressAwareSingleOperation download = + RxAmplify.Storage.downloadFile( + StoragePath.fromString("public/example"), + new File(getApplicationContext().getFilesDir() + "/download.txt"), + options, + ); + +download + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), + error -> Log.e("MyAmplifyApp", "Download Failure", error) + ); +``` + + + + +### Storage bucket client usage + +Additional storage buckets can be referenced from application code by passing the `bucket` option to Amplify Storage APIs. You can provide a target bucket's name assigned in Amplify Backend. + +```swift +let downloadTask = Amplify.Storage.downloadData( + path: .fromString("public/example/path"), + options: .init( + bucket: .fromOutputs(name: "secondBucket") + ) +) +``` + +Alternatively, you can also directly specify the bucket name and region from the console. See each Amplify Storage API page for additional usage examples. + +```swift +let downloadTask = Amplify.Storage.downloadData( + path: .fromString("public/example/path"), + options: .init( + bucket: .fromBucketInfo(.init( + bucketName: "another-bucket-name", + region: "another-bucket-region") + ) + ) +) +``` + + + +### Storage bucket client usage + +Additional storage buckets can be referenced from application code by passing the `bucket` option to Amplify Storage APIs. You can provide a target bucket's name assigned in Amplify Backend. + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +try { + final result = await Amplify.Storage.downloadData( + path: const StoragePath.fromString('album/2024/1.jpg'), + options: StorageDownloadDataOptions( + // highlight-start + // Specify a target bucket using name assigned in Amplify Backend + bucket: StorageBucket.fromOutputs('secondBucket'), + // highlight-end + ), + ).result; +} on Exception catch (e) { + print('Error: $e'); +} +``` +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. See each Amplify Storage API page for additional usage examples. + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +try { + final result = await Amplify.Storage.downloadData( + path: const StoragePath.fromString('album/2024/1.jpg'), + options: const StorageDownloadDataOptions( + // highlight-start + // Alternatively, provide bucket name from console and associated region + bucket: StorageBucket.fromBucketInfo( + BucketInfo( + bucketName: 'second-bucket-name-from-console', + region: 'us-east-2', + ), + ), + // highlight-end + ), + ).result; +} on Exception catch (e) { + print('Error: $e'); +} +``` + + +## Connect your app code to the storage backend + +The Amplify Storage library provides client APIs that connect to the backend resources you defined. + + +### Configure Amplify in project + +Import and load the configuration file in your app. It's recommended you add the Amplify configuration step to your app's root entry point. For example `index.js` in React or `main.ts` in Angular. + +```javascript +import { Amplify } from 'aws-amplify'; +import outputs from '../amplify_outputs.json'; + +Amplify.configure(outputs); +``` + + +Make sure you call `Amplify.configure` as early as possible in your application’s life-cycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. + + + + + +### Prerequisites + +An application with Amplify libraries integrated and a minimum target of any of the following: +- **iOS 13.0**, using **Xcode 14.1** or later. +- **macOS 10.15**, using **Xcode 14.1** or later. +- **tvOS 13.0**, using **Xcode 14.3** or later. +- **watchOS 9.0**, using **Xcode 14.3** or later. +- **visionOS 1.0**, using **Xcode 15 beta 2** or later. (Preview support - see below for more details.) + +For a full example, please follow the [project setup walkthrough](/[platform]/start/quickstart/). + + + +visionOS support is currently in **preview** and can be used by using the latest [Amplify Release](https://github.com/aws-amplify/amplify-swift/releases). +As new Xcode and visionOS versions are released, the support will be updated with any necessary fixes on a best effort basis. + + + +### Install Amplify library via Swift Package Manager + +1. To install Amplify Libraries in your application, open your project in Xcode and select **File > Add Packages...**. + +2. Enter the **Amplify Library for Swift** GitHub repo URL (`https://github.com/aws-amplify/amplify-swift`) into the search bar and click **Add Package**. + + + + **Note:** **Up to Next Major Version** should be selected from the **Dependency Rule** dropdown. + + + +3. Lastly, choose **AWSS3StoragePlugin**, **AWSCognitoAuthPlugin**, and **Amplify**. Then click **Add Package**. + +### Configure Amplify in project + +Initialize the Amplify Storage category by calling `Amplify.add(plugin:)`. To complete initialization call `Amplify.configure()`. + +#### [SwiftUI] + +Add the following imports to the top of your `App` scene and configure Amplify in the `init`: +```swift +import Amplify +import AWSCognitoAuthPlugin +import AWSS3StoragePlugin + +@main +struct MyAmplifyApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } + + init() { + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.add(plugin: AWSS3StoragePlugin()) + try Amplify.configure(with: .amplifyOutputs) + print("Amplify configured with Auth and Storage plugins") + } catch { + print("Failed to initialize Amplify with \(error)") + } + } +} +``` + +#### [UIKit] + +Add the following imports to the top of your `AppDelegate.swift` file: + +```swift +import Amplify +import AWSCognitoAuthPlugin +import AWSS3StoragePlugin +``` + +Add the following code to the `application:didFinishLaunchingWithOptions` method: + +```swift +func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? +) -> Bool { + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.add(plugin: AWSS3StoragePlugin()) + try Amplify.configure(with: .amplifyOutputs) + print("Amplify configured with Auth and Storage plugins") + } catch { + print("Failed to initialize Amplify with \(error)") + } + + return true +} +``` + +Upon building and running this application you should see the following in your console window: + +```console +Amplify configured with Auth and Storage plugins +``` + + + +### Prerequisites + +* An Android application targeting Android API level 24 (Android 7.0) or above + * For a full example of creating Android project, please follow the [quickstart guide](/[platform]/start/quickstart/) + +### Install the Amplify library + +Expand **Gradle Scripts**, open **build.gradle (Module: app)**. You will already have configured Amplify by following the steps in the [quickstart guide](/[platform]/start/quickstart/). + +Add these libraries into the `dependencies` block: +```kotlin title="app/build.gradle.kts" +android { + compileOptions { + // Support for modern Java features + isCoreLibraryDesugaringEnabled = true + } +} + +dependencies { + // Amplify API dependencies + // highlight-start + implementation("com.amplifyframework:aws-storage-s3:ANDROID_VERSION") + implementation("com.amplifyframework:aws-auth-cognito:ANDROID_VERSION") + // highlight-end + // ... other dependencies + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:ANDROID_DESUGAR_VERSION") +} +``` + +`aws-auth-cognito` is used to provide authentication for Amazon S3. + +Click **Sync Now**. + +### Configure Amplify in your project + +Initialize Amplify Storage by calling `Amplify.addPlugin()`. To complete initialization, call `Amplify.configure()`. + +Add the following code to your `onCreate()` method in your application class: + +> **Warning:** Before calling the `Amplify.configure` function, make sure to either download the `amplify_outputs.json` file from the console, or generate it with the following command: +> +> ```bash title="Terminal" showLineNumbers={false} +npx ampx generate outputs --app-id --branch main --out-dir app/src/main/res/raw +``` +> +> Next, be sure the file you generated or downloaded is in the appropriate resource directory for your application (for example, `app/src/main/res/raw`) in your Android project. Otherwise, you will not be able to compile your application. + +#### [Java] + +```java +import android.util.Log; +import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin; +import com.amplifyframework.core.Amplify; +import com.amplifyframework.core.configuration.AmplifyOutputs; +import com.amplifyframework.storage.s3.AWSS3StoragePlugin; +``` + +```java +Amplify.addPlugin(new AWSCognitoAuthPlugin()); +Amplify.addPlugin(new AWSS3StoragePlugin()); +``` + +Your class will look like this: + +```java +public class MyAmplifyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + try { + // Add these lines to add the AWSCognitoAuthPlugin and AWSS3StoragePlugin plugins + Amplify.addPlugin(new AWSCognitoAuthPlugin()); + Amplify.addPlugin(new AWSS3StoragePlugin()); + Amplify.configure(AmplifyOutputs.fromResource(R.raw.amplify_outputs), getApplicationContext()); + + Log.i("MyAmplifyApp", "Initialized Amplify"); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error); + } + } +} +``` + +#### [Kotlin] + +```kotlin +import android.util.Log +import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin +import com.amplifyframework.core.Amplify +import com.amplifyframework.core.configuration.AmplifyOutputs +import com.amplifyframework.storage.s3.AWSS3StoragePlugin +``` + +```kotlin +Amplify.addPlugin(AWSCognitoAuthPlugin()) +Amplify.addPlugin(AWSS3StoragePlugin()) +``` + +Your class will look like this: + +```kotlin +class MyAmplifyApp : Application() { + override fun onCreate() { + super.onCreate() + + try { + // Add these lines to add the AWSCognitoAuthPlugin and AWSS3StoragePlugin plugins + Amplify.addPlugin(AWSCognitoAuthPlugin()) + Amplify.addPlugin(AWSS3StoragePlugin()) + Amplify.configure(AmplifyOutputs(R.raw.amplify_outputs), applicationContext) + Log.i("MyAmplifyApp", "Initialized Amplify") + } catch (error: AmplifyException) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error) + } + } +} +``` + +#### [RxJava] + +```java +import android.util.Log; +import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin; +import com.amplifyframework.core.configuration.AmplifyOutputs; +import com.amplifyframework.rx.RxAmplify; +import com.amplifyframework.storage.s3.AWSS3StoragePlugin; +``` + +```java +RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); +RxAmplify.addPlugin(new AWSS3StoragePlugin()); +``` + +Your class will look like this: + +```java +public class MyAmplifyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + try { + // Add these lines to add the AWSCognitoAuthPlugin and AWSS3StoragePlugin plugins + RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); + RxAmplify.addPlugin(new AWSS3StoragePlugin()); + RxAmplify.configure(AmplifyOutputs.fromResource(R.raw.amplify_outputs), getApplicationContext()); + + Log.i("MyAmplifyApp", "Initialized Amplify"); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error); + } + } +} +``` + +Note that because the storage category requires auth, you will need to either configure [guest access](/[platform]/build-a-backend/auth/concepts/guest-access/) or [sign in a user](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in) before using features in the storage category. + + + +### Prerequisites + +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Refer to [Flutter's supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) when targeting Web, Windows, or Linux. + +### Install Amplify library + +Add the following dependency to your **app**'s `pubspec.yaml` along with others you added above in **Prerequisites**: + +```yaml +dependencies: + flutter: + sdk: flutter + + amplify_auth_cognito: ^2.0.0 + amplify_flutter: ^2.0.0 + amplify_storage_s3: ^2.0.0 +``` + +### Configure Amplify in project + +To initialize the Amplify Auth and Storage categories, call `Amplify.addPlugin()` for each plugin or pass all the plugins in `Amplify.addPlugins()`. To complete initialization, call `Amplify.configure()`. + +Your code should look like this: + +```dart +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; +import 'package:flutter/material.dart'; + +import 'amplify_outputs.dart'; + +Future _configureAmplify() async { + try { + final auth = AmplifyAuthCognito(); + final storage = AmplifyStorageS3(); + await Amplify.addPlugins([auth, storage]); + + // call Amplify.configure to use the initialized categories in your app + await Amplify.configure(amplifyConfig); + } on Exception catch (e) { + safePrint('An error occurred configuring Amplify: $e'); + } +} + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await _configureAmplify(); + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({Key? key}) : super(key: key); + + // ... +} +``` + + +### Upload your first file + +Next, let's a photo to the `picture-submissions/` path. + + +```jsx +import React from 'react'; +import { uploadData } from 'aws-amplify/storage'; + +function App() { + const [file, setFile] = React.useState(); + + const handleChange = (event) => { + setFile(event.target.files?.[0]); + }; + + const handleClick = () => { + if (!file) { + return; + } + uploadData({ + path: `picture-submissions/${file.name}`, + data: file, + }); + }; + + return ( +
    + + +
    + ); +} +``` + + + +```javascript +import { uploadData } from "aws-amplify/storage"; + +const file = document.getElementById("file"); +const upload = document.getElementById("upload"); + +upload.addEventListener("click", () => { + const fileReader = new FileReader(); + fileReader.readAsArrayBuffer(file.files[0]); + + fileReader.onload = async (event) => { + console.log("Complete File read successfully!", event.target.result); + try { + await uploadData({ + data: event.target.result, + path: `picture-submissions/${file.files[0].name}` + }); + } catch (e) { + console.log("error", e); + } + }; +}); +``` + + + +```swift +import Amplify +import SwiftUI +import PhotosUI + +struct ContentView: View { + @State private var selectedPhoto: PhotosPickerItem? + @State private var image: Image? + + var body: some View { + NavigationStack { + VStack { + image? + .resizable() + .scaledToFit() + } + .padding() + PhotosPicker( + selection: $selectedPhoto + ) { + Text("Select a photo to upload") + } + .task(id: selectedPhoto) { + if let imageData = try? await selectedPhoto?.loadTransferable(type: Data.self) { + if let uiImage = UIImage(data: imageData) { + image = Image(uiImage: uiImage) + } + let uploadTask = Amplify.Storage.uploadData( + path: .fromString("picture-submissions/myPhoto.png"), + data: imageData + ) + } + } + } + } +} +``` + + + + +#### [Java] + +```java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "myPhoto.png"); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + Amplify.Storage.uploadFile( + StoragePath.fromString("picture-submissions/myPhoto.png"), + exampleFile, + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) + ); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +private fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "myPhoto.png") + exampleFile.writeText("Example file contents") + + Amplify.Storage.uploadFile(StoragePath.fromString("picture-submissions/myPhoto.png"), exampleFile, + { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, + { Log.e("MyAmplifyApp", "Upload failed", it) } + ) +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +private suspend fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "myPhoto.png") + exampleFile.writeText("Example file contents") + + val upload = Amplify.Storage.uploadFile(StoragePath.fromString("picture-submissions/myPhoto.png"), exampleFile) + try { + val result = upload.result() + Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") + } catch (error: StorageException) { + Log.e("MyAmplifyApp", "Upload failed", error) + } +} +``` + +#### [RxJava] + +```java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "myPhoto.png"); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + RxProgressAwareSingleOperation rxUploadOperation = + RxAmplify.Storage.uploadFile(StoragePath.fromString("picture-submissions/myPhoto.png"), exampleFile); + + rxUploadOperation + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Upload failed", error) + ); +} +``` + + + + + + +**Note**: To use `AWSFilePlatform`, add [aws_common](https://pub.dev/packages/aws_common) package to your Flutter project +by running: `flutter pub add aws_common` + + + +#### [All Platforms] + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future uploadFile() async { + try { + final result = await Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath('/path/to/local/myPhoto.png'), + path: const StoragePath.fromString('picture-submissions/myPhoto.png'), + ).result; + safePrint('Uploaded file: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + + + +```dart +import 'dart:io' show File; + +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:aws_common/vm.dart'; + +Future uploadFile(File file) async { + try { + final result = await Amplify.Storage.uploadFile( + localFile: AWSFilePlatform.fromFile(file), + path: const StoragePath.fromString('picture-submissions/myPhoto.png'), + ).result; + safePrint('Uploaded file: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + +#### [Web] + +```dart +import 'dart:html' show File; + +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:aws_common/web.dart'; + +Future uploadFile(File file) async { + final awsFile = AWSFilePlatform.fromFile(file); + try { + final result = await Amplify.Storage.uploadFile( + localFile: awsFile, + path: const StoragePath.fromString('picture-submissions/myPhoto.png'), + ).result; + safePrint('Uploaded file: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + + + +## Manage files in Amplify console + +After successfully publishing your storage backend and connecting your project with client APIs, you can manage files and folders in [the Amplify console](https://console.aws.amazon.com/amplify). You can perform on-demand actions like upload, download, copy, and more under the Storage tab in the console. Refer to [Manage files in Amplify Console](/[platform]/build-a-backend/storage/manage-with-amplify-console/) guide for additional information. + +## Conclusion + +Congratulations! You finished the Set up Amplify Storage guide. In this guide, you set up and connected to backend resources, customized your file paths and access definitions, and connected your application to the backend to implement features like file uploads and downloads. + +### Next steps + +Now that you have completed setting up storage in your Amplify app, you can proceed to add file management features to your app. You can use the following guides to implement upload and download functionality, or you can access more capabilities from the side navigation. + +- [Upload Files](/[platform]/build-a-backend/storage/upload-files/) +- [Download Files](/[platform]/build-a-backend/storage/download-files/) + +--- + +--- +title: "Customize authorization rules" +section: "build-a-backend/storage" +platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] +gen: 2 +last-updated: "2024-09-19T18:46:56.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/authorization/" +--- + +Customize authorization for your storage bucket by defining access to file paths for guests, authenticated users, and user groups. Access can also be defined for functions that require access to the storage bucket. + +Refer to the following examples to understand how you can further customize authorization against different user types. + +## Access Types + +Authentication is required to continue using Amplify Storage, please make sure you set it up if you haven't already - [documentation to set up Auth](/[platform]/build-a-backend/auth/set-up-auth/). + + + +**Note:** Paths in access definitions cannot have a '/' at the beginning of the string. + +By default, all paths are denied to all types of users unless explicitly granted within `defineStorage` using the `access` callback as shown below. + + + +#### [Guest Users] +To grant all guest (i.e. not signed in) users of your application read access to files under `media/`, use the following `access` values. + +```ts title="amplify/storage/resource.ts" +export const storage = defineStorage({ + name: 'myProjectFiles', + access: (allow) => ({ + 'media/*': [ + allow.guest.to(['read']) // additional actions such as "write" and "delete" can be specified depending on your use case + ] + }) +}); +``` + +#### [Authenticated Users] + + +**Note:** When a user is part of a group, they are assigned the group role, which means permissions defined for the authenticated role will not apply for this user. + +To grant access to users within a group, you must explicitly define access permissions for the group against the desired prefix. + + + +To grant all authenticated (i.e. signed in) users of your application read access to files under `media/`, use the following `access` configuration. + +```ts title="amplify/storage/resource.ts" +export const storage = defineStorage({ + name: 'myProjectFiles', + access: (allow) => ({ + 'media/*': [ + allow.authenticated.to(['read']) // additional actions such as "write" and "delete" can be specified depending on your use case + ] + }) +}); +``` + +#### [User Groups] + + + +**Note:** When a user is part of a group, they are assigned the group role, which means permissions defined for the authenticated role will not apply for this user. + +To grant access to users within a group, you must explicitly define access permissions for the group against the desired prefix. + + + +If you have configured user groups when setting up auth in your `defineAuth` object, you can scope storage access to specific groups. In this example, assume you have a `defineAuth` config with `admin` and `auditor` groups. + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from '@aws-amplify/backend'; + +export const auth = defineAuth({ + loginWith: { + email: true + }, + groups: ['admin', 'auditor'] +}); +``` + +With the following `access` definition, you can configure permissions such that auditors have read only permissions to `media/*` while admin has full permissions. + +```ts title="amplify/storage/resource.ts" +export const storage = defineStorage({ + name: 'myProjectFiles', + access: (allow) => ({ + 'media/*': [ + allow.groups(['auditor']).to(['read']), + allow.groups(['admin']).to(['read', 'write', 'delete']) + ] + }) +}); +``` + +If multiple groups require the same set of actions, this can be combined into a single rule. + +```ts title="amplify/storage/resource.ts" +export const storage = defineStorage({ + name: 'myProjectFiles', + access: (allow) => ({ + 'media/*': [ + allow.groups(['auditor', 'admin']).to(['read', 'write']) + ] + }) +}); +``` + +#### [Owners] +In some use cases, you may want just the uploader of a file to be able to perform actions on it. For example, in a music sharing app anyone can listen to a song, but only the person who uploaded that song could delete it. + +In Amplify Storage, you can do this by using the `entity_id` to represent the user which scopes files to individual users. + +The `entity_id` is a reserved token that will be replaced with the users' identifier when the file is being uploaded. You can specify the method of identification when defining access to the path like `allow.entity().to([..])`. + +Currently, Identity Pool is the only identification method available - `allow.entity('identity').to([..])` + +The following policy would allow authenticated users full access to `media/` that matches their identity id. + +```ts title="amplify/storage/resource.ts" +export const storage = defineStorage({ + name: 'myProjectFiles', + access: (allow) => ({ + 'media/{entity_id}/*': [ + // {entity_id} is the token that is replaced with the user identity id + allow.entity('identity').to(['read', 'write', 'delete']) + ] + }) +}); +``` + +A user with identity id `user123` would be able to perform read/write/delete operations on files within `media/user123/*` but would not be able to perform actions on files with any other path. + +Likewise, a user with identity ID `userABC` would be able to perform read/write/delete operation on files only within `media/userABC/*`. In this way, each user can be granted access to a storage path that is not accessible to any other user. + +The following example shows how you can define access to profile pictures where anyone can view them but only the owner can modify/delete them. + +```ts title="amplify/storage/resource.ts" +export const storage = defineStorage({ + name: 'myProjectFiles', + access: (allow) => ({ + 'media/profile-pictures/{entity_id}/*': [ + allow.entity('identity').to(['read', 'write', 'delete']), + allow.guest.to(['read']), + allow.authenticated.to(['read']) + ] + }) +}); +``` + +When a rule for guests, authenticated users, user groups, or resources is applied to a path with the `{entity_id}` token, the token is replaced with a wildcard (`*`). This means that the access will apply to files uploaded by _any_ user. In the above policy, write and delete is scoped to just the owner, but read is allowed for guest and authenticated users for any file within `media/profile-pictures/*/*`. + +#### [Functions] +In addition to granting application users access to storage files, you may also want to grant a backend function access to storage files. This could be used to enable a use case like resizing images or automatically deleting old files. The following configuration is used to define function access. + +```ts title="amplify/storage/resource.ts" +import { defineStorage, defineFunction } from '@aws-amplify/backend'; + +const demoFunction = defineFunction({}); + +export const storage = defineStorage({ + name: 'myProjectFiles', + access: (allow) => ({ + 'media/*': [allow.resource(demoFunction).to(['read', 'write', 'delete'])] + }) +}); +``` + +This would grant the function `demoFunction` the ability to read write and delete files within `media/*`. + +When a function is granted access to storage, it also receives an environment variable that contains the name of the Amazon S3 bucket configured by storage. This environment variable can be used in the function to make AWS SDK calls to the storage bucket. The environment variable is named `_BUCKET_NAME`. In the above example, it would be named `myProjectFiles_BUCKET_NAME`. + +[Learn more about function resource access environment variables](/[platform]/build-a-backend/functions/#resource-access) + +### Access definition rules + +There are some rules for the types of paths that can be specified at the same time in the storage access definition. + +1. All paths must end with `/*`. +2. Only one level of nesting is allowed. For example, you can define access controls on `media/*` and `media/albums/*` but not on `media/albums/photos/*` because there are two other definitions along the same path. +3. Wildcards cannot conflict with the `{entity_id}` token. For example, you cannot have both `media/*` and `media/{entity_id}/*` defined because the wildcard in the first path conflicts with the `{entity_id}` token in the second path. +4. A path cannot be a prefix of another path with an `{entity_id}` token. For example `media/*` and `media/albums/{entity_id}/*` is not allowed. + +When one path is a subpath of another, the permissions on the subpath _always override_ the permissions from the parent path. Permissions are not "inherited" from a parent path. Consider the following access definition example: + +```ts +export const storage = defineStorage({ + name: 'myProjectFiles', + access: (allow) => ({ + 'media/*': [allow.authenticated.to(['read', 'write', 'delete'])], + 'media/profile-pictures/*': [allow.guest.to(['read'])], + 'media/albums/*': [allow.authenticated.to(['read'])], + 'other/*': [ + allow.guest.to(['read']), + allow.authenticated.to(['read', 'write']) + ] + }) +}); +``` + +The access control matrix for this configuration is + +| Path | media/\* | media/profile-pictures/\* | media/albums/\* | other/\* | +| --- | --- | --- | --- | --- | +| **Authenticated Users** | read, write, delete | NONE | read | read, write | +| **Guest users** | NONE | read | NONE | read | + +Authenticated users have access to read, write, and delete everything under `media/*` EXCEPT `media/profile-pictures/*` and `media/albums/*`. For those subpaths, the scoped down access overrides the access granted on the parent `media/*` + +### Available actions + +When you configure access to a particular path, you can scope the access to one or more CRUDL actions. + +| Access | Corresponding Library APIs | +| -------- | ----------------------------------------------------- | +| `read` | `getUrl`, `downloadData`, `list`, and `getProperties` | +| `get` | `getUrl` and `downloadData` | +| `list` | `list`, and `getProperties` | +| `write` | `uploadData`, `copy` | +| `delete` | `remove` | + + + +**Note:** `read` is a combination of `get` and `list` access definitions and hence cannot be defined in the presence of `get` or `list`. + + + +## For Gen 1 public, protected, and private access pattern + +To configure `defineStorage` in Amplify Gen 2 to behave the same way as the storage category in Gen 1, the following definition can be used. + +```ts title="amplify/storage/resource.ts" +export const storage = defineStorage({ + name: 'myProjectFiles', + access: (allow) => ({ + 'public/*': [ + allow.guest.to(['read']), + allow.authenticated.to(['read', 'write', 'delete']), + ], + 'protected/{entity_id}/*': [ + allow.authenticated.to(['read']), + allow.entity('identity').to(['read', 'write', 'delete']) + ], + 'private/{entity_id}/*': [ + allow.entity('identity').to(['read', 'write', 'delete']) + ] + }) +}); +``` + +--- + +--- +title: "Upload files" +section: "build-a-backend/storage" +platforms: ["angular", "javascript", "nextjs", "react", "vue", "android", "swift", "flutter", "react-native"] +gen: 2 +last-updated: "2025-01-30T19:37:25.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/upload-files/" +--- + + +You can implement upload functionality in your app by either using the File Uploader UI component or further customizing the upload experience using the upload API. + +## File Uploader React UI Component + +Upload files from your app in minutes by using the cloud-connected File Uploader UI Component. + +```bash title="Terminal" showLineNumbers={false} +npm add @aws-amplify/ui-react-storage aws-amplify +``` +Then, use the component in your app. + +```tsx +import { FileUploader } from '@aws-amplify/ui-react-storage'; +import '@aws-amplify/ui-react/styles.css'; + +export const DefaultFileUploaderExample = () => { + return ( + + ); +}; +``` + +![Showing File Uploader UI component](/images/gen2/storage/upload-ui-component.png) + +Learn more about how you can further customize the UI component by referring to the [File Uploader documentation](https://ui.docs.amplify.aws/react/connected-components/storage/fileuploader). + + +## Implement upload functionality + + + +**Note:** Refer to [the Transfer Acceleration documentation](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) to learn how to enable transfer acceleration for storage APIs. + + + + + +### Upload from file + +The following is an example of how you would upload a file from a file object, this could be retrieved from the local machine or a different source. + + +```jsx +import React from 'react'; +import { uploadData } from 'aws-amplify/storage'; + +function App() { + const [file, setFile] = React.useState(); + + const handleChange = (event) => { + setFile(event.target.files?.[0]); + }; + + const handleClick = () => { + if (!file) { + return; + } + uploadData({ + path: `photos/${file.name}`, + data: file, + }); + }; + + return ( +
    + + +
    + ); +} +``` + + + +```javascript +import { uploadData } from "aws-amplify/storage"; + +const file = document.getElementById("file"); +const upload = document.getElementById("upload"); + +upload.addEventListener("click", () => { + const fileReader = new FileReader(); + fileReader.readAsArrayBuffer(file.files[0]); + + fileReader.onload = async (event) => { + console.log("Complete File read successfully!", event.target.result); + try { + await uploadData({ + data: event.target.result, + path: file.files[0].name + }); + } catch (e) { + console.log("error", e); + } + }; +}); +``` + + +### Upload from data + +You can follow this example if you have data saved in memory and would like to upload this data to the cloud. + +```javascript +import { uploadData } from 'aws-amplify/storage'; + +try { + const result = await uploadData({ + path: "album/2024/1.jpg", + // Alternatively, path: ({identityId}) => `album/${identityId}/1.jpg` + data: file, + }).result; + console.log('Succeeded: ', result); +} catch (error) { + console.log('Error : ', error); +} +``` + + + +### Upload from file + +#### [Java] + +```java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + Amplify.Storage.uploadFile( + StoragePath.fromString("public/example"), + exampleFile, + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) + ); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +private fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "example") + exampleFile.writeText("Example file contents") + + Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, + { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, + { Log.e("MyAmplifyApp", "Upload failed", it) } + ) +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +private suspend fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "example") + exampleFile.writeText("Example file contents") + + val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile) + try { + val result = upload.result() + Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") + } catch (error: StorageException) { + Log.e("MyAmplifyApp", "Upload failed", error) + } +} +``` + +#### [RxJava] + +```java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + RxProgressAwareSingleOperation rxUploadOperation = + RxAmplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile); + + rxUploadOperation + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Upload failed", error) + ); +} +``` + + + + +### Upload from Input Stream + +#### [Java] + +```java +private void uploadInputStream() { + try { + InputStream exampleInputStream = getContentResolver().openInputStream(uri); + + Amplify.Storage.uploadInputStream( + StoragePath.fromString("public/example"), + exampleInputStream, + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) + ); + } catch (FileNotFoundException error) { + Log.e("MyAmplifyApp", "Could not find file to open for input stream.", error); + } +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +private fun uploadInputStream(uri: Uri) { + val stream = contentResolver.openInputStream(uri) + + Amplify.Storage.uploadInputStream(StoragePath.fromString("public/example"), stream, + { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, + { Log.e("MyAmplifyApp", "Upload failed", it) } + ) +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +private suspend fun uploadInputStream(uri: Uri) { + val stream = contentResolver.openInputStream(uri) + + val upload = Amplify.Storage.uploadInputStream(StoragePath.fromString("public/example"), stream) + try { + val result = upload.result() + Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}.") + } catch (error: StorageException) { + Log.e("MyAmplifyApp", "Upload failed") + } +} +``` + +#### [RxJava] + +```java +private void uploadInputStream() { + try { + InputStream exampleInputStream = getContentResolver().openInputStream(uri); + + RxProgressAwareSingleOperation rxUploadOperation = + RxAmplify.Storage.uploadInputStream(StoragePath.fromString("public/example"), exampleInputStream); + + rxUploadOperation + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Upload failed", error) + ); + } catch (FileNotFoundException error) { + Log.e("MyAmplifyApp", "Could not find file to open for input stream.", error); + } +} +``` + + + + +### Upload from file + +When you have a file that you want to upload, you can specify the url to the file in the `local` parameter. +If a file with the same `path` already exists in S3, the existing S3 file will be overwritten. + +```swift +let dataString = "My Data" +let fileName = "myFile.txt" +guard let fileUrl = FileManager.default.urls( + for: .documentDirectory, + in: .userDomainMask +).first?.appendingPathComponent(fileName) +else { return } + +try dataString.write( + to: fileUrl, + atomically: true, + encoding: .utf8 +) + +let uploadTask = Amplify.Storage.uploadFile( + path: .fromString("public/example/path/myFile.txt"), + local: fileUrl +) + +``` + +### Upload from data + +To upload a file from a data object, specify the `path` and the `data` object to be uploaded. + +```swift +let dataString = "My Data" +let data = Data(dataString.utf8) +let uploadTask = Amplify.Storage.uploadData( + path: .fromString("public/example/path/myFile.txt"), + data: data +) +``` + + + +### Upload from file + + + +**Note**: To use `AWSFilePlatform`, add [aws_common](https://pub.dev/packages/aws_common) package to your Flutter project +by running: `flutter pub add aws_common` + + + +#### [All Platforms] + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future uploadFile() async { + try { + final result = await Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath('/path/to/local/file.txt'), + path: const StoragePath.fromString('public/file.txt'), + ).result; + safePrint('Uploaded file: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + + + +```dart +import 'dart:io' show File; + +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:aws_common/vm.dart'; + +Future uploadFile(File file) async { + try { + final result = await Amplify.Storage.uploadFile( + localFile: AWSFilePlatform.fromFile(file), + path: const StoragePath.fromString('public/file.png'), + ).result; + safePrint('Uploaded file: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + +#### [Web] + +```dart +import 'dart:html' show File; + +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:aws_common/web.dart'; + +Future uploadFile(File file) async { + final awsFile = AWSFilePlatform.fromFile(file); + try { + final result = await Amplify.Storage.uploadFile( + localFile: awsFile, + path: const StoragePath.fromString('public/file.png'), + ).result; + safePrint('Uploaded file: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + +### Upload from Flutter's `file_picker` plugin + +The [file_picker](https://pub.dev/packages/file_picker) plugin can be used to retrieve arbitrary file types from the user's device. + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:file_picker/file_picker.dart'; + +Future uploadImage() async { + // Select a file from the device + final result = await FilePicker.platform.pickFiles( + type: FileType.custom, + withData: false, + // Ensure to get file stream for better performance + withReadStream: true, + allowedExtensions: ['jpg', 'png', 'gif'], + ); + + if (result == null) { + safePrint('No file selected'); + return; + } + + // Upload file using the filename + final platformFile = result.files.single; + try { + final result = await Amplify.Storage.uploadFile( + localFile: AWSFile.fromStream( + platformFile.readStream!, + size: platformFile.size, + ), + path: StoragePath.fromString('public/${platformFile.name}'), + onProgress: (progress) { + safePrint('Fraction completed: ${progress.fractionCompleted}'); + }, + ).result; + safePrint('Successfully uploaded file: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + +### Upload from data + +To upload from a data object, specify the `path` and `data`, where `data` is an instance of `S3DataPayload` created from various data formats. + +#### [String] + +```dart +Future uploadData() async { + try { + final result = await Amplify.Storage.uploadData( + data: StorageDataPayload.string( + 'hello world', + contentType: 'text/plain', + ), + path: const StoragePath.fromString('public/example.txt'), + ).result; + safePrint('Uploaded data: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + +#### [JSON Object] + +```dart +Future uploadData() async { + try { + final result = await Amplify.Storage.uploadData( + data: StorageDataPayload.json({ + 'title': 'example', + 'author': { + 'firstName': 'Jane', + 'lastName': 'Doe', + }, + }), + path: const StoragePath.fromString('public/example.json'), + ).result; + safePrint('Uploaded data: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + +#### [Data URL] + +See more info about [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs). + +```dart +Future uploadData() async { + // dataUrl should be a valid Data Url. + // see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs + const dataUrl = 'data:text/plain;charset=utf-8;base64,aGVsbG8gd29ybGQ='; + try { + final result = await Amplify.Storage.uploadData( + data: StorageDataPayload.dataUrl(dataUrl), + path: const StoragePath.fromString('public/example.txt'), + ).result; + safePrint('Uploaded data: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + +#### [Bytes] + +```dart +Future uploadBytes() async { + try { + final bytes = 'hello world'.codeUnits; + final result = await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes( + bytes, + contentType: 'text/plain', + ), + path: const StoragePath.fromString('public/example.txt'), + ).result; + safePrint('Uploaded data: ${result.uploadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + + + + +### Monitor upload progress + +```dart +final operation = Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath('/path/to/local/file'), + path: const StoragePath.fromString('public/example.txt'), + onProgress: (progress) { + safePrint('fraction totalBytes: ${progress.totalBytes}'); + safePrint('fraction transferredBytes: ${progress.transferredBytes}'); + safePrint('fraction completed: ${progress.fractionCompleted}'); + } +); +``` + + + +### Pause, resume, and cancel uploads + +A call to `Amplify.Storage.uploadFile` or `Amplify.Storage.uploadData` returns a reference to the operation that is performing the upload. + +```dart +Future upload() async { + final operation = Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath('/path/to/local/file'), + path: const StoragePath.fromString('public/example.txt'), + ); + + // pause operation + await operation.pause(); + + // resume operation + await operation.resume(); + + // cancel operation + await operation.cancel(); +} +``` + + + +### Upload to a specified bucket + +You can also perform an `upload` operation to a specific bucket by providing the `bucket` option. You can pass in a `StorageBucket` object representing the target bucket from the name defined in the Amplify Backend. + +```dart +final data = 'multi bucket upload data byte'.codeUnits; +final result = await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes(data), + path: const StoragePath.fromString('path/to/file.txt'), + options: StorageUploadDataOptions( + // highlight-start + // Specify a target bucket using name assigned in Amplify Backend + bucket: StorageBucket.fromOutputs('secondBucket'), + // highlight-end + ), +).result; +``` +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. + +```dart +final data = 'multi bucket upload data byte'.codeUnits; +final result = await Amplify.Storage.uploadData( + data: StorageDataPayload.bytes(data), + path: const StoragePath.fromString('path/to/file.txt'), + options: StorageUploadDataOptions( + // highlight-start + // Alternatively, provide bucket name from console and associated region + bucket: StorageBucket.fromBucketInfo( + BucketInfo( + bucketName: 'second-bucket-name-from-console', + region: 'us-east-2', + ), + ), + // highlight-end + ), +).result; +``` + + + +### More upload options + +Option | Type | Description | +| -- | -- | ----------- | +| bucket | StorageBucket | The target bucket from the assigned name in the Amplify Backend or from the bucket name and region in the console

    Defaults to default bucket and region from the Amplify configuration if this option is not provided.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | +| getProperties | boolean | Whether to retrieve properties for the uploaded object using theAmplify.Storage.getProperties() after the operation completes. When set to true the returned item will contain additional info such as metadata and content type. | +| useAccelerateEndpoint | boolean | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/upload-files/#transfer-acceleration) | + +Example of `uploadFile` with options: + +```dart +final operation = Amplify.Storage.uploadFile( + localFile: AWSFile.fromPath('/path/to/local/file'), + path: const StoragePath.fromString('public/example.txt'), + options: const StorageUploadFileOptions( + metadata: {'key': 'value'}, + pluginOptions: S3UploadFilePluginOptions( + getProperties: true, + useAccelerateEndpoint: true, + ), + ), +); +``` + +Example of `uploadData` with options: + +```dart +final operation = Amplify.Storage.uploadData( + data: StorageDataPayload.string('example'), + path: const StoragePath.fromString('public/example.txt'), + options: const StorageUploadDataOptions( + metadata: {'key': 'value'}, + pluginOptions: S3UploadDataPluginOptions( + getProperties: true, + useAccelerateEndpoint: true, + ), + ), +); +``` + + +### Upload to a specified bucket + +You can also perform an upload operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. + +```ts +import { uploadData } from 'aws-amplify/storage'; + +const result = await uploadData({ + path: 'album/2024/1.jpg', + data: file, + options: { + // highlight-start + // Specify a target bucket using name assigned in Amplify Backend + bucket: 'assignedNameInAmplifyBackend' + // highlight-end + } +}).result; +``` +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. + +```ts +import { uploadData } from 'aws-amplify/storage'; + +const result = await uploadData({ + path: 'album/2024/1.jpg', + data: file, + options: { + // highlight-start + // Alternatively, provide bucket name from console and associated region + bucket: { + bucketName: 'bucket-name-from-console', + region: 'us-east-2' + } + // highlight-end + } +}).result; + +``` + + + +### Upload to a specified bucket + +You can also perform an upload operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. + +#### [Java] + +```java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); + StorageUploadFileOptions options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); + + Amplify.Storage.uploadFile( + StoragePath.fromString("public/example"), + exampleFile, + options, + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) + ); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +private fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "example") + exampleFile.writeText("Example file contents") + + val secondBucket = StorageBucket.fromOutputs("secondBucket") + val options = StorageUploadFileOptions.builder().bucket(secondBucket).build() + + Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options, + { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, + { Log.e("MyAmplifyApp", "Upload failed", it) } + ) +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +private suspend fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "example") + exampleFile.writeText("Example file contents") + + val secondBucket = StorageBucket.fromOutputs("secondBucket") + val options = StorageUploadFileOptions.builder().bucket(secondBucket).build() + + val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options) + try { + val result = upload.result() + Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") + } catch (error: StorageException) { + Log.e("MyAmplifyApp", "Upload failed", error) + } +} +``` + +#### [RxJava] + +```java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); + StorageUploadFileOptions options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); + + RxProgressAwareSingleOperation rxUploadOperation = + RxAmplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options); + + rxUploadOperation + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Upload failed", error) + ); +} +``` + +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. + +#### [Java] + +```java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); + StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); + StorageUploadFileOptions options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); + + Amplify.Storage.uploadFile( + StoragePath.fromString("public/example"), + exampleFile, + options, + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) + ); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +private fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "example") + exampleFile.writeText("Example file contents") + + val bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); + val secondBucket = StorageBucket.fromBucketInfo(bucketInfo); + val options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); + + Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options, + { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, + { Log.e("MyAmplifyApp", "Upload failed", it) } + ) +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +private suspend fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "example") + exampleFile.writeText("Example file contents") + + val bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); + val secondBucket = StorageBucket.fromBucketInfo(bucketInfo); + val options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); + + val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options) + try { + val result = upload.result() + Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") + } catch (error: StorageException) { + Log.e("MyAmplifyApp", "Upload failed", error) + } +} +``` + +#### [RxJava] + +```java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); + StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); + StorageUploadFileOptions options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); + + RxProgressAwareSingleOperation rxUploadOperation = + RxAmplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options); + + rxUploadOperation + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Upload failed", error) + ); +} +``` + + + + +### Upload to a specified bucket + +You can perform an upload operation to a specific bucket by providing the `bucket` option. + +#### [From Outputs] +You can use `.fromOutputs(name:)` to provide a string representing the target bucket's assigned name in the Amplify Backend. + +```swift +// Upload from File +let uploadTask = Amplify.Storage.uploadFile( + path: .fromString("public/example/path/myFile.txt"), + local: fileUrl, + options: .init( + bucket: .fromOutputs(name: "secondBucket") + ) +) + +// Upload from Data +let uploadTask = Amplify.Storage.uploadData( + path: .fromString("public/example/path/myFile.txt"), + data: data, + options: .init( + bucket: .fromOutputs(name: "secondBucket") + ) +) +``` + +#### [From Bucket Info] +You can also use `.fromBucketInfo(_:)` to provide a bucket name and region directly. + +```swift +// Upload from File +let uploadTask = Amplify.Storage.uploadFile( + path: .fromString("public/example/path/myFile.txt"), + local: fileUrl, + options: .init( + bucket: .fromBucketInfo(.init( + bucketName: "another-bucket-name", + region: "another-bucket-region") + ) + ) +) + +// Upload from Data +let uploadTask = Amplify.Storage.uploadData( + path: .fromString("public/example/path/myFile.txt"), + data: data, + options: .init( + bucket: .fromBucketInfo(.init( + bucketName: "another-bucket-name", + region: "another-bucket-region") + ) + ) +) +``` + + + + +### Monitor upload progress + +Monitor progress of upload by using the `onProgress` option. + +```javascript +import { uploadData } from 'aws-amplify/storage'; + +const monitorUpload = async () => { + try { + const result = await uploadData({ + path: "album/2024/1.jpg", + // Alternatively, path: ({identityId}) => `album/${identityId}/1.jpg` + data: file, + options: { + onProgress: ({ transferredBytes, totalBytes }) => { + if (totalBytes) { + console.log( + `Upload progress ${Math.round( + (transferredBytes / totalBytes) * 100 + )} %` + ); + } + }, + }, + }).result; + console.log("Path from Response: ", result.path); + } catch (error) { + console.log("Error : ", error); + } +} +``` + + + +### Monitor upload progress + +To track progress of the upload, use the reference returned by the `uploadFile` or `uploadData` as shown below. + +#### [Async/Await] +```swift +Task { + for await progress in await uploadTask.progress { + print("Progress: \(progress)") + } +} + +let value = try await uploadTask.value +print("Completed: \(value)") +``` + +#### [Combine] + +```swift +let progressSink = uploadTask + .inProcessPublisher + .sink { progress in + print("Progress: \(progress)") + } + +let resultSink = uploadTask + .resultPublisher + .sink { + if case let .failure(storageError) = $0 { + print("Failed: \(storageError.errorDescription). \(storageError.recoverySuggestion)") + } + } + receiveValue: { data in + print("Completed: \(data)") + } +``` + + + + +### Monitor upload progress + +To track progress of the upload, use the `uploadFile` API that includes a progress listener callback. + +#### [Java] + +```java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + Amplify.Storage.uploadFile( + StoragePath.fromString("public/example"), + exampleFile, + StorageUploadFileOptions.defaultInstance(), + progress -> Log.i("MyAmplifyApp", "Fraction completed: " + progress.getFractionCompleted()), + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) + ); +} +``` + +#### [Kotlin - Callbacks] + +```kotlin +private fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "example") + exampleFile.writeText("Example file contents") + + val options = StorageUploadFileOptions.defaultInstance() + Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options, + { Log.i("MyAmplifyApp", "Fraction completed: ${it.fractionCompleted}") }, + { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, + { Log.e("MyAmplifyApp", "Upload failed", it) } + ) +} +``` + +#### [Kotlin - Coroutines] + +```kotlin +private suspend fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "example") + exampleFile.writeText("Example file contents") + + val options = StorageUploadFileOptions.defaultInstance() + val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options) + val progressJob = activityScope.async { + upload.progress().collect { + Log.i("MyAmplifyApp", "Fraction completed: ${it.fractionCompleted}") + } + } + try { + val result = upload.result() + Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") + } catch (error: StorageException) { + Log.e("MyAmplifyApp", "Upload failed", error) + } + progressJob.cancel() +} +``` + +#### [RxJava] + +```java +RxProgressAwareSingleOperation upload = + RxAmplify.Storage.uploadFile("example", exampleFile); + +upload + .observeProgress() + .subscribe( + progress -> Log.i("MyAmplifyApp", progress.getFractionCompleted()) + ); +``` + + + + +### All `upload` options + +Option | Type | Description | +| -- | -- | ----------- | +| metadata | Map\ | Metadata for the object to store. | +| contentType | String | The standard MIME type describing the format of the object to store. | +| bucket | StorageBucket | The bucket in which the object should be stored. | +| serverSideEncryption | ServerSideEncryption | The server side encryption algorithm. | +| useAccelerateEndpoint | boolean | Flag to determine whether to use acceleration endpoint. | + + + +### Query transfers + +When an upload or download operation is requested using the Amplify Android library, the request is first persisted in the local SQLite Database and then queued for execution. You can query the transfer operation queued in the local database using the transfer ID returned by an upload or download API. Get-Transfer API could retrieve a pending transfer previously en-queued and enable attaching a listener to receive updates on progress change, on-error or on-success, or pause, cancel or resume it. + +#### [Java] + +```java +Amplify.Storage.getTransfer("TRANSFER_ID", + operation -> { + Log.i("MyAmplifyApp", "Current State" + operation.getTransferState()); + // set listener to receive updates + operation.setOnProgress( progress -> {}); + operation.setOnSuccess( result -> {}); + operation.setOnError(error -> {}); + + // possible actions + operation.pause(); + operation.resume(); + operation.start(); + operation.cancel(); + }, + { + error -> Log.e("MyAmplifyApp", "Failed to query transfer", error) + } +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Storage.getTransfer("TRANSFER_ID", + { operation -> + Log.i("MyAmplifyApp", "Current State" + operation.transferState) + // set listener to receive updates + operation.setOnProgress { } + operation.setOnSuccess { } + operation.setOnError { } + + // possible actions + operation.pause() + operation.resume() + operation.start() + operation.cancel() + }, + { + Log.e("MyAmplifyApp", "Failed to query transfer", it) + } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val operation = Amplify.Storage.getTransfer("TRANSFER_ID") + Log.i("MyAmplifyApp", "Current State" + operation.transferState) + // set listener to receive updates + operation.setOnProgress { } + operation.setOnSuccess { } + operation.setOnError { } + + // possible actions + operation.pause() + operation.resume() + operation.start() + operation.cancel() +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "Failed to query transfer", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Storage.getTransfer("TRANSFER_ID") + .subscribe( + operation -> { + Log.i("MyAmplifyApp", "Current State" + operation.getTransferState()); + // set listener to receive updates + operation.setOnProgress( progress -> {}); + operation.setOnSuccess( result -> {}); + operation.setOnError(error -> {}); + + // possible actions + operation.pause(); + operation.resume(); + operation.start(); + operation.cancel(); + }, + error -> Log.e("MyAmplifyApp", "Failed to query transfer", error); + ); +``` + + + + +### Transfer with Object Metadata + +To upload a file accompanied by metadata, utilize the `StorageUploadFileOptions` builder. Start by creating a hashMap object, then incorporate it into the `StorageUploadFileOptions` during the build process before passing it along to the upload function. + +#### [Java] +```java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + // Create metadata + Map userMetadata = new HashMap<>(); + userMetadata.put("myKey", "myVal"); + + // Configure upload options with metadata + StorageUploadFileOptions options = StorageUploadFileOptions.builder() + .metadata(userMetadata) + .build(); + + // Perform the upload + Amplify.Storage.uploadFile( + StoragePath.fromString("public/example"), + exampleFile, + options, + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Upload failed", error) + ); +} +``` + +#### [Kotlin - Callbacks] +```kotlin +fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "example") + exampleFile.writeText("Example file contents") + + // Create metadata + val userMetadata: MutableMap = HashMap() + userMetadata["myKey"] = "myVal" + + // Configure upload options with metadata + val options = StorageUploadFileOptions.builder() + .metadata(userMetadata) + .build() + + // Perform the upload + Amplify.Storage.uploadFile( + StoragePath.fromString("public/example"), + exampleFile, + options, + { result -> Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") }, + { error -> Log.e("MyAmplifyApp", "Upload failed", error) } + ) +} +``` + +#### [Kotlin - Coroutines] +```kotlin +fun uploadFile() { + val exampleFile = File(applicationContext.filesDir, "example") + exampleFile.writeText("Example file contents") + + // Create metadata + val userMetadata: MutableMap = HashMap() + userMetadata["myKey"] = "myVal" + + // Configure upload options with metadata + val options = StorageUploadFileOptions.builder() + .metadata(userMetadata) + .build() + + val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options) + val progressJob = activityScope.async { + upload.progress().collect { + Log.i("MyAmplifyApp", "Fraction completed: ${it.fractionCompleted}") + } + } + try { + val result = upload.result() + Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") + } catch (error: StorageException) { + Log.e("MyAmplifyApp", "Upload failed", error) + } + progressJob.cancel() +} +``` + +#### [RxJava] +```Java +private void uploadFile() { + File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); + writer.append("Example file contents"); + writer.close(); + } catch (Exception exception) { + Log.e("MyAmplifyApp", "Upload failed", exception); + } + + Map userMetadata = new HashMap<>(); + userMetadata.put("myKey", "myVal"); + + StorageUploadFileOptions options = StorageUploadFileOptions.builder() + .metadata(userMetadata) + .build(); + + RxStorageBinding.RxProgressAwareSingleOperation rxUploadOperation = + RxAmplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options); + + rxUploadOperation + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Upload failed", error) + ); +} + ``` + + + + +### Pause, resume, and cancel uploads + +We have callback functions that support resuming, pausing, and cancelling `uploadData` requests. + +```javascript +import { uploadData, isCancelError } from 'aws-amplify/storage'; + +// Pause, resume, and cancel a task +const uploadTask = uploadData({ path, data: file }); +//... +uploadTask.pause(); +//... +uploadTask.resume(); +//... +uploadTask.cancel(); +//... +try { + await uploadTask.result; +} catch (error) { + if (isCancelError(error)) { + // Handle error thrown by task cancellation + } +} +``` + + + +### Pause, resume, and cancel uploads + +Calls to `uploadData` or `uploadFile` return a reference to the task that is actually performing the upload. + +You can pause then resume the task or cancel a task as shown below. + +```swift +uploadTask.pause() +uploadTask.resume() +uploadTask.cancel() +``` + + + +Upload tasks are run using `URLSessionTask` instances internally. You can learn more about them in [Apple's official documentation](https://developer.apple.com/documentation/foundation/urlsessiontask). + + + + + +### All `upload` options + +Option | Type | Description | +| -- | -- | ----------- | +| metadata | [String: String] | Metadata for the object to store. | +| contentType | String | The standard MIME type describing the format of the object to store. | +| bucket | StorageBucket | The bucket in which the object should be stored | + + + +## Working with Security Scoped Resources (from iCloud) +Security scoped resources refer to files that are retrieved from iCloud or other cloud storage providers. You're likely to run into these file types when using system components that provide access to files stored in iCloud, e.g. [UIDocumentBrowserViewController](https://developer.apple.com/documentation/uikit/uidocumentbrowserviewcontroller). + +To upload security scoped resources, you'll need to: +1. use [startAccessingSecurityScopedResource()](https://developer.apple.com/documentation/foundation/url/1779698-startaccessingsecurityscopedreso) and [stopAccessingSecurityScopedResource()](https://developer.apple.com/documentation/foundation/url/1780153-stopaccessingsecurityscopedresou) to access the data within security scoped files +2. temporarily persist the data from the security scoped files in your app's sandbox +3. upload files using the temporary URLs +4. delete temporarily persisted files (optional) +```swift +struct ScopedResourceFile { + let name: String + let data: Data +} + +func getTempUrls(securityScopedUrls: [URL]) -> [URL] { + // 1. get the content of security scoped resources into ScopedResourceFile struct + let fileContents = securityScopedUrls.compactMap { url -> ScopedResourceFile? in + let startAccess = url.startAccessingSecurityScopedResource() + guard startAccess else { + print("Issue accessing security scoped resource at :\(url)") + return nil + } + defer { url.stopAccessingSecurityScopedResource() } + do { + let data = try Data(contentsOf: url) + let fileName = url.lastPathComponent + return ScopedResourceFile(name: fileName, data: data) + } catch { + print("Couldn't create Data from contents of file at url: \(url)") + return nil + } + } + + // 2. write the file contents to temporary files and return the URLs of the temp files + let localFileURLs = persistTemporaryFiles(fileContents) + + // 3. Now you have local URLs for the files you'd like to upload. + return localFileURLs +} +``` + + + +### Transfer with Object Metadata + +Custom metadata can be associated with your uploaded object by passing the metadata option. + +```ts +import { uploadData } from 'aws-amplify/storage'; + +const result = await uploadData({ + path: 'album/2024/1.jpg', + data: file, + options: { + metadata: { + customKey: 'customValue', + }, + }, +}); +``` + +### More upload options + +The behavior of `uploadData` and properties of the uploaded object can be customized by passing in additional options. + +```ts +import { uploadData } from 'aws-amplify/storage'; + +const result = await uploadData({ + path: 'album/2024/1.jpg', + data: file, + options: { + // content-type header to be used when downloading + contentType: "image/jpeg", + // configure how object is presented + contentDisposition: "attachment", + // whether to use accelerate endpoint + useAccelerateEndpoint: true, + // the account ID that owns requested bucket + expectedBucketOwner: "123456789012", + // whether to check if an object with the same key already exists before completing the upload + preventOverwrite: true, + // whether to compute the checksum for the data to be uploaded, so the S3 can verify the data integrity + checksumAlgorithm: "crc-32", // only 'crc-32' is supported currently + }, +}); +``` +Option | Type | Default | Description | +| -- | :--: | :--: | ----------- | +| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | +| contentType | string | application/octet-stream | The default content-type header value of the file when downloading it.

    Read more at [Content-Type documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) | +| contentEncoding | string | β€” | The default content-encoding header value of the file when downloading it.

    Read more at [Content-Encoding documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding) | +| contentDisposition | string | β€” | Specifies presentational information for the object.

    Read more at [Content-Disposition documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) | +| metadata | map\ | β€” | A map of metadata to store with the object in S3.

    Read more at [S3 metadata documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html#UserMetadata) | +| useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/upload-files/#transfer-acceleration) | +| expectedBucketOwner | string | - | The account ID that owns requested bucket. | +| preventOverwrite | boolean | false | Whether to check if an object with the same key already exists before completing the upload. If exists, a `Precondition Failed` error will be thrown | +| checksumAlgorithm | "crc-32" | - | Whether to compute the checksum for the data to be uploaded, so the S3 can verify the data integrity. Only 'crc-32' is supported currently | + + + +Uploads that were initiated over one hour ago will be cancelled automatically. There are instances (e.g. device went offline, user logs out) where the incomplete file remains in your Amazon S3 account. It is recommended to [setup a S3 lifecycle rule](https://aws.amazon.com/blogs/aws-cloud-financial-management/discovering-and-deleting-incomplete-multipart-uploads-to-lower-amazon-s3-costs/) to automatically cleanup incomplete upload requests. + + + + +## MultiPart upload + +Amplify will automatically perform an Amazon S3 multipart upload for objects that are larger than 5MB. For more information about S3's multipart upload, see [Uploading and copying objects using multipart upload](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) + +--- + +--- +title: "Download files" +section: "build-a-backend/storage" +platforms: ["angular", "javascript", "nextjs", "react", "vue", "android", "swift", "flutter", "react-native"] +gen: 2 +last-updated: "2025-07-04T12:58:35.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/download-files/" +--- + + +## Storage Image React UI Component + +You can easily display images in your app by using the cloud-connected Storage Image React UI component. This component fetches images securely from your storage resource and displays it on the web page. + +```bash title="Terminal" showLineNumbers={false} +npm add @aws-amplify/ui-react-storage aws-amplify +``` +```tsx +import { StorageImage } from '@aws-amplify/ui-react-storage'; + +export const DefaultStorageImageExample = () => { + return ; +}; +``` + +Learn more about how you can further customize the UI component by referring to the [Storage Image documentation](https://ui.docs.amplify.aws/react/connected-components/storage/storageimage). + + +To further customize your in-app experience, you can use the `getUrl` or `downloadData` API from the Amplify Library for Storage. + + + +**Note:** Refer to [the Transfer Acceleration documentation](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) to learn how to enable transfer acceleration for storage APIs. + + + +## Get or download file from a URL + +With the `getUrl` API, you can get a presigned URL which is valid for 900 seconds or 15 minutes by default. You can use this URL to create a download link for users to click on. The `expiresAt` property is a `Date` object that represents the time at which the URL will expire. + + +```typescript +import { getUrl } from 'aws-amplify/storage'; + +const linkToStorageFile = await getUrl({ + path: "album/2024/1.jpg", + // Alternatively, path: ({identityId}) => `album/${identityId}/1.jpg` +}); +console.log('signed URL: ', linkToStorageFile.url); +console.log('URL expires at: ', linkToStorageFile.expiresAt); +``` +Inside your template or JSX code, you can use the `url` property to create a link to the file: + +```tsx + + {fileName} + +``` + + + +This function does not check if the file exists by default. As result, the signed URL may fail if the file to be downloaded does not exist. + + + +### More getUrl options + +The behavior of the `getUrl` API can be customized by passing in options. + +```typescript +import { getUrl } from 'aws-amplify/storage'; + +const linkToStorageFile = await getUrl({ + path: "album/2024/1.jpg", + options: { + // specify a target bucket using name assigned in Amplify Backend + bucket: 'assignedNameInAmplifyBackend', + // ensure object exists before getting url + validateObjectExistence: true, + // url expiration time in seconds. + expiresIn: 300, + // whether to use accelerate endpoint + useAccelerateEndpoint: true, + // The account ID that owns the requested bucket. + expectedBucketOwner: '123456789012', + } +}); +``` + +Option | Type | Default | Description | +| :--: | :--: | :--: | ----------- | +| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) +| validateObjectExistence | boolean | false | Whether to head object to make sure the object existence before downloading. | +| expiresIn | number | 900 | Number of seconds till the URL expires.

    The expiration time of the presigned url is dependent on the session and will max out at 1 hour. | +| useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | + + + + +#### [Java] + +```java +Amplify.Storage.getUrl( + StoragePath.fromString("public/example"), + result -> Log.i("MyAmplifyApp", "Successfully generated: " + result.getUrl()), + error -> Log.e("MyAmplifyApp", "URL generation failure", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Storage.getUrl( + StoragePath.fromString("public/example"), + { Log.i("MyAmplifyApp", "Successfully generated: ${it.url}") }, + { Log.e("MyAmplifyApp", "URL generation failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val url = Amplify.Storage.getUrl(StoragePath.fromString("public/example")).url + Log.i("MyAmplifyApp", "Successfully generated: $url") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "URL generation failure", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Storage.getUrl(StoragePath.fromString("public/example")).subscribe( + result -> Log.i("MyAmplifyApp", "Successfully generated: " + result.getUrl()), + error -> Log.e("MyAmplifyApp", "URL generation failure", error) +); +``` + +### Check the existence of a file + +When creating a downloadable URL, you can choose to check if the file exists by setting `validateObjectExistence` to +`true` in `AWSS3StorageGetPresignedUrlOptions`. If the file is inaccessible or does not exist, a `StorageException` is thrown. +This allows you to check if an object exists when generating the presigned URL, which you can then use to download +that object. + +#### [Java] + +```java +AWSS3StorageGetPresignedUrlOptions options = AWSS3StorageGetPresignedUrlOptions + .builder() + .setValidateObjectExistence(true) + .build(); + +Amplify.Storage.getUrl( + StoragePath.fromString("public/example"), + options, + result -> Log.i("MyAmplifyApp", "Successfully generated: " + result.getUrl()), + error -> Log.e("MyAmplifyApp", "URL generation failure", error) +); +``` + +#### [Kotlin - Callbacks] +```kotlin +val options = AWSS3StorageGetPresignedUrlOptions + .builder() + .setValidateObjectExistence(true) + .build() + +Amplify.Storage.getUrl( + StoragePath.fromString("public/example"), + options, + { Log.i("MyAmplifyApp", "Successfully generated: ${it.url}") }, + { Log.e("MyAmplifyApp", "URL generation failure", it) } +) +``` + +#### [Kotlin - Coroutines] +```kotlin +try { + val options = AWSS3StorageGetPresignedUrlOptions + .builder() + .setValidateObjectExistence(true) + .build() + + val url = Amplify.Storage.getUrl(StoragePath.fromString("public/example"), options).url + Log.i("MyAmplifyApp", "Successfully generated: $url") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "URL generation failure", error) +} +``` + +#### [RxJava] +```java +AWSS3StorageGetPresignedUrlOptions options = AWSS3StorageGetPresignedUrlOptions + .builder() + .setValidateObjectExistence(true) + .build(); + +RxAmplify.Storage.getUrl(StoragePath.fromString("public/example"), options).subscribe( + result -> Log.i("MyAmplifyApp", "Successfully generated: " + result.getUrl()), + error -> Log.e("MyAmplifyApp", "URL generation failure", error) +); +``` + +### All `getURL` options + +Option | Type | Description | +| -- | -- | ----------- | +| bucket | StorageBucket | The bucket in which the object is stored. | +| expires | Integer | Number of seconds before the URL expires. | +| useAccelerateEndpoint | Boolean | Flag to configure use of acceleration mode. | +| validateObjectExistence | Boolean | Flag to check if the file exists. | + + + +```swift +let url = try await Amplify.Storage.getURL( + path: .fromString("public/example/path") +) +print("Completed: \(url)") +``` + +### Check the existence of a file + +When creating a downloadable URL, you can choose to check if the file exists by setting `validateObjectExistence` to +`true` in `AWSStorageGetURLOptions`. If the file is inaccessible or does not exist, a `StorageError` is thrown. +This allows you to check if an object exists during generating the presigned URL, which you can then use to download +that object. + +```swift +let url = try await Amplify.Storage.getURL( + path: .fromString("public/example/path"), + options: .init( + pluginOptions: AWSStorageGetURLOptions( + validateObjectExistence: true + ) + ) +) +``` + +### All `getURL` options + +Option | Type | Description | +| -- | -- | ----------- | +| expires | Int | Number of seconds before the URL expires | +| bucket | StorageBucket | The bucket in which the object is stored | + + + +When creating a downloadable URL, you can choose to check if the file exists by setting `validateObjectExistence` to +`true` in `S3GetUrlPluginOptions`. If the file is inaccessible or does not exist, a `StorageException` is thrown. +This allows you to check if an object exists during generating the presigned URL, which you can then use to download +that object. You may also pass in a bucket to target into `StorageGetUrlOptions` from either the chosen name in the +backend or the console name and region. If no bucket is provided, the default bucket defined in the backend will be used. +Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) + +```dart +Future getDownloadUrl() async { + try { + final result = await Amplify.Storage.getUrl( + path: const StoragePath.fromString('public/example.txt'), + /* + // targeting a specific bucket by the name defined in the backend + options: StorageGetUrlOptions( + bucket: StorageBucket.fromOutputs('secondBucket'), + ), + */ + ).result; + safePrint('url: ${result.url}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + + + +## Download to a file + +Use the `downloadData` API to download the file locally. + +```javascript +import { downloadData } from 'aws-amplify/storage'; + +// Downloads file content to memory +const { body, eTag } = await downloadData({ + path: "album/2024/1.jpg" +}).result; +``` + + + +## Download to a file + +Use the `downloadFile` API to download the file locally on the client. + + +**Note:** When downloading a file that will overwrite a preexisting file, ensure that your app has the proper write permission to overwrite it. If you are attempting to write to a file that a different app already contributed to the media store, you must request user consent[as described here](https://developer.android.com/training/data-storage/shared/media#update-other-apps-files). + +To learn more, refer to Android's developer documentation about [Scoped Storage](https://developer.android.com/training/data-storage#scoped-storage). + +Amplify will throw a `StorageException` if it is unable to modify a preexisting file. + + +#### [Java] + +```java +Amplify.Storage.downloadFile( + StoragePath.fromString("public/example"), + new File(getApplicationContext().getFilesDir() + "/download.txt"), + result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), + error -> Log.e("MyAmplifyApp", "Download Failure", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val file = File("${applicationContext.filesDir}/download.txt") +Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, + { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, + { Log.e("MyAmplifyApp", "Download Failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val file = File("${applicationContext.filesDir}/download.txt") + val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file) + try { + val fileName = download.result().file.name + Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") + } catch (error: StorageException) { + Log.e("MyAmplifyApp", "Download Failure", error) + } +} +``` + +#### [RxJava] + +```java +RxProgressAwareSingleOperation download = + RxAmplify.Storage.downloadFile( + StoragePath.fromString("public/example"), + new File(getApplicationContext().getFilesDir() + "/download.txt") + ); + +download + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), + error -> Log.e("MyAmplifyApp", "Download Failure", error) + ); +``` + + + + +## Download files +### Download to a local file + +Use the `downloadFile` API to download the file locally on the client. + +You can download to a file [URL](https://developer.apple.com/documentation/foundation/url) with `Amplify.Storage.downloadFile`: + +#### [Async/Await] +```swift +let downloadToFileUrl = FileManager.default.urls( + for: .documentDirectory, + in: .userDomainMask +)[0].appendingPathComponent("myFile.txt") + +let downloadTask = Amplify.Storage.downloadFile( + path: .fromString("public/example/path"), + local: downloadToFileUrl, + options: nil +) +Task { + for await progress in await downloadTask.progress { + print("Progress: \(progress)") + } +} +try await downloadTask.value +print("Completed") +``` + +#### [Combine] +```swift +let downloadToFileUrl = FileManager.default.urls( + for: .documentDirectory, + in: .userDomainMask +)[0].appendingPathComponent("myFile.txt") + +let downloadTask = Amplify.Storage.downloadFile( + path: .fromString("public/example/path"), + local: downloadToFileUrl, + options: nil +) +let progressSink = downloadTask + .inProcessPublisher + .sink { progress in + print("Progress: \(progress)") + } + +let resultSink = downloadTask + .resultPublisher + .sink { + if case let .failure(storageError) = $0 { + print("Failed: \(storageError.errorDescription). \(storageError.recoverySuggestion)") + } + } + receiveValue: { + print("Completed") + } +``` + +### Download to data in memory + +You can download to in-memory buffer [Data](https://developer.apple.com/documentation/foundation/data) object with `Amplify.Storage.downloadData`: + +#### [Async/Await] + +```swift +let downloadTask = Amplify.Storage.downloadData( + path: .fromString("public/example/path") +) +Task { + for await progress in await downloadTask.progress { + print("Progress: \(progress)") + } +} +let data = try await downloadTask.value +print("Completed: \(data)") +``` + +#### [Combine] + +```swift +let downloadTask = Amplify.Storage.downloadData( + path: .fromString("public/example/path") +) +let progressSink = downloadTask + .inProcessPublisher + .sink { progress in + print("Progress: \(progress)") + } + +let resultSink = downloadTask + .resultPublisher + .sink { + if case let .failure(storageError) = $0 { + print("Failed: \(storageError.errorDescription). \(storageError.recoverySuggestion)") + } + } + receiveValue: { data in + print("Completed: \(data)") + } +``` + +### Download from a specified bucket + +You can also perform a download operation from a specific bucket by providing the `bucket` option + +#### [From Outputs] +You can use `.fromOutputs(name:)` to provide a string representing the target bucket's assigned name in the Amplify Backend. + +```swift +// Download to File +let downloadTask = Amplify.Storage.downloadFile( + path: .fromString("public/example/path"), + local: downloadToFileUrl, + options: .init( + bucket: .fromOutputs(name: "secondBucket") + ) +) + +// Download to Data +let downloadTask = Amplify.Storage.downloadData( + path: .fromString("public/example/path"), + options: .init( + bucket: .fromOutputs(name: "secondBucket") + ) +) +``` + +#### [From Bucket Info] +You can also use `.fromBucketInfo(_:)` to provide a bucket name and region directly. + +```swift +// Download to File +let downloadTask = Amplify.Storage.downloadFile( + path: .fromString("public/example/path"), + local: downloadToFileUrl, + options: .init( + bucket: .fromBucketInfo(.init( + bucketName: "another-bucket-name", + region: "another-bucket-region") + ) + ) +) + +// Download to Data +let downloadTask = Amplify.Storage.downloadData( + path: .fromString("public/example/path"), + options: .init( + bucket: .fromBucketInfo(.init( + bucketName: "another-bucket-name", + region: "another-bucket-region") + ) + ) +) +``` + + + + +## Download to a file + +You can download a file to a local directory using `Amplify.Storage.downloadFile`. + +You can use the [path_provider](https://pub.dev/packages/path_provider) package to create a local file in the user's documents directory where you can store the downloaded data. + +#### [Mobile & Desktop] + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:path_provider/path_provider.dart'; + +Future downloadFile() async { + final documentsDir = await getApplicationDocumentsDirectory(); + final filepath = '${documentsDir.path}/example.txt'; + try { + final result = await Amplify.Storage.downloadFile( + path: const StoragePath.fromString('public/example.txt'), + localFile: AWSFile.fromPath(filepath), + ).result; + safePrint('Downloaded file is located at: ${result.localFile.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + +#### [Web] + +On Web, the download process will be handled by the browser. You can provide the downloaded file name by specifying the `path` parameter of `AWSFile.fromPath`. E.g. this instructs the browser to download the file `download.txt`. + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; + +Future downloadFile() async { + try { + final result = await Amplify.Storage.downloadFile( + path: const StoragePath.fromString('public/example.txt'), + localFile: AWSFile.fromPath('download.txt'), + ).result; + safePrint('Downloaded file: ${result.downloadedItem.path}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + + + + +### Get the text value of downloaded File + +You can get the value of file in any of the three formats: `blob`, `json`, or `text`. You can call the respective method on the `body` property to consume the set data in the respective format. + +```javascript +import { downloadData } from 'aws-amplify/storage'; + +try { + const downloadResult = await downloadData({ + path: "album/2024/1.jpg" + }).result; + const text = await downloadResult.body.text(); + // Alternatively, you can use `downloadResult.body.blob()` + // or `downloadResult.body.json()` get read body in Blob or JSON format. + console.log('Succeed: ', text); +} catch (error) { + console.log('Error : ', error); +} +``` + + + +### Download from a specified bucket + +You can also perform an upload operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. + +```ts +import { downloadData } from 'aws-amplify/storage'; + +const result = await downloadData({ + path: 'album/2024/1.jpg', + options: { + // highlight-start + // Specify a target bucket using name assigned in Amplify Backend + bucket: 'assignedNameInAmplifyBackend' + // highlight-end + } +}).result; + +``` +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. + +```ts +import { downloadData } from 'aws-amplify/storage'; + +const result = await downloadData({ + path: 'album/2024/1.jpg', + options: { + // highlight-start + // Alternatively, provide bucket name from console and associated region + bucket: { + bucketName: 'bucket-name-from-console', + region: 'us-east-2' + } + // highlight-end + } +}).result; + +``` +### Monitor download progress + +```javascript +import { downloadData } from 'aws-amplify/storage'; + +// Download a file from S3 bucket +const { body, eTag } = await downloadData( + { + path: 'album/2024/1.jpg', + options: { + onProgress: (progress) => { + console.log(`Download progress: ${(progress.transferredBytes/progress.totalBytes) * 100}%`); + } + } + } +).result; +``` + +### Cancel download + +```javascript +import { downloadData, isCancelError } from 'aws-amplify/storage'; + +const downloadTask = downloadData({ path: 'album/2024/1.jpg' }); +downloadTask.cancel(); +try { + await downloadTask.result; +} catch (error) { + if (isCancelError(error)) { + // Handle error thrown by task cancellation. + } +} +``` + + +### Download from a specified bucket + +You can also perform a download operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. + +#### [Java] + +```java +StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); +StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); +Amplify.Storage.downloadFile( + StoragePath.fromString("public/example"), + new File(getApplicationContext().getFilesDir() + "/download.txt"), + options, + result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), + error -> Log.e("MyAmplifyApp", "Download Failure", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val secondBucket = StorageBucket.fromOutputs("secondBucket") +val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() +val file = File("${applicationContext.filesDir}/download.txt") +Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, option, + { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, + { Log.e("MyAmplifyApp", "Download Failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val secondBucket = StorageBucket.fromOutputs("secondBucket") +val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() +val file = File("${applicationContext.filesDir}/download.txt") +val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options) +try { + val fileName = download.result().file.name + Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "Download Failure", error) +} +``` + +#### [RxJava] + +```java +StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); +StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); +RxProgressAwareSingleOperation download = + RxAmplify.Storage.downloadFile( + StoragePath.fromString("public/example"), + new File(getApplicationContext().getFilesDir() + "/download.txt"), + options + ); + +download + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), + error -> Log.e("MyAmplifyApp", "Download Failure", error) + ); +``` + +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. + +#### [Java] + +```java +BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); +StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); +StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); +Amplify.Storage.downloadFile( + StoragePath.fromString("public/example"), + new File(getApplicationContext().getFilesDir() + "/download.txt"), + options, + result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), + error -> Log.e("MyAmplifyApp", "Download Failure", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") +val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) +val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() +val file = File("${applicationContext.filesDir}/download.txt") +Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options, + { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, + { Log.e("MyAmplifyApp", "Download Failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") +val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) +val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() +val file = File("${applicationContext.filesDir}/download.txt") +val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options) +try { + val fileName = download.result().file.name + Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "Download Failure", error) +} +``` + +#### [RxJava] + +```java +BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); +StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); +StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); +RxProgressAwareSingleOperation download = + RxAmplify.Storage.downloadFile( + StoragePath.fromString("public/example"), + new File(getApplicationContext().getFilesDir() + "/download.txt"), + options, + ); + +download + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), + error -> Log.e("MyAmplifyApp", "Download Failure", error) + ); +``` + +### Monitor download progress + +#### [Java] + +```java +Amplify.Storage.downloadFile( + StoragePath.fromString("public/example"), + new File(getApplicationContext().getFilesDir() + "/download.txt"), + StorageDownloadFileOptions.defaultInstance(), + progress -> Log.i("MyAmplifyApp", "Fraction completed: " + progress.getFractionCompleted()), + result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), + error -> Log.e("MyAmplifyApp", "Download Failure", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val file = File("${applicationContext.filesDir}/download.txt") +val options = StorageDownloadFileOptions.defaultInstance() +Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options, + { Log.i("MyAmplifyApp", "Fraction completed: ${it.fractionCompleted}") }, + { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, + { Log.e("MyAmplifyApp", "Download Failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val file = File("${applicationContext.filesDir}/download.txt") +val options = StorageDownloadFileOptions.defaultInstance() +val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options) +val progressJob = activityScope.async { + download.progress().collect { progress -> + Log.i("MyAmplifyApp", "Fraction completed: ${progress.fractionCompleted}") + } +} +try { + val fileName = download.result().file.name + Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "Download Failure", error) +} +progressJob.cancel() +``` + +#### [RxJava] + +```java +RxProgressAwareSingleOperation download = + RxAmplify.Storage.downloadFile(StoragePath.fromString("public/example"), localFile); + +download + .observeProgress() + .subscribe( + progress -> Log.i("MyAmplifyApp", progress.getFractionCompleted()) + ); +``` + + + + +### Monitor download progress + +```dart +final operation = Amplify.Storage.downloadData( + path: const StoragePath.fromString('public/example.txt'), + onProgress: (progress) { + safePrint('fraction totalBytes: ${progress.totalBytes}'); + safePrint('fraction transferredBytes: ${progress.transferredBytes}'); + safePrint('fraction completed: ${progress.fractionCompleted}'); + }, +); +``` + + + +### Pause, resume, and cancel downloads + +```dart + +Future upload() async { + final operation = Amplify.Storage.downloadFile( + localFile: AWSFile.fromPath('/path/to/local/file'), + path: const StoragePath.fromString('public/example.txt'), + ); + + // pause operation + await operation.pause(); + + // resume operation + await operation.resume(); + + // cancel operation + await operation.cancel(); +} + +``` + + + +### Query transfers + +When an upload or download operation is requested using the Amplify Android library, the request is first persisted in the local SQLite Database and then queued for execution. You can query the transfer operation queued in the local database using the transfer ID returned by an upload or download API. Get-Transfer API could retrieve a pending transfer previously en-queued and enable attaching a listener to receive updates on progress change, on-error or on-success, or pause, cancel or resume it. + +#### [Java] + +```java +Amplify.Storage.getTransfer("TRANSFER_ID", + operation -> { + Log.i("MyAmplifyApp", "Current State" + operation.getTransferState()); + // set listener to receive updates + operation.setOnProgress( progress -> {}); + operation.setOnSuccess( result -> {}); + operation.setOnError(error -> {}); + + // possible actions + operation.pause(); + operation.resume(); + operation.start(); + operation.cancel(); + }, + { + error -> Log.e("MyAmplifyApp", "Failed to query transfer", error) + } +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Storage.getTransfer("TRANSFER_ID", + { operation -> + Log.i("MyAmplifyApp", "Current State" + operation.transferState) + // set listener to receive updates + operation.setOnProgress { } + operation.setOnSuccess { } + operation.setOnError { } + + // possible actions + operation.pause() + operation.resume() + operation.start() + operation.cancel() + }, + { + Log.e("MyAmplifyApp", "Failed to query transfer", it) + } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val operation = Amplify.Storage.getTransfer("TRANSFER_ID") + Log.i("MyAmplifyApp", "Current State" + operation.transferState) + // set listener to receive updates + operation.setOnProgress { } + operation.setOnSuccess { } + operation.setOnError { } + + // possible actions + operation.pause() + operation.resume() + operation.start() + operation.cancel() +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "Failed to query transfer", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Storage.getTransfer("TRANSFER_ID") + .subscribe( + operation -> { + Log.i("MyAmplifyApp", "Current State" + operation.getTransferState()); + // set listener to receive updates + operation.setOnProgress( progress -> {}); + operation.setOnSuccess( result -> {}); + operation.setOnError(error -> {}); + + // possible actions + operation.pause(); + operation.resume(); + operation.start(); + operation.cancel(); + }, + error -> Log.e("MyAmplifyApp", "Failed to query transfer", error); + ); +``` + + + + +## API to download data in memory + +You can download a file to in-memory buffer with `Amplify.Storage.downloadData`: + +```dart +Future download() async { + try { + final result = await Amplify.Storage.downloadData( + path: const StoragePath.fromString('public/example.txt'), + ).result; + safePrint('Downloaded data: ${result.bytes}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + +## More download options + +Option | Type | Description | +| -- | -- | ----------- | +| bucket | StorageBucket | The target bucket from the assigned name in the Amplify Backend or from the bucket name and region in the console

    Defaults to the default bucket and region from the Amplify configuration if this option is not provided.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | +| getProperties | boolean | Whether to retrieve properties for the downloaded object using theAmplify.Storage.getProperties() after the operation completes. When set to true the returned item will contain additional info such as metadata and content type. | +| useAccelerateEndpoint | boolean | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/upload-files/#transfer-acceleration) | +| bytesRange | S3DataBytesRange | The byte range to download from the object | + +### Example of `downloadFile` with options + +```dart +final operation = Amplify.Storage.downloadFile( + path: const StoragePath.fromString('public/example.txt'), + localFile: AWSFile.fromPath('/path/to/local/file.txt'), + options: const StorageDownloadFileOptions( + pluginOptions: S3DownloadFilePluginOptions( + getProperties: true, + useAccelerateEndpoint: true, + ), + bucket: StorageBucket.fromOutputs('secondBucket'), + ), +); +``` + +### Example of `downloadData` with options + +```dart +final operation = Amplify.Storage.downloadData( + path: const StoragePath.fromString('public/example.txt'), + options: StorageDownloadDataOptions( + pluginOptions: S3DownloadDataPluginOptions( + getProperties: true, + useAccelerateEndpoint: true, + bytesRange: S3DataBytesRange(start: 0, end: 100), + ), + ), +); +``` + +You can also perform a `downloadData` or `downloadFile` operation to a specific bucket by providing the `bucket` option. You can pass in a `StorageBucket` object representing the target bucket from the name defined in the Amplify Backend. + +```dart +final operation = Amplify.Storage.downloadFile( + path: const StoragePath.fromString('public/example.txt'), + localFile: AWSFile.fromPath('/path/to/local/file.txt'), + options: const StorageDownloadFileOptions( + pluginOptions: S3DownloadFilePluginOptions( + getProperties: true, + useAccelerateEndpoint: true, + ), + bucket: StorageBucket.fromOutputs('secondBucket'), + ), +); +``` + +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. + +```dart +final operation = Amplify.Storage.downloadData( + path: const StoragePath.fromString('public/example.txt'), + options: StorageDownloadDataOptions( + pluginOptions: S3DownloadDataPluginOptions( + getProperties: true, + useAccelerateEndpoint: true, + bytesRange: S3DataBytesRange(start: 0, end: 100), + ), + bucket: StorageBucket.fromBucketInfo( + BucketInfo( + bucketName: 'second-bucket-name-from-console', + region: 'us-east-2', + ), + ), + ), +); +``` + + + +### Pause, resume, and cancel downloads + +Calls to `downloadData` or `downloadFile` return a reference to the task that is actually performing the download. + +You can pause then resume the task or cancel a task as shown below. + +```swift +downloadTask.pause() +downloadTask.resume() +downloadTask.cancel() +``` + + + +Download tasks are run using `URLSessionTask` instances internally. You can learn more about them in [Apple's official documentation](https://developer.apple.com/documentation/foundation/urlsessiontask). + + + + + +### More download options +The behavior of the `downloadData` API can be customized by passing in options. + +```javascript +import { downloadData } from 'aws-amplify/storage'; + +// Downloads file content to memory +const { body, eTag } = await downloadData({ + path: "album/2024/1.jpg", + options: { + // optional bytes range parameter to download a part of the file, the 2nd MB of the file in this example + bytesRange: { + start: 1024, + end: 2048 + }, + useAccelerateEndpoint: true, + } +}).result; + +``` + +Option | Type | Default | Description | +| :--: | :--: | :--: | ----------- | +| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | +| onProgress | callback | β€” | Callback function tracking the upload/download progress. | +| bytesRange | \{ start: number; end:number; \} | β€” | Bytes range parameter to download a part of the file. | +| useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | + +## Frequently Asked Questions + +- [Image compression](https://github.com/aws-amplify/amplify-js/issues/6081) or CloudFront CDN caching for your S3 buckets is not yet possible. +- `downloadData` does not provide a cache control option and it replies on runtime HTTP caching behavior. If you need to bypass the cache, you can use the `getUrl` API to create a presigned URL for downloading the file. +- `downloadData` does not support S3 object versioning, it always downloads the latest version. + + +--- + +--- +title: "List file properties" +section: "build-a-backend/storage" +platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] +gen: 2 +last-updated: "2025-02-07T16:03:59.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/list-files/" +--- + +You can list files without having to download all the files. You can do this by using the `list` API from the Amplify Library for Storage. +You can also get properties individually for a file using the `getProperties` API. + + +## List Files + + +```javascript +import { list } from 'aws-amplify/storage'; + +const result = await list({ + path: 'album/photos/', + // Alternatively, path: ({identityId}) => `album/${identityId}/photos/` +}); +``` + +Note the trailing slash `/` - if you had requested `list({ path : 'album/photos' })` it would also match against files like `album/photos123.jpg` alongside `album/photos/123.jpg`. + +The format of the response will look similar to the below example: + +```js +{ + items: [ + { + path: "album/photos/123.jpg", + eTag: "30074401292215403a42b0739f3b5262", + lastModified: "Thu Oct 08 2020 23:59:31 GMT+0800 (Singapore Standard Time)", + size: 138256 + }, + // ... + ], +} +```` + +If the `pageSize` is set lower than the total file size, a single `list` call only returns a subset of all the files. To list all the files with multiple calls, users can use the `nextToken` flag: + +```javascript +import { list } from 'aws-amplify/storage'; + +const PAGE_SIZE = 20; +let nextToken; +// ... +const loadNextPage = async () => { + const response = await list({ + path: 'photos/', + // Alternatively, path: ({ identityId }) => `album/${identityId}/photos/` + options: { + pageSize: PAGE_SIZE, + nextToken, + }, + }); + if (response.nextToken) { + nextToken = response.nextToken; + } else { + nextToken = undefined; + } + // render list items from response.items +}; +``` + +### List All files +```javascript +import { list } from 'aws-amplify/storage'; + +const result = await list({ + path: 'album/photos/', + // Alternatively, path: ({identityId}) => `album/${identityId}/photos/`, + options: { + listAll: true, + } +}); +``` + + Manually created folders will show up as files with a `size` of 0, but you can also match keys against a regex like `file.key.match(/\.[0-9a-z]+$/i)` to distinguish files from folders. Since "folders" are a virtual concept in Amazon S3, any file may declare any depth of folder just by having a `/` in its name. + +To access the contents and subpaths of a "folder", you have two options: + +1. Request the entire path and parse the contents. +2. Use the subpathStrategy option to retrieve only the files within the specified path (i.e. exclude files under subpaths). + +### Get all nested files within a path + +This retrieves all files and folders under a given path. You may need to parse the result to get only the files within the specified path. + +```js +function processStorageList(response) { + let files = []; + let folders = new Set(); + response.items.forEach((res) => { + if (res.size) { + files.push(res); + // sometimes files declare a folder with a / within then + let possibleFolder = res.path.split('/').slice(0, -1).join('/'); + if (possibleFolder) folders.add(possibleFolder); + } else { + folders.add(res.path); + } + }); + return { files, folders }; +} +``` + +If you need the files and folders in terms of a nested object instead (for example, to build an explorer UI), you could parse it recursively: + +```js +function processStorageList(response) { + const filesystem = {}; + // https://stackoverflow.com/questions/44759750/how-can-i-create-a-nested-object-representation-of-a-folder-structure + const add = (source, target, item) => { + const elements = source.split('/'); + const element = elements.shift(); + if (!element) return; // blank + target[element] = target[element] || { __data: item }; // element; + if (elements.length) { + target[element] = + typeof target[element] === 'object' ? target[element] : {}; + add(elements.join('/'), target[element], item); + } + }; + response.items.forEach((item) => add(item.path, filesystem, item)); + return filesystem; +} +``` + +This places each item's data inside a special `__data` key. + +### Excluding subpaths + +In addition to using the `list` API to get all the contents of a path, you can also use it to get only the files within a path while excluding files under subpaths. + +For example, given the following keys in your `path` you may want to return only the jpg object, and not the "vacation" subpath and its contents: + +``` +photos/photo1.jpg +photos/vacation/ +``` + +This can be accomplished with the `subpathStrategy` option: + +```ts title="src/main.ts" +import { list } from "aws-amplify/storage"; +const result = await list({ + path: "photos/", + options:{ + subpathStrategy: { strategy:'exclude' } + } +}); +``` + +The response will include only the objects within the `photos/` path and will also communicate any excluded subpaths: + +```js +{ + excludedSubpaths: [ + 'photos/vacation/' + ], + items: [ + { + path: "photos/photo1.jpg", + eTag: "30074401292215403a42b0739f3b5262", + lastModified: "Thu Oct 08 2020 23:59:31 GMT+0800 (Singapore Standard Time)", + size: 138256 + }, + ] +} +``` + +The default delimiter character is '/', but this can be changed by supplying a custom delimiter: + +```ts title="src/main.ts" +const result = await list({ + // Path uses '-' character to organize files rather than '/' + path: 'photos-', + options: { + subpathStrategy: { + strategy: 'exclude', + delimiter: '-' + } + } +}); +``` + +### List files from a specified bucket + +You can also perform an `copy` operation to a specific bucket by providing the `bucket` option. This option can either be a string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console. + +```ts +import { list } from 'aws-amplify/storage'; + +const result = await list({ + path: 'photos/', + options: { + // Specify a target bucket using name assigned in Amplify Backend + bucket: 'assignedNameInAmplifyBackend', + // Alternatively, provide bucket name from console and associated region + // bucket: { + // bucketName: 'generated-secondary-bucket-name', + // region: 'us-east-2' + // } + } +}); +``` + +### More `list` options + +| Option | Type | Default | Description | +| -- | :--: | :--: | ----------- | +| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | +| listAll | boolean | false | Set to true to list all files within the specified `path` | +| pageSize | number | 1000 | Sets the maximum number of files to be return. The range is 0 - 1000 | +| nextToken | string | β€” | Indicates whether the list is being continued on this bucket with a token | +| subpathStrategy | \{ strategy: 'include' \} \|
    \{ 'exclude',
    delimiter?: string \} | \{ strategy: 'include' \} | An object representing the subpath inclusion strategy and the delimiter used to group results for exclusion.

    Read more at [Excluding subpaths](/[platform]/build-a-backend/storage/list-files/#excluding-subpaths) | +| useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | + + + +The following example lists all objects inside the `public/photos/` path: + +#### [Java] + +```java +StoragePagedListOptions options = StoragePagedListOptions.builder() + .setPageSize(1000) + .build(); + +Amplify.Storage.list( + StoragePath.fromString("public/photos/"), + options, + result -> { + for (StorageItem item : result.getItems()) { + Log.i("MyAmplifyApp", "Item: " + item.getPath()); + } + Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); + }, + error -> Log.e("MyAmplifyApp", "List failure", error); +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val options = StoragePagedListOptions.builder() + .setPageSize(1000) + .build() + +Amplify.Storage.list(StoragePath.fromString("public/photos/"), options, + { result -> + result.items.forEach { item -> + Log.i("MyAmplifyApp", "Item: ${item.path}") + } + Log.i("MyAmplifyApp", "Next Token: ${result.nextToken}") + }, + { Log.e("MyAmplifyApp", "List failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val options = StoragePagedListOptions.builder() + .setPageSize(1000) + .build() + +try { + val result = Amplify.Storage.list(StoragePath.fromString("public/photos/"), options) + result.items.forEach { + Log.i("MyAmplifyApp", "Item: $it") + } + Log.i("MyAmplifyApp", "next token: ${result.nextToken}") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "List failure", error) +} +``` + +#### [RxJava] + +```java +StoragePagedListOptions options = StoragePagedListOptions.builder() + .setPageSize(1000) + .build(); + +RxAmplify.Storage.list(StoragePath.fromString("public/photos/"), options) + .subscribe( + result -> { + for (StorageItem item : result.getItems()) { + Log.i("MyAmplifyApp", "Item: " + item.getPath()); + } + Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); + }, + error -> Log.e("MyAmplifyApp", "List failure", error); + ); +``` + + +Note the trailing slash `/` in the given path. + +If you had used `public/photos` as path, it would also match against files like `public/photos01.jpg`. + + +### List files from a specified bucket + +You can also perform a list operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. + +#### [Java] + +```java +StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); +StoragePagedListOptions options = StoragePagedListOptions.builder() + .setPageSize(1000) + .bucket(secondBucket) + .build(); + +Amplify.Storage.list( + StoragePath.fromString("public/photos/"), + options, + result -> { + for (StorageItem item : result.getItems()) { + Log.i("MyAmplifyApp", "Item: " + item.getPath()); + } + Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); + }, + error -> Log.e("MyAmplifyApp", "List failure", error); +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val secondBucket = StorageBucket.fromOutputs("secondBucket") +val options = StoragePagedListOptions.builder() + .setPageSize(1000) + .bucket(secondBucket) + .build() + +Amplify.Storage.list(StoragePath.fromString("public/photos/"), options, + { result -> + result.items.forEach { item -> + Log.i("MyAmplifyApp", "Item: ${item.path}") + } + Log.i("MyAmplifyApp", "Next Token: ${result.nextToken}") + }, + { Log.e("MyAmplifyApp", "List failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val secondBucket = StorageBucket.fromOutputs("secondBucket") +val options = StoragePagedListOptions.builder() + .setPageSize(1000) + .bucket(secondBucket) + .build() + +try { + val result = Amplify.Storage.list(StoragePath.fromString("public/photos/"), options) + result.items.forEach { + Log.i("MyAmplifyApp", "Item: $it") + } + Log.i("MyAmplifyApp", "next token: ${result.nextToken}") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "List failure", error) +} +``` + +#### [RxJava] + +```java +StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); +StoragePagedListOptions options = StoragePagedListOptions.builder() + .setPageSize(1000) + .bucket(secondBucket) + .build(); + +RxAmplify.Storage.list(StoragePath.fromString("public/photos/"), options) + .subscribe( + result -> { + for (StorageItem item : result.getItems()) { + Log.i("MyAmplifyApp", "Item: " + item.getPath()); + } + Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); + }, + error -> Log.e("MyAmplifyApp", "List failure", error); + ); +``` + +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. + +#### [Java] + +```java +BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); +StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); +StoragePagedListOptions options = StoragePagedListOptions.builder() + .setPageSize(1000) + .bucket(secondBucket) + .build(); + +Amplify.Storage.list( + StoragePath.fromString("public/photos/"), + options, + result -> { + for (StorageItem item : result.getItems()) { + Log.i("MyAmplifyApp", "Item: " + item.getPath()); + } + Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); + }, + error -> Log.e("MyAmplifyApp", "List failure", error); +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") +val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) +val options = StoragePagedListOptions.builder() + .setPageSize(1000) + .bucket(secondBucket) + .build() + +Amplify.Storage.list(StoragePath.fromString("public/photos/"), options, + { result -> + result.items.forEach { item -> + Log.i("MyAmplifyApp", "Item: ${item.path}") + } + Log.i("MyAmplifyApp", "Next Token: ${result.nextToken}") + }, + { Log.e("MyAmplifyApp", "List failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") +val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) +val options = StoragePagedListOptions.builder() + .setPageSize(1000) + .bucket(secondBucket) + .build() + +try { + val result = Amplify.Storage.list(StoragePath.fromString("public/photos/"), options) + result.items.forEach { + Log.i("MyAmplifyApp", "Item: $it") + } + Log.i("MyAmplifyApp", "next token: ${result.nextToken}") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "List failure", error) +} +``` + +#### [RxJava] + +```java +BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); +StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); +StoragePagedListOptions options = StoragePagedListOptions.builder() + .setPageSize(1000) + .bucket(secondBucket) + .build(); + +RxAmplify.Storage.list(StoragePath.fromString("public/photos/"), options) + .subscribe( + result -> { + for (StorageItem item : result.getItems()) { + Log.i("MyAmplifyApp", "Item: " + item.getPath()); + } + Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); + }, + error -> Log.e("MyAmplifyApp", "List failure", error); + ); +``` + +### Exclude results from nested subpaths + +By default, the `list` API will return all objects contained within the given path, including objects inside nested subpaths. + +For example, the previous `public/photos/` path would include these objects: + +```bash +Path: public/photos/photo1.jpg +Path: public/photos/vacation/photo1.jpg +Path: public/photos/thumbnails/photo1.jpg +``` + +In order to exclude objects within the `vacation` and `thumbnails` subpaths, you can set the `subpathStrategy` option to `SubpathStrategy.Exclude()`: + +#### [Java] + +```java +StoragePagedListOptions options = StoragePagedListOptions.builder() + .setPageSize(1000) + .setSubpathStrategy(SubpathStrategy.Exclude()) + .build(); + +Amplify.Storage.list( + StoragePath.fromString("public/photos/"), + options, + result -> { + for (StorageItem item : result.getItems()) { + Log.i("MyAmplifyApp", "Item: " + item.getPath()); + } + Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); + }, + error -> Log.e("MyAmplifyApp", "List failure", error); +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val options = StoragePagedListOptions.builder() + .setPageSize(1000) + .setSubpathStrategy(SubpathStrategy.Exclude()) + .build() + +Amplify.Storage.list(StoragePath.fromString("public/photos/"), options, + { result -> + result.items.forEach { item -> + Log.i("MyAmplifyApp", "Item: ${item.path}") + } + Log.i("MyAmplifyApp", "Next Token: ${result.nextToken}") + }, + { Log.e("MyAmplifyApp", "List failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val options = StoragePagedListOptions.builder() + .setPageSize(1000) + .setSubpathStrategy(SubpathStrategy.Exclude()) + .build() + +try { + val result = Amplify.Storage.list(StoragePath.fromString("public/photos/"), options) + result.items.forEach { + Log.i("MyAmplifyApp", "Item: $it") + } + Log.i("MyAmplifyApp", "next token: ${result.nextToken}") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "List failure", error) +} +``` + +#### [RxJava] + +```java +StoragePagedListOptions options = StoragePagedListOptions.builder() + .setPageSize(1000) + .setSubpathStrategy(SubpathStrategy.Exclude()) + .build(); + +RxAmplify.Storage.list(StoragePath.fromString("public/photos/"), options) + .subscribe( + result -> { + for (StorageItem item : result.getItems()) { + Log.i("MyAmplifyApp", "Item: " + item.getPath()); + } + Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); + }, + error -> Log.e("MyAmplifyApp", "List failure", error); + ); +``` + +The response will only include objects within the `public/photos/` path and will also provide a list of the excluded subpaths: + +```bash +Path: public/photos/photo01.jpg +Subpath: public/photos/vacation/ +Subpath: public/photos/thumbnails/ +``` + +The default delimiter character is `"/"`, but this can be changed by supplying a custom delimiter: + +#### [Java] + +```java +StoragePagedListOptions options = StoragePagedListOptions.builder() + .setPageSize(1000) + .setSubpathStrategy(SubpathStrategy.Exclude("-")) + .build(); + +Amplify.Storage.list( + StoragePath.fromString("public/photos/"), + options, + result -> { + for (StorageItem item : result.getItems()) { + Log.i("MyAmplifyApp", "Item: " + item.getPath()); + } + Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); + }, + error -> Log.e("MyAmplifyApp", "List failure", error); +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val options = StoragePagedListOptions.builder() + .setPageSize(1000) + .setSubpathStrategy(SubpathStrategy.Exclude("-")) + .build() + +Amplify.Storage.list(StoragePath.fromString("public/photos/"), options, + { result -> + result.items.forEach { item -> + Log.i("MyAmplifyApp", "Item: ${item.path}") + } + Log.i("MyAmplifyApp", "Next Token: ${result.nextToken}") + }, + { Log.e("MyAmplifyApp", "List failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val options = StoragePagedListOptions.builder() + .setPageSize(1000) + .setSubpathStrategy(SubpathStrategy.Exclude("-")) + .build() + +try { + val result = Amplify.Storage.list(StoragePath.fromString("public/photos/"), options) + result.items.forEach { + Log.i("MyAmplifyApp", "Item: $it") + } + Log.i("MyAmplifyApp", "next token: ${result.nextToken}") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "List failure", error) +} +``` + +#### [RxJava] + +```java +StoragePagedListOptions options = StoragePagedListOptions.builder() + .setPageSize(1000) + .setSubpathStrategy(SubpathStrategy.Exclude("-")) + .build(); + +RxAmplify.Storage.list(StoragePath.fromString("public/photos/"), options) + .subscribe( + result -> { + for (StorageItem item : result.getItems()) { + Log.i("MyAmplifyApp", "Item: " + item.getPath()); + } + Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); + }, + error -> Log.e("MyAmplifyApp", "List failure", error); + ); +``` + +The response will only include objects within the `public/photos/` path not grouped by the delimiter `-`. + +```bash +Path: public/photos/2023/photos01.jpg +Path: public/photos/2024/photos02.jpg +Subpath: public/photos/202- +``` + +### All `list` options + +| Option | Type | Description | +| --- | --- | --- | +| subpathStrategy | SubpathStrategy | The strategy to use when listing contents from subpaths. | +| pageSize | int | Number between 1 and 1,000 that indicates the limit of how many entries to fetch when retrieving file lists from the server. | +| bucket | StorageBucket | The bucket in which the objects are stored. | +| nextToken | String | String indicating the page offset at which to resume a listing. | + +If the `pageSize` is set lower than the total file size available, a single `list` call only returns a subset of all the files. To list all the files with multiple calls, the user can use the `nextToken` value from the previous response. + + + +The following example lists all objects inside the `public/photos/` path: + +#### [Async/Await] + +```swift +let listResult = try await Amplify.Storage.list( + path: .fromString("public/photos/") +) +listResult.items.forEach { item in + print("Path: \(item.path)") +} +``` + +#### [Combine] + +```swift +let sink = Amplify.Publisher.create { + try await Amplify.Storage.list( + path: .fromString("public/photos/") + ) +}.sink { + if case let .failure(error) = $0 { + print("Failed: \(error)") + } +} +receiveValue: { listResult in + listResult.items.forEach { item in + print("Path: \(item.path)") + } +} +``` + + +Note the trailing slash `/` in the given path. + +If you had used `public/photos` as path, it would also match against files like `public/photos01.jpg`. + + +### List files from a specified bucket + +You can perform a list operation from a specific bucket by providing the `bucket` option. + +#### [From Outputs] +You can use `.fromOutputs(name:)` to provide a string representing the target bucket's assigned name in the Amplify Backend. + +```swift +let listResult = try await Amplify.Storage.list( + path: .fromString("public/photos/"), + options: .init( + bucket: .fromOutputs(name: "secondBucket") + ) +) +``` + +#### [From Bucket Info] +You can also use `.fromBucketInfo(_:)` to provide a bucket name and region directly. + +```swift +let listResult = try await Amplify.Storage.list( + path: .fromString("public/photos/"), + options: .init( + bucket: .fromBucketInfo(.init( + bucketName: "another-bucket-name", + region: "another-bucket-region") + ) + ) +) +``` + +### Exclude results from nested subpaths + +By default, the `list` API will return all objects contained within the given path, including objects inside nested subpaths. + +For example, the previous `public/photos/` path would include these objects: + +```bash +Path: public/photos/photo1.jpg +Path: public/photos/vacation/photo1.jpg +Path: public/photos/thumbnails/photo1.jpg +``` + +In order to exclude objects within the `vacation` and `thumbnails` subpaths, you can set the `subpathStrategy` option to `.exclude`: + +#### [Async/Await] + +```swift +let listResult = try await Amplify.Storage.list( + path: .fromString("public/photos/"), + options: .init( + subpathStrategy: .exclude + ) +) +listResult.items.forEach { item in + print("Path: \(item.path)") +} +listResult.excludedSubpaths.forEach { subpath in + print("Subpath: \(subpath)") +} +``` + +#### [Combine] + +```swift +let sink = Amplify.Publisher.create { + try await Amplify.Storage.list( + path: .fromString("public/photos/"), + options: .init( + subpathStrategy: .exclude + ) + ) +}.sink { + if case let .failure(error) = $0 { + print("Failed: \(error)") + } +} +receiveValue: { listResult in + listResult.items.forEach { item in + print("Path: \(item.path)") + } + listResult.excludedSubpaths.forEach { subpath in + print("Subpath: \(subpath)") + } +} +``` + +The response will only include objects within the `public/photos/` path and will also provide a list of the excluded subpaths: + +```bash +Path: public/photos/photo01.jpg +Subpath: public/photos/vacation/ +Subpath: public/photos/thumbnails/ +``` + +The default delimiter character is `"/"`, but this can be changed by supplying a custom delimiter: + +#### [Async/Await] + +```swift +let listResult = try await Amplify.Storage.list( + path: .fromString("public/photos-"), + options: .init( + subpathStrategy: .exclude(delimitedBy: "-") + ) +) +``` + +#### [Combine] + +```swift +let sink = Amplify.Publisher.create { + try await Amplify.Storage.list( + path: .fromString("public/photos-"), + options: .init( + subpathStrategy: .exclude(delimitedBy: "-") + ) + ) +}.sink { + if case let .failure(error) = $0 { + print("Failed: \(error)") + } +} +receiveValue: { listResult in + // ... +} +``` + +### All `list` options + +| Option | Type | Description | +| --- | --- | --- | +| subpathStrategy | SubpathStrategy | The strategy to use when listing contents from subpaths | +| pageSize | UInt | Number between 1 and 1,000 that indicates the limit of how many entries to fetch when retrieving file lists from the server | +| bucket | StorageBucket | The bucket in which the objects are stored | +| nextToken | String | String indicating the page offset at which to resume a listing. | + +If the `pageSize` is set lower than the total file size available, a single `list` call only returns a subset of all the files. To list all the files with multiple calls, the user can use the `nextToken` value from the previous response. + + + +This will list all files located under path `album` that: + +- have `private` access level +- are in the root of `album/` (the result doesn't include files under any sub path) + +```dart +Future listAlbum() async { + try { + String? nextToken; + bool hasNextPage; + do { + final result = await Amplify.Storage.list( + path: const StoragePath.fromString('public/album/'), + options: StorageListOptions( + pageSize: 50, + nextToken: nextToken, + pluginOptions: const S3ListPluginOptions( + excludeSubPaths: true, + ), + ), + ).result; + safePrint('Listed items: ${result.items}'); + nextToken = result.nextToken; + hasNextPage = result.hasNextPage; + } while (hasNextPage); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + +Pagination is enabled by default. The default `pageSize` is `1000` if it is not set in the `StorageListOptions`. + +### List all files without pagination + +You can also list all files under a given path without pagination by using the `pluginOptions` and `S3ListPluginOptions.listAll()` constructor. + +This will list all public files (i.e. those with `guest` access level): + +```dart + +Future listAllUnderPublicPath() async { + try { + final result = await Amplify.Storage.list( + path: const StoragePath.fromString('public/'), + options: const StorageListOptions( + pluginOptions: S3ListPluginOptions.listAll(), + ), + ).result; + safePrint('Listed items: ${result.items}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} + +``` + +### List files from a specified bucket +You can also perform a `list` operation to a specific bucket by providing the `bucket` option. You can pass in a `StorageBucket` object representing the target bucket from the name defined in the Amplify Backend. + +```dart +final result = await Amplify.Storage.list( + path: const StoragePath.fromString('path/to/dir'), + options: StorageListOptions( + // highlight-start + // Specify a target bucket using name assigned in Amplify Backend + bucket: StorageBucket.fromOutputs('secondBucket'), + // highlight-end + ), +).result; +``` +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. + +```dart +final result = await Amplify.Storage.list( + path: const StoragePath.fromString('path/to/dir'), + options: StorageListOptions( + // highlight-start + // Alternatively, provide bucket name from console and associated region + bucket: StorageBucket.fromBucketInfo( + BucketInfo( + bucketName: 'second-bucket-name-from-console', + region: 'us-east-2', + ), + ), + // highlight-end + ), +).result; +``` + + +### More `list` options + +| Option | Type | Description | +| --- | --- | --- | +| bucket | StorageBucket | The target bucket from the assigned name in the Amplify Backend or from the bucket name and region in the console

    Defaults to the default bucket and region from the Amplify configuration if this option is not provided.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | +| excludeSubPaths | boolean | Whether to exclude objects under the sub paths of the path to list. Defaults to false. | +| delimiter | String | The delimiter to use when evaluating sub paths. If excludeSubPaths is false, this value has no impact on behavior. | + + + +## Get File Properties + +You can also view the properties of an individual file. + +```javascript +import { getProperties } from 'aws-amplify/storage'; + +try { + const result = await getProperties({ + path: 'album/2024/1.jpg', + // Alternatively, path: ({ identityId }) => `album/${identityId}/1.jpg` + options: { + // Specify a target bucket using name assigned in Amplify Backend + bucket: 'assignedNameInAmplifyBackend' + } + }); + console.log('File Properties ', result); +} catch (error) { + console.log('Error ', error); +} +``` + +The properties and metadata will look similar to the below example + +```js +{ + path: "album/2024/1.jpg", + contentType: "image/jpeg", + contentLength: 6873, + eTag: "\"56b32cf4779ff6ca3ba3f2d455fa56a7\"", + lastModified: Wed Apr 19 2023 14:20:55 GMT-0700 (Pacific Daylight Time) {}, + metadata: { owner: 'aws' } +} +``` + +### More `getProperties` options + +Option | Type | Default | Description | +| -- | -- | -- | ----------- | +| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | +| useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint. | [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | + + + +To get the metadata in result for all APIs you have to configure user defined metadata in CORS. + +Learn more about how to setup an appropriate [CORS Policy](/[platform]/build-a-backend/storage/extend-s3-resources/#for-manually-configured-s3-resources). + + + + + +## Get File Properties + +You can also view properties of an individual file. + +```dart +Future getFileProperties() async { + try { + final result = await Amplify.Storage.getProperties( + path: const StoragePath.fromString('example.txt'), + ).result; + safePrint('File size: ${result.storageItem.size}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + +As well as specify a bucket to target to view properties of a file + +```dart +Future getFileProperties() async { + try { + final result = await Amplify.Storage.getProperties( + path: const StoragePath.fromString('example.txt'), + options: StorageGetPropertiesOptions( + StorageBucket.fromOutputs('secondBucket'), + ), + // Alternatively, provide bucket name from console and associated region + /* + options: StorageGetPropertiesOptions( + bucket: StorageBucket.fromBucketInfo( + BucketInfo( + bucketName: 'second-bucket-name-from-console', + region: 'us-east-2', + ), + ), + ), + */ + ).result; + safePrint('File size: ${result.storageItem.size}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + + + +To get the metadata in result for all APIs when building against a web target, you have to configure user defined metadata in CORS. + +Learn more about how to setup an appropriate [CORS Policy](/[platform]/build-a-backend/storage/extend-s3-resources/#for-manually-configured-s3-resources). + + + + +--- + +--- +title: "Remove files" +section: "build-a-backend/storage" +platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] +gen: 2 +last-updated: "2026-02-04T14:20:47.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/remove-files/" +--- + +Files can be removed from a storage bucket using the `remove` API. If a file is protected by an identity Id, only the user who owns the file will be able to remove it. + + +## Remove a single file + +You can also perform a remove operation from a specific bucket by providing the target bucket's assigned name from Amplify Backend in `bucket` option. + +```javascript +import { remove } from 'aws-amplify/storage'; + +try { + await remove({ + path: 'album/2024/1.jpg', + // Alternatively, path: ({identityId}) => `album/${identityId}/1.jpg` + bucket: 'assignedNameInAmplifyBackend', // Specify a target bucket using name assigned in Amplify Backend + }); +} catch (error) { + console.log('Error ', error); +} +``` + +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. + +```javascript +import { remove } from 'aws-amplify/storage'; + +try { + await remove({ + path: 'album/2024/1.jpg', + // Alternatively, provide bucket name from console and associated region + bucket: { + bucketName: 'bucket-name-from-console', + region: 'us-east-2' + } + + }); +} catch (error) { + console.log('Error ', error); +} +``` + + + +## Remove folders + +You can remove entire folders and their contents by providing a folder path. The remove API will automatically detect folders and perform batch deletion of all contained files. + +```javascript +import { remove } from 'aws-amplify/storage'; + +try { + await remove({ + path: 'album/2024/' + }); +} catch (error) { + console.error(error); +} +``` + +### Folder deletion with progress tracking + +For large folders, you can track the deletion progress and handle any failures: + +```javascript +import { remove } from 'aws-amplify/storage'; + +try { + const result = await remove({ + path: 'large-folder/', + options: { + onProgress: (fileBatch) => { + console.log(fileBatch); + } + } + }); + + console.log('Success', result); +} catch (error) { + console.log('Error during folder deletion:', error); +} +``` + +### Cancellable folder deletion + +You can cancel folder deletion operations, useful for user-initiated cancellations or when navigating away from a page. + +When you call `remove()` without `await`, it returns a cancellable operation object with a `result` property and `cancel()` method. This differs from `await remove()` which directly returns the result but cannot be cancelled. + +```javascript +import { remove } from 'aws-amplify/storage'; + +let deleteOperation; + +// Start deletion when user clicks delete button +function handleDeleteFolder() { + // remove() returns { result: Promise, cancel: Function } + deleteOperation = remove({ + path: 'user-uploads/large-dataset/', + options: { + onProgress: (fileBatch) => { + updateProgressBar(fileBatch.deleted?.length || 0); + } + } + }); + + // Access the promise via .result property + deleteOperation.result.then(result => { + console.log('Success', result); + }).catch(error => { + if (error.name === 'CanceledError') { + console.log('Deletion cancelled by user'); + } else { + console.log('Error:', error); + } + }); +} + +// Cancel when user clicks cancel or navigates away +function handleCancel() { + if (deleteOperation) { + deleteOperation.cancel(); + } +} +``` + + + + +#### [Java] + +```java +Amplify.Storage.remove( + StoragePath.fromString("public/myUploadedFileName.txt"), + result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Remove failure", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), + { Log.i("MyAmplifyApp", "Successfully removed: ${it.path}") }, + { Log.e("MyAmplifyApp", "Remove failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +try { + val result = Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt")) + Log.i("MyAmplifyApp", "Successfully removed: ${result.path}") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "Remove failure", error) +} +``` + +#### [RxJava] + +```java +RxAmplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt")) + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Remove failure", error) + ); +``` + +## Remove files from a specified bucket + +You can also perform a remove operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. + +#### [Java] + +```java +StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); +StorageRemoveOptions options = StorageRemoveOptions.builder() + .bucket(secondBucket) + .build(); + +Amplify.Storage.remove( + StoragePath.fromString("public/myUploadedFileName.txt"), + options, + result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Remove failure", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val secondBucket = StorageBucket.fromOutputs("secondBucket") +val options = StorageRemoveOptions.builder() + .bucket(secondBucket) + .build() + +Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options, + { Log.i("MyAmplifyApp", "Successfully removed: ${it.path}") }, + { Log.e("MyAmplifyApp", "Remove failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val secondBucket = StorageBucket.fromOutputs("secondBucket") +val options = StorageRemoveOptions.builder() + .bucket(secondBucket) + .build() + +try { + val result = Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options) + Log.i("MyAmplifyApp", "Successfully removed: ${result.path}") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "Remove failure", error) +} +``` + +#### [RxJava] + +```java +StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); +StorageRemoveOptions options = StorageRemoveOptions.builder() + .bucket(secondBucket) + .build(); +RxAmplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options) + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Remove failure", error) + ); +``` + +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. + +#### [Java] + +```java +BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); +StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); +StorageRemoveOptions options = StorageRemoveOptions.builder() + .bucket(secondBucket) + .build(); + +Amplify.Storage.remove( + StoragePath.fromString("public/myUploadedFileName.txt"), + options, + result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Remove failure", error) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") +val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) +val options = StorageRemoveOptions.builder() + .bucket(secondBucket) + .build() + +Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options, + { Log.i("MyAmplifyApp", "Successfully removed: ${it.path}") }, + { Log.e("MyAmplifyApp", "Remove failure", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") +val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) +val options = StorageRemoveOptions.builder() + .bucket(secondBucket) + .build() + +try { + val result = Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options) + Log.i("MyAmplifyApp", "Successfully removed: ${result.path}") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "Remove failure", error) +} +``` + +#### [RxJava] + +```java +BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); +StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); +StorageRemoveOptions options = StorageRemoveOptions.builder() + .bucket(secondBucket) + .build(); + +RxAmplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options) + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Remove failure", error) + ); +``` + + + + + +#### [Async/Await] + +```swift +let removedObject = try await Amplify.Storage.remove( + path: .fromString("public/example/path") +) +print("Deleted \(removedObject)") +``` + +#### [Combine] + +```swift +let sink = Amplify.Publisher.create { + try await Amplify.Storage.remove( + path: .fromString("public/example/path") + ) +}.sink { + if case let .failure(error) = $0 { + print("Failed: \(error)") + } +} +receiveValue: { removedObject in + print("Deleted \(removedObject)") +} +``` + +## Remove files from a specified bucket + +You can perform a remove operation from a specific bucket by providing the `bucket` option. + +#### [From Outputs] +You can use `.fromOutputs(name:)` to provide a string representing the target bucket's assigned name in the Amplify Backend. + +```swift +let removedObject = try await Amplify.Storage.remove( + path: .fromString("public/example/path"), + options: .init( + bucket: .fromOutputs(name: "secondBucket") + ) +) +``` + +#### [From Bucket Info] +You can also use `.fromBucketInfo(_:)` to provide a bucket name and region directly. + +```swift +let removedObject = try await Amplify.Storage.remove( + path: .fromString("public/example/path"), + options: .init( + bucket: .fromBucketInfo(.init( + bucketName: "another-bucket-name", + region: "another-bucket-region") + ) + ) +) +``` + + + + +You can also perform a `remove` operation to a specific bucket by providing the `bucket` option. You can pass in a `StorageBucket` object representing the target bucket from the name defined in the Amplify Backend. + +```dart +final result = await Amplify.Storage.remove( + path: const StoragePath.fromString('path/to/file.txt'), + options: StorageRemoveOptions( + // highlight-start + // Specify a target bucket using name assigned in Amplify Backend + bucket: StorageBucket.fromOutputs('secondBucket'), + // highlight-end + ), +).result; +``` + +Alternatively, you can also pass in an object by specifying the bucket name and region from the console. + +```dart +final result = await Amplify.Storage.remove( + path: const StoragePath.fromString('path/to/file.txt'), + options: StorageRemoveOption( + // highlight-start + // Alternatively, provide bucket name from console and associated region + bucket: StorageBucket.fromBucketInfo( + BucketInfo( + bucketName: 'second-bucket-name-from-console', + region: 'us-east-2', + ), + ), + // highlight-end + ), +).result; +``` + +## Remove multiple Files + +You can remove multiple files using `Amplify.Storage.removeMany`, as well as specify a bucket to target, the files to be removed in a batch should have the same access level: + +```dart +Future remove() async { + try { + final result = await Amplify.Storage.removeMany( + paths: [ + const StoragePath.fromString('public/file-1.txt'), + const StoragePath.fromString('public/file-2.txt'), + ], + // if this option is not provided, the default bucket in the Amplify Backend will be used + options: StorageRemoveManyOptions( + bucket: StorageBucket.fromOutputs('secondBucket'), + /* Alternatively, provide bucket name from console and associated region + bucket: StorageBucket.fromBucketInfo( + BucketInfo( + bucketName: 'second-bucket-name-from-console', + region: 'us-east-2', + ), + ), + */ + ), + ).result; + safePrint('Removed files: ${result.removedItems}'); + } on StorageException catch (e) { + safePrint(e.message); + } +} +``` + + + +## More `remove` options + +Option | Type | Default | Description | +| -- | :--: | :--: | ----------- | +| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | +| onProgress | (fileBatch: \{
    deleted?: \{path: string\}[];
    failed?: \{path: string; code: string; message: string\}[];
    \}) => void | Optional | Callback function for tracking folder deletion progress. Called after each batch of files is processed during folder deletion operations. | + + +--- + +--- +title: "Copy files" +section: "build-a-backend/storage" +platforms: ["angular", "javascript", "nextjs", "react", "vue", "flutter", "react-native"] +gen: 2 +last-updated: "2025-07-04T12:58:35.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/copy-files/" +--- + + + +**Note:** You can only copy files up to 5GB in a single operation + + + +You can copy an existing file to a different path within the storage bucket using the copy API. + + +The `copy` method duplicates an existing file to a designated path and returns an object `{path: 'destPath'}` upon successful completion. + +```javascript +import { copy } from 'aws-amplify/storage'; + +const copyFile = async () => { + try { + const response = await copy({ + source: { + path: `album/2024/${encodeURIComponent('#1.jpg')}`, + // Alternatively, path: ({identityId}) => `album/${identityId}/${encodeURIComponent('#1.jpg')` + }, + destination: { + path: 'shared/2024/#1.jpg', + // Alternatively, path: ({identityId}) => `shared/${identityId}/#1.jpg` + }, + }); + } catch (error) { + console.error('Error', err); + } +}; +``` + + +The operation can fail if there's a special character in the `source` path. You should URI encode the source +path with special character. You **don't** need to encode the `destination` path. + + + + + +Cross identity ID copying is only allowed if the destination path has the the right access rules to allow other authenticated users writing to it. + + + +## Specify a bucket or copy across buckets / regions + +You can also perform an `copy` operation to a specific bucket by providing the `bucket` option. This option can either be a string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console. + +```javascript +import { copy } from 'aws-amplify/storage'; + +const copyFile = async () => { + try { + const response = await copy({ + source: { + path: 'album/2024/1.jpg', + // Specify a target bucket using name assigned in Amplify Backend + // or bucket name from console and associated region + bucket: 'assignedNameInAmplifyBackend', + expectedBucketOwner: '123456789012' + }, + destination: { + path: 'shared/2024/1.jpg', + // Specify a target bucket using name assigned in Amplify Backend + // or bucket name from console and associated region + bucket: { + bucketName: 'generated-second-bucket-name', + region: 'us-east-2' + }, + expectedBucketOwner: '123456789013' + } + }); + } catch (error) { + console.error('Error', error); + } +}; +``` + + +In order to copy to or from a bucket other than your default, both source and destination must have `bucket` explicitly defined. + + +## Copy `source` and `destination` options + +Option | Type | Default | Description | +| -- | :--: | :--: | ----------- | +| path | string \|
    (\{ identityId \}) => string | Required | A string or callback that represents the path in source and destination bucket to copy the object to or from.
    **Each segment of the path in `source` must by URI encoded.** | +| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets). | +| eTag | string | Optional | The copy **source object** entity tag (ETag) value. Only Copies the object if its ETag matches the specified tag. | +| notModifiedSince | Date | Optional | Copies the **source object** if it hasn't been modified since the specified time.

    **This is evaluated only when `eTag` is NOT supplied**| +| expectedBucketOwner | string | Optional | `source.expectedBucketOwner`: The account ID that owns the source bucket.

    `destination.expectedBucketOwner`: The account ID that owns the destination bucket. | + + + +User who initiates a copy operation should have read permission on the copy source file. + +```dart +Future copy() async { + try { + final result = await Amplify.Storage.copy( + source: const StoragePath.fromString('album/2024/1.jpg'), + destination: const StoragePath.fromString('shared/2024/1.jpg'), + ).result; + safePrint('Copied file: ${result.copiedItem.path}'); + } on StorageException catch (e) { + safePrint(e); + } +} +``` +## Specify a bucket or copy across buckets / regions + +You can also perform a `copy` operation to a specific bucket by providing the `CopyBuckets` option. +This option is an object that takes two `StorageBucket` parameters, which can be constructed by the specified name in the Amplify Backend, or the bucket name and region from the console. + +```dart +final mainBucket = StorageBucket.fromOutputs( + 'mainBucket', +); +final bucket2 = StorageBucket.fromBucketInfo( + BucketInfo( + bucketName: 'second-bucket-name-from-console', + region: 'us-east-2', + ), +), +try { + final result = await Amplify.Storage.copy( + source: const StoragePath.fromString('album/2024/1.jpg'), + destination: const StoragePath.fromString('shared/2024/1.jpg'), + options: StorageCopyOptions( + buckets: CopyBuckets( + source: bucket1, + destination: bucket2, + ), + ), + ).result; + safePrint('Copied file: ${result.copiedItem.path}'); +} on StorageException catch (e) { + print('Error: $e'); +} +``` + + +In order to copy to or from a bucket other than your default, the source and/or destination paths must exist in that bucket + + +## `copy` options + +Option | Type | Description | +| -- | -- | ----------- | +| getProperties | boolean | Whether to retrieve properties for the copied object using theAmplify.Storage.getProperties() after the operation completes. When set to true the returned item will contain additional info such as metadata and content type. | +| buckets | CopyBuckets | An object that accepts two `StorageBucket` parameters. To copy to and from the same bucket, use the `CopyBuckets.sameBucket(targetBucket)` method, where `targetBucket` is a `StorageBucket`. Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets)| + +Example of `copy` with options: + +```dart +final result = Amplify.Storage.copy( + source: const StoragePath.fromString('album/2024/1.jpg'), + destination: const StoragePath.fromString('shared/2024/1.jpg'), + options: const StorageCopyOptions( + pluginOptions: S3CopyPluginOptions( + getProperties: true, + ), + buckets: CopyBuckets.sameBucket( + StorageBucket.fromOutputs('secondBucket'), + ), + ), +); +``` + + +--- + +--- +title: "Listen to storage events" +section: "build-a-backend/storage" +platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] +gen: 2 +last-updated: "2024-10-07T19:55:17.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/lambda-triggers/" +--- + +Function triggers can be configured to enable event-based workflows when files are uploaded or deleted. To add a function trigger, modify the `defineStorage` configuration. + +First, in your storage definition, add the following: + +```ts title="amplify/storage/resource.ts" +export const storage = defineStorage({ + name: 'myProjectFiles', + // highlight-start + triggers: { + onUpload: defineFunction({ + entry: './on-upload-handler.ts' + }), + onDelete: defineFunction({ + entry: './on-delete-handler.ts' + }) + } + // highlight-end +}); +``` + +Then create the function definitions at `amplify/storage/on-upload-handler.ts` and `amplify/storage/on-delete-handler.ts`. + +```ts title="amplify/storage/on-upload-handler.ts" +import type { S3Handler } from 'aws-lambda'; + +export const handler: S3Handler = async (event) => { + const objectKeys = event.Records.map((record) => record.s3.object.key); + console.log(`Upload handler invoked for objects [${objectKeys.join(', ')}]`); +}; +``` + +```ts title="amplify/storage/on-delete-handler.ts" +import type { S3Handler } from 'aws-lambda'; + +export const handler: S3Handler = async (event) => { + const objectKeys = event.Records.map((record) => record.s3.object.key); + console.log(`Delete handler invoked for objects [${objectKeys.join(', ')}]`); +}; +``` + +> **Info:** **Note:** The `S3Handler` type comes from the [`@types/aws-lambda`](https://www.npmjs.com/package/@types/aws-lambda) npm package. This package contains types for different kinds of Lambda handlers, events, and responses. + +Now, when you deploy your backend, these functions will be invoked whenever an object is uploaded or deleted from the bucket. + +## More Advanced Triggers + +The example listed above demonstrates what is exposed directly in your `storage` definition. Specifically, the use of the `triggers` option when you use `defineStorage`. This method is for simple triggers that always execute on file uploads or file deletions. There are no additional modifications you can make to the triggers defined in this way. + +If you want the ability to do something more than simply handle the events `onUpload` and `onDelete` you will have to use `.addEventNotification` in your `backend.ts`. If you use this method, the `triggers` section in your `storage/resource.ts` file should be removed. + +Here is an example of how you can add a Lambda trigger for an S3 object PUT event. This trigger will execute when a file that has been uploaded to the bucket defined in your `storage/resource.ts` has a matching prefix and suffix as that listed in the function input of `addEventNotification`. + +```ts title="amplify/backend.ts" +import { EventType } from 'aws-cdk-lib/aws-s3'; +import { LambdaDestination } from 'aws-cdk-lib/aws-s3-notifications'; +import { defineBackend } from '@aws-amplify/backend'; +import { storage } from './storage/resource'; +import { yourLambda } from './functions/yourLambda/resource'; + +const backend = defineBackend({ + storage, + yourLambda, +}); + +backend.storage.resources.bucket.addEventNotification( + EventType.OBJECT_CREATED_PUT, + new LambdaDestination(backend.yourLambda.resources.lambda), + { + prefix: 'protected/uploads/', + suffix: '-uploadManifest.json', + } +); +``` + +It's important to note that using this methodology does not require any changes your lambda function. This modification on your `backend.ts` file will create a new `AWS CloudFormation handler for "Custom::S3BucketNotifications" resources (@aws-cdk/aws-s3)` that specifically handles checking the prefix and suffix. + +--- + +--- +title: "Extend S3 resources" +section: "build-a-backend/storage" +platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] +gen: 2 +last-updated: "2024-05-21T17:56:46.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/extend-s3-resources/" +--- + +## For Amplify-generated S3 resources + +Amplify Storage generates Amazon S3 resources to offer storage features. You can access the underlying Amazon S3 resources to further customize your backend configuration by using the AWS Cloud Developer Kit (AWS CDK). + +### Example - Enable Transfer Acceleration + +The following is an example of how you would enable Transfer Acceleration on the bucket ([CDK documentation](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.CfnBucket.AccelerateConfigurationProperty.html)). In order to enable Transfer Acceleration on the bucket, you will have to unwrap the L1 CDK construct from the L2 CDK construct like the following. + +```tsx +// highlight-next-line +import * as s3 from 'aws-cdk-lib/aws-s3'; +import { defineBackend } from '@aws-amplify/backend'; +import { storage } from './storage/resource'; + +const backend = defineBackend({ + storage +}); + +// highlight-start +const s3Bucket = backend.storage.resources.bucket; + +const cfnBucket = s3Bucket.node.defaultChild as s3.CfnBucket; + +cfnBucket.accelerateConfiguration = { + accelerationStatus: "Enabled" // 'Suspended' if you want to disable transfer acceleration +} +// highlight-end +``` + +### Upload files using the accelerated S3 endpoint + +We switch to the accelerated S3 endpoint by using the `useAccelerateEndpoint` parameter set to `true` in the `AWSS3StorageUploadFileOptions`. + +#### [Java] + +```java +AWSS3StorageUploadFileOptions awsS3StorageUploadFileOptions = + AWSS3StorageUploadFileOptions.builder().setUseAccelerateEndpoint(true).build(); + Amplify.Storage.uploadFile( + StoragePath.fromString("public/example"), + file + awsS3StorageUploadFileOptions, + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) +); +``` + +#### [Kotlin - Callbacks] + +```kotlin +val awsS3StorageUploadFileOptions = AWSS3StorageUploadFileOptions.builder(). + setUseAccelerateEndpoint(true). + build() + Amplify.Storage.uploadFile( + StoragePath.fromString("public/example"), + file + awsS3StorageUploadFileOptions, + { Log.i("MyAmplifyApp", "Successfully uploaded: " + it.getPath()) }, + { Log.e("MyAmplifyApp", "Upload failed", it) } +) +``` + +#### [Kotlin - Coroutines] + +```kotlin +val awsS3StorageUploadFileOptions = AWSS3StorageUploadFileOptions.builder(). + setUseAccelerateEndpoint(true). + build() +val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), file, awsS3StorageUploadFileOptions) +try { + val result = upload.result() + Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") +} catch (error: StorageException) { + Log.e("MyAmplifyApp", "Upload failed", error) +} + +``` + +#### [RxJava] + +```java +AWSS3StorageUploadFileOptions awsS3StorageUploadFileOptions = + AWSS3StorageUploadFileOptions.builder().setUseAccelerateEndpoint(true).build(); +RxProgressAwareSingleOperation rxUploadOperation = + RxAmplify.Storage.uploadFile(StoragePath.fromString("public/example"), file, awsS3StorageUploadFileOptions); +rxUploadOperation + .observeResult() + .subscribe( + result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), + error -> Log.e("MyAmplifyApp", "Upload failed", error) + ); + +``` + + + + +### Upload files using the accelerated S3 endpoint + +You can use transfer acceleration by setting `"useAccelerateEndpoint"` to `true` in the corresponding `pluginOptions` for any of the following Storage APIs: +- `getUrl(key:options:)` +- `downloadData(key:options:)` +- `downloadFile(key:local:options:)` +- `uploadData(key:data:options:)` +- `uploadFile(key:local:options:)` + +For example, to upload a file using transfer acceleration: + +```swift +let uploadTask = Amplify.Storage.uploadFile( + key: aKey, + local: aLocalFile, + options: .init( + pluginOptions: [ + "useAccelerateEndpoint": true + ] + ) +) + +let data = try await uploadTask.value +``` + + + +### Upload files using the accelerated S3 endpoint + +You can use transfer acceleration when calling the following APIs: + +* `getUrl` +* `downloadData` +* `downloadFile` +* `uploadData` +* `uploadFile` + +Set `useAccelerateEndpoint` to `true` in the corresponding Storage S3 plugin options to apply an accelerated S3 endpoint to the operation. For example, upload a file using transfer acceleration: + +```dart +import 'package:amplify_storage_s3/amplify_storage_s3.dart'; + +Future uploadFileUsingAcceleration(String filePath, String key) async { + final localFile = AWSFile.fromPath(filePath); + try { + final uploadFileOperation = Amplify.Storage.uploadFile( + localFile: localFile, + key: key, + options: const StorageUploadFileOptions( + pluginOptions: S3UploadFilePluginOptions( + useAccelerateEndpoint: true, + ), + ), + ); + + final result = await uploadFileOperation.result; + safePrint('Uploaded file: ${result.uploadedItem.key}'); + } on StorageException catch (error) { + safePrint('Something went wrong uploading file: ${error.message}'); + } +} +``` + + +Read more about [escape hatches in the CDK](https://docs.aws.amazon.com/cdk/v2/guide/cfn_layer.html#develop-customize-escape). + + +## For Manually configured S3 resources + + +Follow this guide if you are building against a web target. + + +> **Warning:** To make calls to your S3 bucket from your App, you need to set up a CORS Policy for your S3 bucket. This callout is only for manual configuration of your S3 bucket. + +The following steps will set up your CORS Policy: + +1. Go to [Amazon S3 console](https://s3.console.aws.amazon.com/s3/home?region=us-east-1) and click on your project's `userfiles` bucket, which is normally named as [Bucket Name][Id]-dev. ![Go to [Amazon S3 Console]](/images/storage/CORS1.png) +2. Click on the **Permissions** tab for your bucket. ![Click on the **Permissions** tab for your bucket](/images/storage/CORS2.png) +3. Click the edit button in the **Cross-origin resource sharing (CORS)** section. ![Click the edit button in the **Cross-origin resource sharing (CORS)** section](/images/storage/CORS3.png) +4. Make the Changes and click on Save Changes. You can add required metadata to be exposed in `ExposeHeaders` with `x-amz-meta-XXXX` format. ![Click on Save Changes:](/images/storage/CORS4.png) + +```json +[ + { + "AllowedHeaders": ["*"], + "AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"], + "AllowedOrigins": ["*"], + "ExposeHeaders": [ + "x-amz-server-side-encryption", + "x-amz-request-id", + "x-amz-id-2", + "ETag", + "x-amz-meta-foo" + ], + "MaxAgeSeconds": 3000 + } +] +``` + + + +**Note:** You can restrict the access to your bucket by updating AllowedOrigin to include individual domains. + + + + +--- + +--- +title: "Use AWS SDK for S3 APIs" +section: "build-a-backend/storage" +platforms: ["android", "swift"] +gen: 2 +last-updated: "2024-09-24T23:57:23.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/use-aws-sdk/" +--- + +For advanced use cases where Amplify does not provide the functionality, you can retrieve the escape hatch to access the `S3Client` instance: + + + +#### [Java] + + + +Learn more about consuming Kotlin clients from Java using either a blocking interface or an equivalent async interface based on futures [here](https://github.com/awslabs/smithy-kotlin/blob/main/docs/design/kotlin-smithy-sdk.md#java-interop). + + + +```java +AWSS3StoragePlugin plugin = (AWSS3StoragePlugin) Amplify.Storage.getPlugin("awsS3StoragePlugin"); +S3Client client = plugin.getEscapeHatch(); +``` + +#### [Kotlin] + +```kotlin +val plugin = Amplify.Storage.getPlugin("awsS3StoragePlugin") as AWSS3StoragePlugin +val client = plugin.escapeHatch +``` + + + + +Add the following import: + +```swift +import AWSS3StoragePlugin +``` + +Then retrieve the escape hatch with this code: + +```swift +do { + // Retrieve the reference to AWSS3StoragePlugin + let plugin = try Amplify.Storage.getPlugin(for: "awsS3StoragePlugin") + guard let storagePlugin = plugin as? AWSS3StoragePlugin else { + return + } + + // Retrieve the reference to S3Client + let s3Client = storagePlugin.getEscapeHatch() + + // Make requests using s3Client... + // ... +} catch { + print("Get escape hatch failed with error - \(error)") +} +``` + +For additional client documentation, see the [AWS SDK for Swift Client documentation](https://docs.aws.amazon.com/sdk-for-swift/latest/developer-guide/using-client-services.html). For S3Client code examples, see the [Amazon S3 examples using SDK for Swift](https://docs.aws.amazon.com/sdk-for-swift/latest/developer-guide/swift_s3_code_examples.html) + + +--- + +--- +title: "Use Amplify Storage with any S3 bucket" +section: "build-a-backend/storage" +platforms: ["javascript", "react", "react-native", "angular", "vue", "nextjs", "swift", "android", "flutter"] +gen: 2 +last-updated: "2025-04-21T11:34:09.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/use-with-custom-s3/" +--- + +With Amplify Storage APIs, you can use your own S3 buckets instead of the Amplify-created ones. + + +**Important:** To utilize the storage APIs with an S3 bucket outside of Amplify, you must have Amplify Auth configured in your project. + +## Use storage resources with an Amplify backend + +### Add necessary permissions to the S3 bucket + +For the specific Amazon S3 bucket that you want to use with these APIs, you need to make sure that the associated IAM role has the necessary permissions to read and write data to that bucket. + +To do this, go to **Amazon S3 console** > **Select the S3 bucket** > **Permissions** > **Edit** Bucket Policy. + +![Showing Amplify console showing Storage tab selected](/images/gen2/storage/s3-console-permissions.png) + +The policy will look something like this: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Statement1", + "Principal": { + "AWS": "arn:aws:iam:::role/" + }, + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject", + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::", + "arn:aws:s3:::/*" + ] + } + ] +} +``` +Replace `` with your AWS account ID and `` with the IAM role associated with your Amplify Auth setup. Replace `` with the S3 bucket name. + +You can refer to [Amazon S3's Policies and Permissions documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-policy-language-overview.html) for more ways to customize access to the bucket. + +> **Warning:** In order to make calls to your manually configured S3 bucket from your application, you must also set up a [CORS Policy](/[platform]/build-a-backend/storage/extend-s3-resources/#for-manually-configured-s3-resources) for the bucket. + +### Specify the S3 bucket in Amplify's backend config + +Next, use the `addOutput` method from the backend definition object to define a custom S3 bucket by specifying the name and region of the bucket in your `amplify/backend.ts` file. You must also set up the appropriate resources and IAM policies to be attached to the backend. + + +**Important:** You can use a storage backend configured through Amplify and a custom S3 bucket at the same time using this method. However, the Amplify-configured storage will be used as the **default bucket** and the custom S3 bucket will only be used as an additional bucket. + + +#### Configure the S3 bucket + +Below are several examples of configuring the backend to define a custom S3 bucket: + +#### [Guest Users] +Below is an example of expanding the original backend object to grant all guest (i.e. not signed in) users read access to files under `public/`: + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend"; +import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam"; +import { Bucket } from "aws-cdk-lib/aws-s3"; +import { auth } from "./auth/resource"; + +const backend = defineBackend({ + auth, +}); +// highlight-start +const customBucketStack = backend.createStack("custom-bucket-stack"); + +// Import existing bucket +const customBucket = Bucket.fromBucketAttributes(customBucketStack, "MyCustomBucket", { + bucketArn: "arn:aws:s3:::", + region: "" +}); + +backend.addOutput({ + storage: { + aws_region: customBucket.env.region, + bucket_name: customBucket.bucketName, + // optional: `buckets` can be used when setting up more than one existing bucket + buckets: [ + { + aws_region: customBucket.env.region, + bucket_name: customBucket.bucketName, + name: customBucket.bucketName, + /* + optional: `paths` can be used to set up access to specific + bucket prefixes and configure user access types to them + */ + paths: { + "public/*": { + // "write" and "delete" can also be added depending on your use case + guest: ["get", "list"], + }, + }, + } + ] + }, +}); + +/* + Define an inline policy to attach to Amplify's unauth role + This policy defines how unauthenticated/guest users can access your existing bucket +*/ +const unauthPolicy = new Policy(backend.stack, "customBucketUnauthPolicy", { + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["s3:GetObject"], + resources: [`${customBucket.bucketArn}/public/*`], + }), + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["s3:ListBucket"], + resources: [ + `${customBucket.bucketArn}`, + `${customBucket.bucketArn}/*` + ], + conditions: { + StringLike: { + "s3:prefix": ["public/", "public/*"], + }, + }, + }), + ], +}); + +// Add the policies to the unauthenticated user role +backend.auth.resources.unauthenticatedUserIamRole.attachInlinePolicy( + unauthPolicy, +); +// highlight-end +``` + +#### [Authenticated Users] +Below is an example of expanding the original backend object to grant all authenticated (i.e. signed in) users with full access to files under `public/`: +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend"; +import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam"; +import { Bucket } from "aws-cdk-lib/aws-s3"; +import { auth } from "./auth/resource"; + +const backend = defineBackend({ + auth, +}); + +const customBucketStack = backend.createStack("custom-bucket-stack"); + +// Import existing bucket +const customBucket = Bucket.fromBucketAttributes(customBucketStack, "MyCustomBucket", { + bucketArn: "arn:aws:s3:::", + region: "" +}); + +backend.addOutput({ + storage: { + aws_region: customBucket.env.region, + bucket_name: customBucket.bucketName, + buckets: [ + { + aws_region: customBucket.env.region, + bucket_name: customBucket.bucketName, + name: customBucket.bucketName, + paths: { + "public/*": { + guest: ["get", "list"], + // highlight-start + authenticated: ["get", "list", "write", "delete"], + // highlight-end + }, + }, + } + ] + }, +}); + +// ... Unauthenticated/guest user policies and role attachments go here ... +// highlight-start +/* + Define an inline policy to attach to Amplify's auth role + This policy defines how authenticated users can access your existing bucket +*/ +const authPolicy = new Policy(backend.stack, "customBucketAuthPolicy", { + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ], + resources: [`${customBucket.bucketArn}/public/*`,], + }), + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["s3:ListBucket"], + resources: [ + `${customBucket.bucketArn}`, + `${customBucket.bucketArn}/*` + ], + conditions: { + StringLike: { + "s3:prefix": ["public/*", "public/"], + }, + }, + }), + ], +}); + +// Add the policies to the authenticated user role +backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy(authPolicy); +// highlight-end +``` + +#### [User Groups] +Below is an example of expanding the original backend object with user group permissions. Here, any authenticated users can read from `admin/` and `public/` and authenticated users belonging to the "admin" user group can only manage `admin/`: + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend"; +import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam"; +import { Bucket } from "aws-cdk-lib/aws-s3"; +import { auth } from "./auth/resource"; + +const backend = defineBackend({ + auth, +}); + +const customBucketStack = backend.createStack("custom-bucket-stack"); + +// Import existing bucket +const customBucket = Bucket.fromBucketAttributes(customBucketStack, "MyCustomBucket", { + bucketArn: "arn:aws:s3:::", + region: "" +}); + +backend.addOutput({ + storage: { + aws_region: customBucket.env.region, + bucket_name: customBucket.bucketName, + buckets: [ + { + aws_region: customBucket.env.region, + bucket_name: customBucket.bucketName, + name: customBucket.bucketName, + /* + @ts-expect-error: Amplify backend type issue + https://github.com/aws-amplify/amplify-backend/issues/2569 + */ + paths: { + "public/*": { + authenticated: ["get", "list", "write", "delete"], + }, + // highlight-start + "admin/*": { + authenticated: ["get", "list"], + groupsadmin: ["get", "list", "write", "delete"], + }, + // highlight-end + }, + } + ] + }, +}); + +// ... Authenticated user policy and role attachment goes here ... +// highlight-start +/* + Define an inline policy to attach to "admin" user group role + This policy defines how authenticated users with + "admin" user group role can access your existing bucket +*/ +const adminPolicy = new Policy(backend.stack, "customBucketAdminPolicy", { + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ], + resources: [ `${customBucket.bucketArn}/admin/*`], + }), + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["s3:ListBucket"], + resources: [ + `${customBucket.bucketArn}` + `${customBucket.bucketArn}/*` + ], + conditions: { + StringLike: { + "s3:prefix": ["admin/*", "admin/"], + }, + }, + }), + ], +}); + +// Add the policies to the "admin" user group role +backend.auth.resources.groups["admin"].role.attachInlinePolicy(adminPolicy); +// highlight-end +``` + +#### [Owners] +Amplify allows scoping file access to individual users via the user's identity ID. To specify the user's identity ID, you can use the token `${cognito-identity.amazonaws.com:sub}`. + +Below is an example of expanding the original backend object to define read access for guests to the `public/` folder, as well as defining a `protected/` folder where anyone can view uploaded files, but only the file owner can modify/delete them: + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend"; +import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam"; +import { Bucket } from "aws-cdk-lib/aws-s3"; +import { auth } from "./auth/resource"; + +const backend = defineBackend({ + auth, +}); + +const customBucketStack = backend.createStack("custom-bucket-stack"); + +// Import existing bucket +const customBucket = Bucket.fromBucketAttributes(customBucketStack, "MyCustomBucket", { + bucketArn: "arn:aws:s3:::", + region: "" +}); + +backend.addOutput({ + storage: { + aws_region: customBucket.env.region, + bucket_name: customBucket.bucketName, + buckets: [ + { + aws_region: customBucket.env.region, + bucket_name: customBucket.bucketName, + name: customBucket.bucketName, + /* + @ts-expect-error: Amplify backend type issue + https://github.com/aws-amplify/amplify-backend/issues/2569 + */ + paths: { + "public/*": { + guest: ["get", "list"], + authenticated: ["get", "list", "write", "delete"], + }, + // highlight-start + // allow all users to view all folders/files within `protected/` + "protected/*": { + guest: ["get", "list"], + authenticated: ["get", "list"], + }, + // allow owners to read, write and delete their own files in assigned subfolder + "protected/${cognito-identity.amazonaws.com:sub}/*": { + entityidentity: ["get", "list", "write", "delete"] + } + // highlight-end + }, + } + ] + }, +}); +// highlight-start +/* + Define an inline policy to attach to Amplify's unauth role + This policy defines how unauthenticated users/guests + can access your existing bucket +*/ +const unauthPolicy = new Policy(backend.stack, "customBucketUnauthPolicy", { + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["s3:GetObject"], + resources: [ + `${customBucket.bucketArn}/public/*` + `${customBucket.bucketArn}/protected/*` + ], + }), + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["s3:ListBucket"], + resources: [ + `${customBucket.bucketArn}` + `${customBucket.bucketArn}/*` + ], + conditions: { + StringLike: { + "s3:prefix": [ + "public/", + "public/*", + "protected/", + "protected/*" + ], + }, + }, + }), + ], +}); + +/* + Define an inline policy to attach to Amplify's auth role + This policy defines how authenticated users can access your + existing bucket and customizes owner access to their individual folder +*/ +const authPolicy = new Policy(backend.stack, "customBucketAuthPolicy", { + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["s3:GetObject"], + resources: [ + `${customBucket.bucketArn}/public/*` + `${customBucket.bucketArn}/protected/*` + ], + }), + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["s3:ListBucket"], + resources: [ + `${customBucket.bucketArn}` + `${customBucket.bucketArn}/*` + ], + conditions: { + StringLike: { + "s3:prefix": [ + "public/", + "public/*", + "protected/", + "protected/*" + ], + }, + }, + }), + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["s3:PutObject"], + resources: [ + `${customBucket.bucketArn}/public/*` + `${customBucket.bucketArn}/protected/${cognito-identity.amazonaws.com:sub}/*` + ], + }), + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["s3:DeleteObject"], + resources: [ + `${customBucket.bucketArn}/protected/${cognito-identity.amazonaws.com:sub}/*` + ], + }), + ], +}); + +// Add the policies to the unauthenticated user role +backend.auth.resources.unauthenticatedUserIamRole.attachInlinePolicy( + unauthPolicy, +); + +// Add the policies to the authenticated user role +backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy(authPolicy); +// highlight-end +``` + + +The custom authorization rules defined in the examples can be combined, and follow the same rules as Amplify-defined storage. Please refer to our documentation on [customizing authorization rules](/[platform]/build-a-backend/storage/authorization/) for more information. + + + +### Import latest `amplify_outputs.json` file + +To ensure the local `amplify_outputs.json` file is up-to-date, you can run [the `npx ampx generate outputs` command](/[platform]/reference/cli-commands/#npx-ampx-generate-outputs) or download the latest `amplify_outputs.json` from the Amplify console as shown below. + +![](/images/gen2/getting-started/react/amplify-outputs-download.png) + + + +### Import latest `amplify_outputs.dart` file + +To ensure the local `amplify_outputs.dart` file is up-to-date, you can run [the `npx ampx generate outputs` command](/[platform]/reference/cli-commands/#npx-ampx-generate-outputs). + + +Now that you've configured the necessary permissions, you can start using the storage APIs with your chosen S3 bucket. + +## Use storage resources without an Amplify backend + +While using the Amplify backend is the easiest way to get started, existing storage resources can also be integrated with Amplify Storage. + +In addition to manually configuring your storage options, you will also need to ensure Amplify Auth is properly configured in your project and associated IAM roles have the necessary permissions to interact with your existing bucket. Read more about [using existing auth resources without an Amplify backend](/[platform]/build-a-backend/auth/use-existing-cognito-resources/#use-auth-resources-without-an-amplify-backend). + +### Using `Amplify.configure` +Existing storage resource setup can be accomplished by passing the resource metadata to `Amplify.configure`. This will configure the Amplify Storage client library to interact with the additional resources. It's recommended to add the Amplify configuration step as early as possible in the application lifecycle, ideally at the root entry point. + +```ts +import { Amplify } from "aws-amplify"; + +Amplify.configure({ + Auth: { + // add your auth configuration + }, + Storage: { + S3: { + bucket: "", + region: "", + // default bucket metadata should be duplicated below with any additional buckets + buckets: { + "": { + bucketName: "", + region: "", + paths: { + "public/*": { + guest: ["get", "list"], + authenticated: ["get", "list", "write", "delete"], + groupsadmin: ["get", "list", "write", "delete"] + }, + "protected/*": { + guest: ["get", "list"], + authenticated: ["get", "list"], + groupsadmin: ["get", "list", "write", "delete"] + } + "protected/${cognito-identity.amazonaws.com:sub}/*": { + entityidentity: ["get", "list", "write", "delete"] + }, + "admin/*": { + authenticated: ["get", "list"], + groupsadmin: ["get", "list", "write", "delete"], + }, + } + }, + "": { + bucketName: "", + region: "", + paths: { + // ... + } + } + } + } + } +}); +``` + +### Using `amplify_outputs.json` + +Alternatively, existing storage resources can be used by creating or modifying the `amplify_outputs.json` file directly. + +```ts title="amplify_outputs.json" +{ + "auth": { + // add your auth configuration + }, + "storage": { + "aws_region": "", + "bucket_name": "", + // default bucket metadata should be duplicated below with any additional buckets + "buckets": [ + { + "name": "", + "bucket_name": "", + "aws_region": "", + "paths": { + "public/*": { + "guest": [ + "get", + "list" + ], + "authenticated": [ + "get", + "list", + "write", + "delete" + ], + "groupsadmin": [ + "get", + "list", + "write", + "delete" + ] + }, + "protected/*": { + "guest": [ + "get", + "list" + ], + "authenticated": [ + "get", + "list" + ], + "groupsadmin": [ + "get", + "list", + "write", + "delete" + ] + }, + "protected/${cognito-identity.amazonaws.com:sub}/*": { + "entityidentity": [ + "get", + "list", + "write", + "delete" + ] + }, + "admin/*": { + "authenticated": [ + "get", + "list" + ], + "groupsadmin": [ + "get", + "list", + "write", + "delete" + ] + } + } + }, + { + "name": "", + "bucket_name": "", + "aws_region": "", + "paths": { + // add more paths for the bucket + } + } + ] + } +} +``` + + + +--- + +--- +title: "Data usage policy" +section: "build-a-backend/storage" +platforms: ["swift", "flutter"] +gen: 2 +last-updated: "2024-10-25T16:39:44.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/data-usage/" +--- + +Apple requires app developers to provide the data usage policy of the app when they submit their app to the App Store. See Apple's [User privacy and data use](https://developer.apple.com/app-store/user-privacy-and-data-use/) for more details. Amplify Library is used to interact with AWS resources under the developer’s ownership and management. The library cannot predict the usage of its APIs and it is up to the developer to provide the privacy manifest that accurately reflects the data collected by the app. Below are the different categories identified by Apple and the corresponding data type used by the Amplify Library. + +By utilizing the library, Amplify gathers API usage metrics from the AWS services accessed. This process involves adding a user agent to the request made to your AWS service. The user-agent header is included with information about the Amplify Library version, operating system name, and version. AWS collects this data to generate metrics related to our library usage. This information is not linked to the user’s identity and not used for tracking purposes as described in Apple's privacy and data use guidelines. + +Should you have any specific concerns or require additional information for the enhancement of your privacy manifest, please don't hesitate to reach out. + +## Contact info + +| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | +| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | +| **Name** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | βœ… | +| **Email Address** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | βœ… | +| **Phone Number** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | βœ… | + +## User Content + +| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | +| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | +| **Photos or Videos** | | | | | | +| | Storage | App Functionality | ❌ | ❌ | βœ… | +| | Predictions | App Functionality | ❌ | ❌ | βœ… | +| **Audio Data** | | | | | | +| | Predictions | App Functionality | ❌ | ❌ | βœ… | + +## Identifiers + +| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | +| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | +| **User ID** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| | Analytics | Analytics | βœ… | ❌ | ❌ | +| **Device ID** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| | Analytics | Analytics | βœ… | ❌ | ❌ | + +## Other Data + +| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | +| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | +| **OS Version** | | | | | | +| | All categories | Analytics | ❌ | ❌ | ❌ | +| **OS Name** | | | | | | +| | All categories | Analytics | ❌ | ❌ | ❌ | +| **Locale Info** | | | | | | +| | All categories | Analytics | ❌ | ❌ | ❌ | +| **App Version** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Min OS target of the app** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Timezone information** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Network information** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Has SIM card** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Cellular Carrier Name** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Device Model** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Device Name** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Device OS Version** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Device Height and Width** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **Device Language** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | +| **identifierForVendor** | | | | | | +| | Auth | App Functionality | βœ… | ❌ | ❌ | + +## Health and Fitness +No data is collected + +## Financial Info +No data is collected + +## Location +No data is collected + +## Sensitive Info +No data is collected + +## Contacts +No data is collected + +## Browsing History +No data is collected + +## Search History +No data is collected + +## Diagnostics +No data is collected + +## Clearing data + +Some Amplify categories such as Analytics, Auth, and DataStore persist data to the local device. Some of that data is automatically removed when a user uninstalls the app from the device. + +Amplify stores Auth information in the local [system keychain](https://developer.apple.com/documentation/security/keychain_services), which does not guarantee any particular behavior around whether data is removed when an app is uninstalled. + +Deciding on when to clear this auth information is not something that the SDK can do in a generic way, so App developers should decide when to clear the data by signing out. One strategy for accomplishing this would be to use [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) to detect whether or not the app is launching for the first time, and invoking [`Auth.signOut()`](/[platform]/build-a-backend/auth/connect-your-frontend/sign-out/) if the app has not been launched before. + +--- + +--- +title: "Manage files with Amplify console" +section: "build-a-backend/storage" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-08-06T19:20:15.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/storage/manage-with-amplify-console/" +--- + +The **File storage** page in the Amplify console provides a user-friendly interface for managing your application's backend file storage. It allows for efficient testing and management of your files. + +If you have not yet created a **storage** resource, visit the [Storage setup guide](/[platform]/build-a-backend/storage/set-up-storage/). + +## Access File storage + +After you've deployed your storage resource, you can access the manager on Amplify Console. + +1. Log in to the [Amplify console](https://console.aws.amazon.com/amplify/home) and choose your app. +2. Select the branch you would like to access. +3. Select **Storage** from the left navigation bar. + +### To upload a file + +1. On the **Storage** page, select the **Upload** button +2. Select the file you would like to upload and then select **Done** + +Alternatively, you can **Drag and drop** a file onto the Storage page. + +### To delete a file + +1. On the **Storage** page, select the file you want to delete. +2. Select the **Actions** dropdown and then select **Delete**. + +### To copy a file + +1. On the **Storage** page, select the file you want to copy. +2. Select the **Actions** dropdown and then select **Copy to**. +3. Select or create the folder you want a copy of your file to be saved to. +4. Select **Copy** to copy your file to the selected folder. + +### To move a file + +1. On the **Storage** page, select the file you want to move. +3. Select the **Actions** dropdown and then select **Move to**. +4. Select or create the folder you want to move your file to. +5. Select **Move** to move your file to the selected folder. + +--- + +--- +title: "API References" +section: "build-a-backend/storage" +platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] +gen: 2 +last-updated: "" +url: "https://docs.amplify.aws/react/build-a-backend/storage/reference/" +--- + + + +--- + +--- +title: "Functions" +section: "build-a-backend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-04-18T20:39:52.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/" +--- + + + +--- + +--- +title: "Set up a Function" +section: "build-a-backend/functions" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-03-25T21:50:31.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/set-up-function/" +--- + +Amplify Functions are powered by [AWS Lambda](https://aws.amazon.com/lambda/), and allow you to perform a wide variety of customization through self-contained _functions_. Functions can respond to events from other resources, execute some logic in-between events like an authentication flow, or act as standalone jobs. They are used in a variety of settings and use cases: + +- Authentication flow customizations (e.g. attribute validations, allowlisting email domains) +- Resolvers for GraphQL APIs +- Handlers for individual REST API routes, or to host an entire API +- Scheduled jobs + +To get started, create a new directory and a resource file, `amplify/functions/say-hello/resource.ts`. Then, define the Function with `defineFunction`: + +```ts title="amplify/functions/say-hello/resource.ts" +import { defineFunction } from '@aws-amplify/backend'; + +export const sayHello = defineFunction({ + // optionally specify a name for the Function (defaults to directory name) + name: 'say-hello', + // optionally specify a path to your handler (defaults to "./handler.ts") + entry: './handler.ts' +}); +``` + +Next, create the corresponding handler file at `amplify/functions/say-hello/handler.ts`. This is where your function code will go. + +```ts title="amplify/functions/say-hello/handler.ts" +import type { Handler } from 'aws-lambda'; + +export const handler: Handler = async (event, context) => { + // your function code goes here + return 'Hello, World!'; +}; +``` + +The handler file _must_ export a function named "handler". This is the entry point to your function. For more information on writing functions, refer to the [AWS documentation for Lambda function handlers using Node.js](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html). + +Lastly, this function needs to be added to your backend. + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +// highlight-next-line +import { sayHello } from './functions/say-hello/resource'; + +defineBackend({ + // highlight-next-line + sayHello +}); +``` + +Now when you run `npx ampx sandbox` or deploy your app on Amplify, it will include your Function. + +To invoke your Function, we recommend adding your [Function as a handler for a custom query with your Amplify Data resource](/[platform]/build-a-backend/data/custom-business-logic/). This will enable you to strongly type Function arguments and the return statement, and use this to author your Function's business logic. To get started, open your `amplify/data/resource.ts` file and specify a new query in your schema: + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend" +import { sayHello } from "../functions/say-hello/resource" + +const schema = a.schema({ + // highlight-start + sayHello: a + .query() + .arguments({ + name: a.string(), + }) + .returns(a.string()) + .authorization(allow => [allow.guest()]) + .handler(a.handler.function(sayHello)), + // highlight-end +}) + +export type Schema = ClientSchema + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "iam", + }, +}) +``` + +Now you can use this query from the `Schema` export to strongly type your Function handler: + +```ts title="amplify/functions/say-hello/handler.ts" +import type { Schema } from "../../data/resource" + +export const handler: Schema["sayHello"]["functionHandler"] = async (event) => { + // arguments typed from `.arguments()` + const { name } = event.arguments + // return typed from `.returns()` + return `Hello, ${name}!` +} +``` + +Finally, use the data client to invoke your Function by calling its associated query. + + +```ts title="src/main.ts" +import type { Schema } from "./amplify/data/resource" +import { Amplify } from "aws-amplify" +import { generateClient } from "aws-amplify/api" +import outputs from "./amplify_outputs.json" + +Amplify.configure(outputs) + +const client = generateClient() + +// highlight-start +client.queries.sayHello({ + name: "Amplify", +}) +// highlight-end +``` + + +```kt +data class SayHelloDetails( + val name: String, +) + +data class SayHelloResponse( + val sayHello: SayHelloDetails +) + +val document = """ + query SayHelloQuery(${'$'}name: String!) { + sayHello(name: ${'$'}name) { + name + executionDuration + } + } +""".trimIndent() +val sayHelloQuery = SimpleGraphQLRequest( + document, + mapOf("name" to "Amplify"), + String::class.java, + GsonVariablesSerializer()) + +Amplify.API.query( + sayHelloQuery, + { + var gson = Gson() + val response = gson.fromJson(it.data, SayHelloResponse::class.java) + Log.i("MyAmplifyApp", "${response.sayHello.name}") + }, + { Log.e("MyAmplifyApp", "$it")} +) +``` + + +First define a class that matches your response shape: + +```dart +class SayHelloResponse { + final SayHello sayHello; + + SayHelloResponse({required this.sayHello}); + + factory SayHelloResponse.fromJson(Map json) { + return SayHelloResponse( + sayHello: SayHello.fromJson(json['sayHello']), + ); + } +} + +class SayHello { + final String name; + final double executionDuration; + + SayHello({required this.name, required this.executionDuration}); + + factory SayHello.fromJson(Map json) { + return SayHello( + name: json['name'], + executionDuration: json['executionDuration'], + ); + } +} +``` + +Next, make the request and map the response to the classes defined above: + +```dart +// highlight-next-line +import 'dart:convert'; + +// highlight-start +const graphQLDocument = ''' + query SayHello(\$name: String!) { + sayHello(name: \$name) { + name + executionDuration + } + } +'''; + +final echoRequest = GraphQLRequest( + document: graphQLDocument, + variables: {"name": "Amplify"}, +); + +final response = + await Amplify.API.query(request: echoRequest).response; +safePrint(response); + +Map jsonMap = json.decode(response.data!); +SayHelloResponse SayHelloResponse = SayHelloResponse.fromJson(jsonMap); +safePrint(SayHelloResponse.sayHello.name); +// highlight-end +``` + + +```swift +struct SayHelloResponse: Codable { + public let sayHello: SayHello + + struct SayHello: Codable { + public let name: String + public let executionDuration: Float + } +} + +let document = """ + query EchoQuery($name: String!) { + sayHello(name: $name) { + name + executionDuration + } + } + """ + +let result = try await Amplify.API.query(request: GraphQLRequest( + document: document, + variables: [ + "name": "Amplify" + ], + responseType: SayHelloResponse.self +)) +switch result { +case .success(let response): + print(response.sayHello) +case .failure(let error): + print(error) +} +``` + + +## Next steps + +Now that you have completed setting up your first Function, you may also want to add some additional features or modify a few settings. We recommend you learn more about: + +- [Environment variables and secrets](/[platform]/build-a-backend/functions/environment-variables-and-secrets/) +- [Grant access to other resources](/[platform]/build-a-backend/functions/grant-access-to-other-resources/) +- [Explore example use cases](/[platform]/build-a-backend/functions/examples/) +- [Modifying underlying resources with CDK](/[platform]/build-a-backend/functions/modify-resources-with-cdk/) + +--- + +--- +title: "Environment variables and secrets" +section: "build-a-backend/functions" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-22T17:49:49.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/environment-variables-and-secrets/" +--- + +Amplify Functions support setting environment variables and secrets on the `environment` property of `defineFunction`. + +> **Warning:** **Note:** do not store secret values in environment variables. Environment variables values are rendered in plaintext to the build artifacts located at `.amplify/artifacts` and may be emitted to CloudFormation stack event messages. To store secrets [skip to the secrets section](#secrets) + +> **Info:** **Note:** Environment variables and secrets configuration in `defineFunction` is not supported for [Custom Functions](/[platform]/build-a-backend/functions/custom-functions/). + +## Environment variables + +Environment variables can be configured in `defineFunction` using the `environment` property. + +```ts title="amplify/functions/say-hello/resource.ts" +import { defineFunction } from '@aws-amplify/backend'; + +export const sayHello = defineFunction({ + environment: { + NAME: 'World' + } +}); +``` + +Any environment variables specified here will be available to the function at runtime. + +Some environment variables are constant across all branches and deployments. But many environment values differ between deployment environments. [Branch-specific environment variables can be configured for Amplify hosting deployments](/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/). + +Suppose you created a branch-specific environment variable in hosting called "API_ENDPOINT" which had a different value for your "staging" vs "prod" branch. If you wanted that value to be available to your function you can pass it to the function using + +```ts title="amplify/functions/say-hello/resource.ts" +export const sayHello = defineFunction({ + environment: { + NAME: "World", + API_ENDPOINT: process.env.API_ENDPOINT + } +}); +``` + +### Accessing environment variables + +Within your function handler, you can access environment variables using the normal `process.env` global object provided by the Node runtime. However, this does not make it easy to discover what environment variables will be available at runtime. Amplify generates an `env` symbol that can be used in your function handler and provides typings for all variables that will be available at runtime. Copy the following code to use it. + +```ts title="amplify/functions/say-hello/handler.ts" +// highlight-next-line +import { env } from '$amplify/env/say-hello'; // the import is '$amplify/env/' + +export const handler = async (event) => { + // the env object has intellisense for all environment variables that are available to the function + return `Hello, ${env.NAME}!`; +}; +``` + + + +At the end of [AWS Cloud Development Kit's (AWS CDK)](https://aws.amazon.com/cdk/) synthesis, Amplify gathers names of environment variables that will be available to the function at runtime and generates the file `.amplify/generated/env/.ts`. + +If you created your project with [`create-amplify`](https://www.npmjs.com/package/create-amplify), then Amplify has already set up your project to use the `env` symbol. + +If you did not, you will need to manually configure your project. Within your `amplify/tsconfig.json` file add a `paths` compiler option: + +```json title="amplify/tsconfig.json" +{ + "compilerOptions": { + "paths": { + "$amplify/*": ["../.amplify/generated/*"] + } + } +} +``` + + + +### Generated env files + +When you configure your function with environment variables or secrets, Amplify's backend tooling generates a file using the function's `name` in `.amplify/generated` with references to your environment variables and secrets, as well as [environment variables predefined by the Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime). This provides a type-safe experience for working with environment variables that does not require typing `process.env` manually. + +> **Info:** **Note:** generated files are created before deployments when executing `ampx sandbox` or `ampx pipeline-deploy` + +For example, if you have a function with the following definition: + +```ts title="amplify/functions/say-hello/resource.ts" +import { defineFunction } from "@aws-amplify/backend"; + +export const sayHello = defineFunction({ + name: "say-hello", + environment: { + NAME: "World", + }, +}); +``` + +Upon starting your next deployment, Amplify will create a file at the following location: + +``` +.amplify/generated/env/say-hello.ts +``` + +Using the TypeScript path alias, `$amplify`, you can import the file in your function's handler: + +```ts title="amplify/functions/say-hello/handler.ts" +import { env } from "$amplify/env/say-hello" + +export const handler = async (event) => { + // the env object has intellisense for all environment variables that are available to the function + return `Hello, ${env.NAME}!`; +}; +``` + +Encountering issues with this file? [Visit the troubleshooting guide for `Cannot find module $amplify/env/`](/[platform]/build-a-backend/troubleshooting/cannot-find-module-amplify-env/) + +## Secrets + +Sometimes it is necessary to provide a secret value to a function. For example, it may need a database password or an API key to perform some business use case. Environment variables should NOT be used for this because environment variable values are included in plaintext in the function configuration. Instead, secret access can be used. + +Before using a secret in a function, you need to [define a secret](/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/#set-secrets). After you have defined a secret, you can reference it in your function config. + +```ts title="amplify/functions/say-hello/resource.ts" +import { defineFunction, secret } from '@aws-amplify/backend'; + +export const sayHello = defineFunction({ + environment: { + NAME: "World", + API_ENDPOINT: process.env.API_ENDPOINT, + API_KEY: secret('MY_API_KEY') // this assumes you created a secret named "MY_API_KEY" + } +}); +``` + +You can use this secret value at runtime in your function the same as any other environment variable. However, you will notice that the value of the environment variable is not stored as part of the function configuration. Instead, the value is fetched when your function runs and is provided in memory. + +```ts title="amplify/functions/say-hello/handler.ts" +import { env } from '$amplify/env/say-hello'; + +export const handler = async (event) => { + const request = new Request(env.API_ENDPOINT, { + headers: { + // this is the value of secret named "MY_API_KEY" + Authorization: `Bearer ${env.API_KEY}` + } + }) + // ... + return `Hello, ${env.NAME}!`; +}; +``` + +--- + +--- +title: "Configure Functions" +section: "build-a-backend/functions" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-22T17:49:49.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/configure-functions/" +--- + +`defineFunction` comes out-of-the-box with sensible but minimal defaults. The following options are provided to tweak the function configuration. + +> **Info:** **Note:** The following options are not supported for [Custom Functions](/[platform]/build-a-backend/functions/custom-functions/) except for `resourceGroupName`. + +## `name` + +By default, functions are named based on the directory the `defineFunction` call is placed in. In the above example, defining the function in `amplify/functions/my-demo-function/resource.ts` will cause the function to be named `my-demo-function` by default. + +If an entry is specified, then the name defaults to the basename of the entry path. For example, an `entry` of `./signup-trigger-handler.ts` would cause the function name to default to `signup-trigger-handler`. + +This optional property can be used to explicitly set the name of the function. + +```ts title="amplify/functions/my-demo-function/resource.ts" +export const myDemoFunction = defineFunction({ + entry: './demo-function-handler.ts', + name: 'overrideName' // explicitly set the name to override the default naming behavior +}); +``` + +## `timeoutSeconds` + +By default, functions will time out after 3 seconds. This can be configured to any whole number of seconds up to 15 minutes. + +```ts title="amplify/functions/my-demo-function/resource.ts" +export const myDemoFunction = defineFunction({ + // highlight-next-line + timeoutSeconds: 60 // 1 minute timeout +}); +``` + +## `memoryMB` + +By default, functions have 512 MB of memory allocated to them. This can be configured from 128 MB up to 10240 MB. Note that this can increase the cost of function invocation. For more pricing information see [here](https://aws.amazon.com/lambda/pricing/). + +```ts title="amplify/functions/my-demo-function/resource.ts" +export const myDemoFunction = defineFunction({ + // highlight-next-line + memoryMB: 256 // allocate 256 MB of memory to the function. +}); +``` + +## `ephemeralStorageSizeMB` + +By default, functions have 512MB of ephemeral storage to them. This can be configured from 512 MB upto 10240 MB. Note that this can increase the cost of function invocation. For more pricing information visit the [Lambda pricing documentation](https://aws.amazon.com/lambda/pricing/). + +```ts title="amplify/functions/my-demo-function/resource.ts" +export const myDemoFunction = defineFunction({ + // highlight-next-line + ephemeralStorageSizeMB: 1024 // allocate 1024 MB of ephemeral storage to the function. +}); +``` + +## `runtime` + +Currently, only Node runtimes are supported by `defineFunction`. However, you can change the Node version that is used by the function. The default is the oldest Node LTS version that is supported by AWS Lambda (currently Node 18). + +If you wish to use an older version of Node, keep an eye on the [Lambda Node version deprecation schedule](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html). As Lambda removes support for old Node versions, you will have to update to newer supported versions. + +```ts title="amplify/functions/my-demo-function/resource.ts" +export const myDemoFunction = defineFunction({ + runtime: 20 // use Node 20 +}); +``` + +## `entry` + +By default, Amplify will look for your function handler in a file called `handler.ts` in the same directory as the file where `defineFunction` is called. To point to a different handler location, specify an `entry` value. + +```ts title="amplify/functions/my-demo-function/resource.ts" +export const myDemoFunction = defineFunction({ + entry: './path/to/handler.ts' // this path should either be absolute or relative to the current file +}); +``` + +## `resourceGroupName` + +By default, functions are grouped together in a resource group named `function`. You can override this to group related function with other Amplify resources like `auth`, `data`, `storage`, or separate them into your own custom group. +This is typically useful when you have resources that depend on each other and you want to group them together. + +```ts title="amplify/functions/my-demo-function/resource.ts" +export const myDemoFunction = defineFunction({ + resourceGroupName: 'data' +}); +``` + +--- + +--- +title: "Configure client library" +section: "build-a-backend/functions" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-04-21T17:02:02.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/configure-client-library/" +--- + +The [`aws-amplify`](https://www.npmjs.com/package/aws-amplify) client library can be configured for use inside function handler files by using the credentials available from the AWS Lambda runtime. To get started, use the `getAmplifyDataClientConfig` from the backend runtime package and pass the generated `env` object to retrieve the preconfigured `resourceConfig` and `libraryOptions`. + +```ts title="amplify/my-function/handler.ts" +import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; +import { env } from '$amplify/env/my-function'; + +const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig( + env +); +``` + +> **Warning:** When using `getAmplifyDataClientConfig`, your function requires schema information stored in an Amplify deployed Amazon S3 bucket. This bucket is created during backend deployment and includes necessary access grants for your function. Modifying this bucket outside of the backend deployment process may cause unexpected failures on your function. + +`resourceConfig` and `libraryOptions` are returned for you to pass into `Amplify.configure`. This will instruct the client library which resources it can interact with, and where to retrieve AWS credentials to use when signing requests to those resources. + +```ts title="amplify/my-function/handler.ts" +import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; +// highlight-next-line +import { Amplify } from 'aws-amplify'; +import { env } from '$amplify/env/my-function'; + +const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig( + env +); + +// highlight-next-line +Amplify.configure(resourceConfig, libraryOptions); +``` + +The client library will now have access to perform operations against other AWS resources as specified by the function's IAM role. This is handled for you when [granting access to other resources using the `access` property](/[platform]/build-a-backend/functions/grant-access-to-other-resources/#using-the-access-property), however it can also be [extended using CDK](/[platform]/build-a-backend/functions/grant-access-to-other-resources/#using-cdk). + +## Under the hood + +The `getAmplifyDataClientConfig` function assists with creating the arguments' values to pass to `Amplify.configure`, which reads from the generated `env` object in order to produce configuration for the resources you have granted your function access to interact with. Under the hood this is also generating the configuration that specifies how the client library should behave, namely where the library should read credentials. + +```ts title="amplify/my-function/handler.ts" +import { env } from "$amplify/env/my-function"; + +Amplify.configure( + {/* resource configuration */}, + { + Auth: { + credentialsProvider: { + // instruct the client library to read credentials from the environment + getCredentialsAndIdentityId: async () => ({ + credentials: { + accessKeyId: env.AWS_ACCESS_KEY_ID, + secretAccessKey: env.AWS_SECRET_ACCESS_KEY, + sessionToken: env.AWS_SESSION_TOKEN, + }, + }), + clearCredentialsAndIdentityId: () => { + /* noop */ + }, + }, + }, + } +); +``` + +--- + +--- +title: "Scheduling Functions" +section: "build-a-backend/functions" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-02-12T16:04:46.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/scheduling-functions/" +--- + +Amplify offers the ability to schedule Functions to run on specific intervals using natural language or [cron expressions](https://en.wikipedia.org/wiki/Cron). To get started, specify the `schedule` property in `defineFunction`: + +> **Info:** **Note:** Configuring the schedule in `defineFunction` is not supported for [Custom Functions](/[platform]/build-a-backend/functions/custom-functions/). + +```ts title="amplify/jobs/weekly-digest/resource.ts" +import { defineFunction } from "@aws-amplify/backend"; + +export const weeklyDigest = defineFunction({ + name: "weekly-digest", + schedule: "every week", +}); +``` + +Function schedules are powered by [Amazon EventBridge rules](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rules.html), and can be leveraged to address use cases such as: + +- generating a "front page" of top-performing posts +- generating a weekly digest of top-performing posts +- generating a monthly report of warehouse inventory + +Their handlers can be typed using the `EventBridgeHandler` type: + +```ts title="amplify/jobs/weekly-digest/handler.ts" +import type { EventBridgeHandler } from "aws-lambda"; + +export const handler: EventBridgeHandler<"Scheduled Event", null, void> = async (event) => { + console.log("event", JSON.stringify(event, null, 2)) +} +``` + +> **Info:** **Note**: AWS Lambda types can be installed with +> +> ```bash title="Terminal" showLineNumbers={false} +npm add --save-dev @types/aws-lambda +``` + +Schedules can either be a single interval, or multiple intervals: + +```ts title="amplify/jobs/generate-report/resource.ts" +import { defineFunction } from "@aws-amplify/backend"; + +export const generateReport = defineFunction({ + name: "generate-report", + schedule: ["every week", "every month", "every year"], +}); +``` + +Schedules can also be defined to execute using minutes or hours with a shorthand syntax: + +```ts title="amplify/jobs/drink-some-water/resource.ts" +import { defineFunction } from "@aws-amplify/backend"; + +export const drinkSomeWater = defineFunction({ + name: "drink-some-water", + schedule: "every 1h" +}) +``` + +Or combined to create complex schedules: + +```ts title="amplify/jobs/remind-me/resource.ts" +import { defineFunction } from "@aws-amplify/backend"; + +export const remindMe = defineFunction({ + name: "remind-me", + schedule: [ + // every sunday at midnight + "every week", + // every tuesday at 5pm + "0 17 ? * 3 *", + // every wednesday at 5pm + "0 17 ? * 4 *", + // every thursday at 5pm + "0 17 ? * 5 *", + // every friday at 5pm + "0 17 ? * 6 *", + ] +}) +``` + +## Using natural language + +Schedules can be written using natural language, using terms you use every day. Amplify supports the following time periods: + +- `day` will always start at midnight +- `week` will always start on Sunday at midnight +- `month` will always start on the first of the month at midnight +- `year` will always start on the first of the year at midnight +- `m` for minutes +- `h` for hours + +Natural language expressions are prefixed with "every": + +```ts title="amplify/jobs/drink-some-water/resource.ts" +import { defineFunction } from "@aws-amplify/backend"; + +export const drinkSomeWater = defineFunction({ + name: "drink-some-water", + schedule: "every 1h" +}) +``` + +## Using cron expressions + +Schedules can be written using cron expressions. + +```ts title="amplify/jobs/remind-me/resource.ts" +import { defineFunction } from "@aws-amplify/backend"; + +export const remindMe = defineFunction({ + name: "remind-me-to-take-the-trash-out", + schedule: [ + // every tuesday at 9am + "0 9 ? * 3 *", + // every friday at 9am + "0 9 ? * 6 *", + ] +}) +``` + +--- + +--- +title: "Streaming logs" +section: "build-a-backend/functions" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-02-25T21:44:20.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/streaming-logs/" +--- + +Amplify enables you to stream logs from your AWS Lambda functions directly to your terminal while running [`ampx sandbox`](/[platform]/reference/cli-commands/#npx-ampx-sandbox). To get started, specify the `--stream-function-logs` option when starting sandbox: + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox --stream-function-logs +``` + +> **Info:** **Note**: this feature is only available for [Sandbox](/[platform]/deploy-and-host/sandbox-environments/) + +Streaming function logs directly to your terminal enable faster debug iterations, and greater insight into your functions' executions. + +## Filtering + +By default, Amplify will stream all of your functions' logs. If you wish to only stream a subset of functions you can specify a filter by function name or a regular expression for function names. For example, if you have a collection of [Auth triggers](/[platform]/build-a-backend/auth/customize-auth-lifecycle/triggers/) where the function names include "auth". + +> **Info:** When working with more than 5 functions, we recommend using the `--logs-filter` flag to filter the log output to specific functions. + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox --stream-function-logs --logs-filter auth +``` + +After you successfully deploy your personal cloud sandbox, start your frontend application, and sign up for the first time, you will see logs from your triggers' executions printed to the terminal where sandbox is running. + +```console title="Terminal" +> npx ampx sandbox --stream-function-logs --logs-filter auth +... + +✨ Total time: 158.44s + +[Sandbox] Watching for file changes... +File written: amplify_outputs.json +[auth-pre-sign-up] 3:36:34 PM INIT_START Runtime Version: nodejs:18.v30 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:f89c264158db39a1cfcbb5f9b3741413df1cfce4d550c9a475a67d923e19e2f4 +[auth-pre-sign-up] 3:36:34 PM START RequestId: 685be2bd-5df1-4dd5-9eb1-24f5f6337f91 Version: $LATEST +[auth-pre-sign-up] 3:36:34 PM END RequestId: 685be2bd-5df1-4dd5-9eb1-24f5f6337f91 +[auth-pre-sign-up] 3:36:34 PM REPORT RequestId: 685be2bd-5df1-4dd5-9eb1-24f5f6337f91 Duration: 4.12 ms Billed Duration: 5 ms Memory Size: 512 MB Max Memory Used: 67 MB Init Duration: 173.67 ms +[auth-post-confirmation] 3:38:40 PM INIT_START Runtime Version: nodejs:18.v30 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:f89c264158db39a1cfcbb5f9b3741413df1cfce4d550c9a475a67d923e19e2f4 +[auth-post-confirmation] 3:38:40 PM START RequestId: fce69b9f-b257-4af8-8a6e-821f84a39ce7 Version: $LATEST +[auth-post-confirmation] 3:38:41 PM 2024-07-19T22:38:41.209Z fce69b9f-b257-4af8-8a6e-821f84a39ce7 INFO processed 412f8911-acfa-41c7-9605-fa0c40891ea9 +[auth-post-confirmation] 3:38:41 PM END RequestId: fce69b9f-b257-4af8-8a6e-821f84a39ce7 +[auth-post-confirmation] 3:38:41 PM REPORT RequestId: fce69b9f-b257-4af8-8a6e-821f84a39ce7 Duration: 264.38 ms Billed Duration: 265 ms Memory Size: 512 MB Max Memory Used: 93 MB Init Duration: 562.19 ms +[auth-pre-authentication] 3:38:41 PM INIT_START Runtime Version: nodejs:18.v30 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:f89c264158db39a1cfcbb5f9b3741413df1cfce4d550c9a475a67d923e19e2f4 +[auth-pre-authentication] 3:38:41 PM START RequestId: 9210ca3a-1351-4826-8544-123684765710 Version: $LATEST +[auth-pre-authentication] 3:38:41 PM END RequestId: 9210ca3a-1351-4826-8544-123684765710 +[auth-pre-authentication] 3:38:41 PM REPORT RequestId: 9210ca3a-1351-4826-8544-123684765710 Duration: 3.47 ms Billed Duration: 4 ms Memory Size: 512 MB Max Memory Used: 67 MB Init Duration: 180.24 ms +[auth-post-authentication] 3:38:42 PM INIT_START Runtime Version: nodejs:18.v30 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:f89c264158db39a1cfcbb5f9b3741413df1cfce4d550c9a475a67d923e19e2f4 +[auth-post-authentication] 3:38:42 PM START RequestId: 60c1d680-ea24-4a8b-93de-02d085859140 Version: $LATEST +[auth-post-authentication] 3:38:42 PM END RequestId: 60c1d680-ea24-4a8b-93de-02d085859140 +[auth-post-authentication] 3:38:42 PM REPORT RequestId: 60c1d680-ea24-4a8b-93de-02d085859140 Duration: 4.61 ms Billed Duration: 5 ms Memory Size: 512 MB Max Memory Used: 68 MB Init Duration: 172.66 ms +``` + +## Writing to a file + +By default, Amplify will print logs to the terminal where sandbox is running, however you can alternatively write logs to a file by specifying `--logs-out-file`: + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox --stream-function-logs --logs-out-file sandbox.log +``` + +This can be combined with `--logs-filter` to create a log file of just your Auth-related functions, for example: + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox --stream-function-logs --logs-filter auth --logs-out-file sandbox-auth.log +``` + +However it cannot be combined multiple times to write logs to multiple files. + +--- + +--- +title: "Lambda Layers" +section: "build-a-backend/functions" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-22T17:49:49.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/add-lambda-layers/" +--- + +Amplify offers the ability to add layers to your functions which contain your library dependencies. Lambda layers allow you to separate your function code from its dependencies, enabling easier management of shared components across multiple functions and reducing deployment package sizes. + +> **Info:** **Note:** Configuring or adding layers in `defineFunction` is not supported for [Custom Functions](/[platform]/build-a-backend/functions/custom-functions/). + +To add a Lambda layer to your function, follow these steps: + +1. First, create and set up your Lambda layer in AWS. You can do this through the AWS Console or using the AWS CLI. For guidance on creating layers, refer to the [AWS documentation on creating Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html#configuration-layers-create). + +2. Once your layer is created and available in AWS, you can reference it in your Amplify project as shown below. + + Specify the `layers` property in `defineFunction`, for example: + + ```ts title="amplify/functions/my-function/resource.ts" + import { defineFunction } from "@aws-amplify/backend"; + + export const myFunction = defineFunction({ + name: "my-function", + layers: { + "@aws-lambda-powertools/logger": + "arn:aws:lambda:us-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:12", + }, + }); + ``` + + The Lambda layer is represented by an object of key/value pairs where the key is the module name that is exported from your layer and the value is the ARN of the layer. The key (module name) is used to externalize the module dependency so it doesn't get bundled with your Lambda function. A maximum of 5 layers can be attached to a function, and they must be in the same region as the function. + +
    Alternatively, you can specify the layer as `myLayer:1` where `myLayer` is the name of the layer and `1` is the version of the layer. For example: + + ```ts title="amplify/functions/my-function/resource.ts" + import { defineFunction } from "@aws-amplify/backend"; + + export const myFunction = defineFunction({ + name: "my-function", + layers: { + "some-module": "myLayer:1" + }, + }); + ``` + + Amplify will automatically convert this to the full layer ARN format `arn:aws:lambda:::layer:myLayer:1` using your existing account ID and region. + + + + When using layers, be mindful of versioning. The ARN includes a version number (e.g., `:12` in the example). Ensure you're using the appropriate version and have a strategy for updating layers when new versions are released. + + + +3. Then use the locally installed module in the function handler: + ```ts title="amplify/functions/my-function/handler.ts" + import { Logger } from "@aws-lambda-powertools/logger"; + import type { Handler } from "aws-lambda"; + + const logger = new Logger({ serviceName: "serverlessAirline" }); + + export const handler: Handler = async (event, context) => { + logger.info("Hello World"); + }; + ``` + +For further information on creating and managing your layers refer to [AWS documentation for Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html) + +--- + +--- +title: "Grant access to other resources" +section: "build-a-backend/functions" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-11-01T21:19:44.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/grant-access-to-other-resources/" +--- + +In order for Amplify Functions to interact with other resources they must be given access. There are two ways to grant Amplify Functions access to other resources: + +1. [Using the `access` property](#using-the-access-property) +2. [Using the AWS Cloud Development Kit (CDK)](#using-cdk) + +## Using the `access` property + +The `access` property is a property found in each of the `define*` functions for defining Amplify resources. It allows you specify the necessary actions using common language. + + + +When you grant a function access to another resource in your Amplify backend ([such as granting access to storage](/[platform]/build-a-backend/storage/#resource-access)), it will configure environment variables for that function to make SDK calls to the AWS services it has access to. Those environment variables are typed and available as part of the `env` object. + + + +Say you have a function that generates reports each month from your Data resource and needs to store the generated reports in Storage: + +```ts title="amplify/storage/resource.ts" +import { defineStorage } from '@aws-amplify/backend'; +import { generateMonthlyReports } from '../functions/generate-monthly-reports/resource'; + +export const storage = defineStorage({ + name: 'myReports', + access: (allow) => ({ + 'reports/*': [ + allow.resource(generateMonthlyReports).to(['read', 'write', 'delete']) + ] + }) +}); +``` + +This access definition will add the environment variable `myReports_BUCKET_NAME` to the function. This environment variable can be accessed on the `env` object. + +Here's an example of how it can be used to upload some content to S3. + +```ts title="amplify/functions/generate-monthly-reports/handler.ts" +import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; +import { env } from '$amplify/env/generate-monthly-reports'; + +const s3Client = new S3Client(); + +export const handler = async () => { + const command = new PutObjectCommand({ + Bucket: env.MY_REPORTS_BUCKET_NAME, + Key: `reports/${new Date().toISOString()}.csv`, + Body: new Blob([''], { type: 'text/csv;charset=utf-8;' }) + }); + + await s3Client.send(command); +}; +``` + +## Using CDK + +When permissions are needed to access resources beyond the capabilities of the `access` property, you must use CDK. + +Functions are created with an [_execution role_](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html), which is an IAM role that contains policies that dictate what resources your Function can interact with when it executes. This role can be extended using the [`addToRolePolicy()`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.IFunction.html#addwbrtowbrrolewbrpolicystatement) method: + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend" +import * as iam from "aws-cdk-lib/aws-iam" +import * as sns from "aws-cdk-lib/aws-sns" +import { weeklyDigest } from "./functions/weekly-digest/resource" + +const backend = defineBackend({ + weeklyDigest, +}) + +const weeklyDigestLambda = backend.weeklyDigest.resources.lambda + +const topicStack = backend.createStack("WeeklyDigest") +const topic = new sns.Topic(topicStack, "Topic", { + displayName: "digest", +}) + +// highlight-start +const statement = new iam.PolicyStatement({ + sid: "AllowPublishToDigest", + actions: ["sns:Publish"], + resources: [topic.topicArn], +}) + +weeklyDigestLambda.addToRolePolicy(statement) + // highlight-end +``` + +However some constructs provide a `grant*` method to grant access to common policy actions. Revisiting the example above you can grant the same access with `grantPublish`: + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend" +import * as sns from "aws-cdk-lib/aws-sns" +import { weeklyDigest } from "./functions/weekly-digest/resource" + +const backend = defineBackend({ + weeklyDigest, +}) + +const weeklyDigestLambda = backend.weeklyDigest.resources.lambda + +const topicStack = backend.createStack("WeeklyDigest") +const topic = new sns.Topic(topicStack, "Topic", { + displayName: "digest" +}) + +// highlight-next-line +topic.grantPublish(weeklyDigestLambda) +``` + +--- + +--- +title: "Examples" +section: "build-a-backend/functions" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-04-01T16:12:41.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/" +--- + + + +--- + +--- +title: "Email domain filtering" +section: "build-a-backend/functions/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-04-24T15:59:23.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/email-domain-filtering/" +--- + +You can use `defineAuth` and `defineFunction` to create a [Cognito pre sign-up Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html) that performs filtering based on the user's email address. This can allow or deny user signups based on their email address. + +To get started, install the `aws-lambda` package, which is used to define the handler type. + +```bash title="Terminal" showLineNumbers={false} +npm add --save-dev @types/aws-lambda +``` + +Next, create a new directory and a resource file, `amplify/auth/pre-sign-up/resource.ts`. Then, define the Function with `defineFunction`: + +```ts title="amplify/auth/pre-sign-up/resource.ts" +import { defineFunction } from '@aws-amplify/backend'; + +export const preSignUp = defineFunction({ + name: 'pre-sign-up', + // optionally define an environment variable for your filter + environment: { + ALLOW_DOMAIN: 'amazon.com' + } +}); +``` + +Next, create the corresponding handler file, `amplify/auth/pre-sign-up/handler.ts`, file with the following contents: + +```ts title="amplify/auth/pre-sign-up/handler.ts" +import type { PreSignUpTriggerHandler } from 'aws-lambda'; +import { env } from '$amplify/env/pre-sign-up'; + +export const handler: PreSignUpTriggerHandler = async (event) => { + const email = event.request.userAttributes['email']; + + if (!email.endsWith(env.ALLOW_DOMAIN)) { + throw new Error('Invalid email domain'); + } + + return event; +}; +``` + +Lastly, set the newly created Function resource on your auth resource: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from '@aws-amplify/backend'; +import { preSignUp } from './pre-sign-up/resource'; + +export const auth = defineAuth({ + // ... + triggers: { + preSignUp + } +}); +``` + +After deploying the changes, whenever a user attempts to sign up without an `amazon.com` email address they will receive an error. + +--- + +--- +title: "Add user to group" +section: "build-a-backend/functions/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-12-09T21:42:11.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/add-user-to-group/" +--- + +You can use `defineAuth` and `defineFunction` to create a [Cognito post confirmation Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html) that extends the behavior to perform some action when a user is confirmed. + +> **Info:** A user is "confirmed" when they verify their account. Typically this happens when the user confirms their email via the verification email. The post confirmation handler will _not_ be triggered for federated sign-ins (i.e. social sign-in). + +To get started, install the AWS SDK v3 package, which will be used to perform actions against your auth resource, and the `@types/aws-lambda` package, which is used to define the handler type: + +```bash title="Terminal" +npm add --save-dev @aws-sdk/client-cognito-identity-provider @types/aws-lambda +``` + +Next, create a new directory and a resource file, `amplify/auth/post-confirmation/resource.ts`. Then, define the Function with `defineFunction`: + +```ts title="amplify/auth/post-confirmation/resource.ts" +import { defineFunction } from '@aws-amplify/backend'; + +export const postConfirmation = defineFunction({ + name: 'post-confirmation', + // optionally define an environment variable for your group name + environment: { + GROUP_NAME: 'EVERYONE' + }, + resourceGroupName: 'auth' +}); +``` + +After creating the Function definition you will need to: + +1. create the `EVERYONE` group +2. grant access to your auth resource to ensure it can perform the `addUserToGroup` action +3. set the Function as the post confirmation trigger + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend"; +import { postConfirmation } from "./post-confirmation/resource" + +export const auth = defineAuth({ + loginWith: { + email: true, + }, + groups: ["EVERYONE"], + triggers: { + postConfirmation, + }, + access: (allow) => [ + allow.resource(postConfirmation).to(["addUserToGroup"]), + ], +}) +``` + +Then create the Function's corresponding handler file, `amplify/auth/post-confirmation/handler.ts`, file with the following contents: + +```ts title="amplify/auth/post-confirmation/handler.ts" +import type { PostConfirmationTriggerHandler } from 'aws-lambda'; +import { + CognitoIdentityProviderClient, + AdminAddUserToGroupCommand +} from '@aws-sdk/client-cognito-identity-provider'; +import { env } from '$amplify/env/post-confirmation'; + +const client = new CognitoIdentityProviderClient(); + +// add user to group +export const handler: PostConfirmationTriggerHandler = async (event) => { + const command = new AdminAddUserToGroupCommand({ + GroupName: env.GROUP_NAME, + Username: event.userName, + UserPoolId: event.userPoolId + }); + const response = await client.send(command); + console.log('processed', response.$metadata.requestId); + return event; +}; +``` + +After deploying the changes, whenever a user signs up and verifies their account they are automatically added to the group named "EVERYONE". + +--- + +--- +title: "Create a user profile record" +section: "build-a-backend/functions/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-12-09T22:00:29.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/create-user-profile-record/" +--- + +You can use `defineAuth` and `defineFunction` to create a [Cognito post confirmation Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html) to create a profile record when a user is confirmed. + +> **Info:** A user is "confirmed" when they verify their account. Typically this happens when the user confirms their email via the verification email. The post confirmation handler will _not_ be triggered for federated sign-ins (i.e. social sign-in). +To get started, install the `aws-lambda` package, which is used to define the handler type. + +```bash title="Terminal" showLineNumbers={false} +npm add --save-dev @types/aws-lambda +``` + +Update the `amplify/data/resource.ts` file to define a data model for the user's profile: + +> **Warning:** Make sure to configure the authorization rule to allow the `postConfirmation` resource as highlighted below. Granting access to resources creates environment variables for your Function such as the GraphQL API endpoint. To learn more visit the [environment variables and secrets documentation for Functions](/[platform]/build-a-backend/functions/environment-variables-and-secrets/). + +```ts title="amplify/data/resource.ts" +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; +import { postConfirmation } from "../auth/post-confirmation/resource"; + +const schema = a + .schema({ + UserProfile: a + .model({ + email: a.string(), + profileOwner: a.string(), + }) + .authorization((allow) => [ + allow.ownerDefinedIn("profileOwner"), + ]), + }) + .authorization((allow) => [allow.resource(postConfirmation)]); +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); + +``` + +Create a new directory and a resource file, `amplify/auth/post-confirmation/resource.ts`. Then, define the Function with `defineFunction`: + +```ts title="amplify/auth/post-confirmation/resource.ts" +import { defineFunction } from '@aws-amplify/backend'; + +export const postConfirmation = defineFunction({ + name: 'post-confirmation', +}); +``` + +Then, create the corresponding handler file, `amplify/auth/post-confirmation/handler.ts`, file with the following contents: + +```ts title="amplify/auth/post-confirmation/handler.ts" +import type { PostConfirmationTriggerHandler } from "aws-lambda"; +import { type Schema } from "../../data/resource"; +import { Amplify } from "aws-amplify"; +import { generateClient } from "aws-amplify/data"; +import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; +import { env } from "$amplify/env/post-confirmation"; + +const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig( + env +); + +Amplify.configure(resourceConfig, libraryOptions); + +const client = generateClient(); + +export const handler: PostConfirmationTriggerHandler = async (event) => { + await client.models.UserProfile.create({ + email: event.request.userAttributes.email, + profileOwner: `${event.request.userAttributes.sub}::${event.userName}`, + }); + + return event; +}; + +``` + +> **Warning:** When configuring Amplify with `getAmplifyDataClientConfig`, your function consumes schema information from an S3 bucket created during backend deployment with grants for the access your function need to use it. Any changes to this bucket outside of backend deployment may break your function. + +Lastly, set the newly created Function resource on your auth resource: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from '@aws-amplify/backend'; +import { postConfirmation } from './post-confirmation/resource'; + +export const auth = defineAuth({ + loginWith: { + email: true, + }, + triggers: { + postConfirmation + } +}); +``` + +After deploying the changes, whenever a user signs up and verifies their account a profile record is automatically created. + +--- + +--- +title: "Override ID token claims" +section: "build-a-backend/functions/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-12-09T21:42:11.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/override-token/" +--- + +You can use `defineAuth` and `defineFunction` to create an [Amazon Cognito Pre token generation AWS Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html) to override the token by adding a new claim or modifying the user's group membership. + +To get started, install the `aws-lambda` package, which is used to define the handler type. + +```bash title="Terminal" showLineNumbers={false} +npm add --save-dev @types/aws-lambda +``` + +Create a new directory and a resource file, `amplify/auth/pre-token-generation/resource.ts`. Then, define the function with `defineFunction`: + +```ts title="amplify/auth/pre-token-generation/resource.ts" +import { defineFunction } from '@aws-amplify/backend'; + +export const preTokenGeneration = defineFunction({ + name: 'pre-token-generation', + resourceGroupName: 'auth' +}); +``` + +Then, create the corresponding handler file, `amplify/auth/post-confirmation/pre-token-generation/handler.ts`, file with the following contents: + +```ts title="amplify/auth/pre-token-generation/handler.ts" +import type { PreTokenGenerationTriggerHandler } from "aws-lambda"; + +export const handler: PreTokenGenerationTriggerHandler = async (event) => { + event.response = { + claimsOverrideDetails: { + groupOverrideDetails: { + // This will add the user to the cognito group "amplify_group_1" + groupsToOverride: ["amplify_group_1"], + }, + claimsToAddOrOverride: { + // This will add the custom claim "amplfy_attribute" to the id token + amplfy_attribute: "amplify_gen_2", + }, + }, + }; + return event; +}; + +``` + +Lastly, set the newly created function resource on your auth resource: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from '@aws-amplify/backend'; +import { preTokenGeneration } from './pre-token-generation/resource'; + +export const auth = defineAuth({ + loginWith: { + email: true, + }, + triggers: { + preTokenGeneration + } +}); +``` + +After deploying the changes, The idToken of the user will be modified as per the trigger above. + +```json showLineNumbers={false} +{ + "cognito:groups": [ + "amplify_group_1" + ], + "email_verified": true, + "iss": "...", + "cognito:username": "...", + "origin_jti": "...", + "amplfy_attribute": "amplify_gen_2", + "aud": "...", +} + +``` + +--- + +--- +title: "User attribute validation" +section: "build-a-backend/functions/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-04-30T20:16:55.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/user-attribute-validation/" +--- + +You can use `defineAuth` and `defineFunction` to create a [Cognito pre sign-up Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html) that extends the behavior of sign-up to validate attribute values. + +To get started, create a new directory and a resource file, `amplify/auth/pre-sign-up/resource.ts`. Then, define the function with `defineFunction`: + +```ts title="amplify/auth/pre-sign-up/resource.ts" +import { defineFunction } from '@aws-amplify/backend'; + +export const preSignUp = defineFunction({ + name: "pre-sign-up", + resourceGroupName: 'auth' +}); +``` + +Next, create the corresponding handler file, `amplify/auth/pre-sign-up/handler.ts`, file with the following contents: + +```ts title="amplify/auth/pre-sign-up/handler.ts" +import type { PreSignUpTriggerHandler } from "aws-lambda" + +function isOlderThan(date: Date, age: number) { + const comparison = new Date() + comparison.setFullYear(comparison.getFullYear() - age) + return date.getTime() <= comparison.getTime() +} + +export const handler: PreSignUpTriggerHandler = async (event) => { + const birthdate = new Date(event.request.userAttributes["birthdate"]) + + // you must be 13 years or older + if (!isOlderThan(birthdate, 13)) { + throw new Error("You must be 13 years or older to use this site") + } + + return event +} +``` + +Lastly, set the newly created function resource on your auth resource: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from '@aws-amplify/backend'; +import { preSignUp } from './pre-sign-up/resource'; + +export const auth = defineAuth({ + // ... + triggers: { + preSignUp + } +}); +``` + +After deploying the changes, whenever a user attempts to sign up this handler will verify the submitter's age is above 13 years. + +--- + +--- +title: "Custom message" +section: "build-a-backend/functions/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-04-09T18:10:02.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/custom-message/" +--- + +You can use `defineAuth` and `defineFunction` to create an [Amazon Cognito custom message AWS Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-message.html) thats sends an custom email or phone verification message, or a multi-factor authentication (MFA) code. + +To get started, install `@types/aws-lambda` package that will be used to define the type of the handler: + +```bash title="Terminal" showLineNumbers={false} +npm add --save-dev @types/aws-lambda +``` + +Next, create a new directory and a resource file, `amplify/auth/custom-message/resource.ts`. Then, define the function with `defineFunction`: + +```ts title="amplify/auth/custom-message/resource.ts" +import { defineFunction } from '@aws-amplify/backend'; + +export const customMessage = defineFunction({ + name: "custom-message", + resourceGroupName: 'auth' +}); +``` + +Next, create the corresponding handler file, `amplify/auth/custom-message/handler.ts`, file with the following contents: + + +The input event for the `CustomMessage_AdminCreateUser` trigger source includes both a username and verification code. Admin-created users must receive both their username and code in order to sign in and thus you must include both the `usernameParameter` and `codeParameter` in your message template. + + +```ts title="amplify/auth/custom-message/handler.ts" +import type { CustomMessageTriggerHandler } from "aws-lambda"; + +export const handler: CustomMessageTriggerHandler = async (event) => { + if (event.triggerSource === "CustomMessage_ForgotPassword") { + const locale = event.request.userAttributes["locale"]; + if (locale === "en") { + event.response.emailMessage = `Your new one-time code is ${event.request.codeParameter}`; + event.response.emailSubject = "Reset my password"; + } else if (locale === "es") { + event.response.emailMessage = `Tu nuevo cΓ³digo de un solo uso es ${event.request.codeParameter}`; + event.response.emailSubject = "Restablecer mi contraseΓ±a"; + } + } + + if (event.triggerSource === "CustomMessage_AdminCreateUser") { + event.response.emailMessage = `Your username is ${event.request.usernameParameter} and your temporary password is ${event.request.codeParameter}`; + event.response.emailSubject = 'Welcome to Example App'; + } + + return event; +}; +``` + + +Lastly, set the newly created function resource on your auth resource: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from '@aws-amplify/backend'; +import { customMessage } from "./custom-message/resource"; + +export const auth = defineAuth({ + // ... + triggers: { + customMessage, + } +}); +``` + +After deploying the changes, whenever a user with user attribute `locale` set to `es` attempts to reset a password they will receive an email with a one-time code in Spanish. + +--- + +--- +title: "Google reCAPTCHA challenge" +section: "build-a-backend/functions/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-12-09T21:42:11.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/google-recaptcha-challenge/" +--- + +You can use `defineAuth` and `defineFunction` to create an auth experience that requires a [reCAPTCHA v3](https://developers.google.com/recaptcha/docs/v3) token. This can be accomplished by leveraging [Amazon Cognito's feature to define a custom auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#Custom-authentication-flow-and-challenges) and 3 triggers: + +1. [Create auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-create-auth-challenge.html) +2. [Define auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html) +3. [Verify auth challenge response](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-verify-auth-challenge-response.html) + +## Create auth challenge trigger + +To get started, create the first of the three triggers, `create-auth-challenge`. This is the trigger responsible for creating the reCAPTCHA challenge after a password is verified. + +```ts title="amplify/auth/create-auth-challenge/resource.ts" +import { defineFunction } from "@aws-amplify/backend" + +export const createAuthChallenge = defineFunction({ + name: "create-auth-challenge", + resourceGroupName: 'auth' +}) +``` + +After creating the resource file, create the handler with the following contents: + +```ts title="amplify/auth/create-auth-challenge/handler.ts" +import type { CreateAuthChallengeTriggerHandler } from "aws-lambda" + +export const handler: CreateAuthChallengeTriggerHandler = async (event) => { + const { request, response } = event + + if ( + // session will contain 3 "steps": SRP, password verification, custom challenge + request.session.length === 2 && + request.challengeName === "CUSTOM_CHALLENGE" + ) { + response.publicChallengeParameters = { trigger: "true" } + response.privateChallengeParameters = { answer: "" } + // optionally set challenge metadata + response.challengeMetadata = "CAPTCHA_CHALLENGE" + } + + return event +} +``` + +## Define auth challenge trigger + +Next, you will want to create the trigger responsible for _defining_ the auth challenge flow, `define-auth-challenge`. + +```ts title="amplify/auth/define-auth-challenge/resource.ts" +import { defineFunction } from "@aws-amplify/backend" + +export const defineAuthChallenge = defineFunction({ + name: "define-auth-challenge", + resourceGroupName: 'auth' +}) +``` + +After creating the resource file, create the handler with the following contents: + +```ts title="amplify/auth/define-auth-challenge/handler.ts" +import type { DefineAuthChallengeTriggerHandler } from "aws-lambda" + +export const handler: DefineAuthChallengeTriggerHandler = async (event) => { + const { response } = event + const [srp, password, captcha] = event.request.session + + // deny by default + response.issueTokens = false + response.failAuthentication = true + + if (srp?.challengeName === "SRP_A") { + response.failAuthentication = false + response.challengeName = "PASSWORD_VERIFIER" + } + + if ( + password?.challengeName === "PASSWORD_VERIFIER" && + password.challengeResult === true + ) { + response.failAuthentication = false + response.challengeName = "CUSTOM_CHALLENGE" + } + + if ( + captcha?.challengeName === "CUSTOM_CHALLENGE" && + // check for the challenge metadata set in "create-auth-challenge" + captcha?.challengeMetadata === "CAPTCHA_CHALLENGE" && + captcha.challengeResult === true + ) { + response.issueTokens = true + response.failAuthentication = false + } + + return event +} +``` + +## Verify auth challenge response trigger + +Lastly, create the trigger responsible for _verifying_ the challenge response, which in this case is the reCAPTCHA token verification. + +> **Info:** If you have not done so already, you will need to register your application and retrieve a reCAPTCHA secret key. This can then be configured for use with your cloud sandbox using: +> +> ```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox secret set GOOGLE_RECAPTCHA_SECRET_KEY +``` + +```ts title="amplify/auth/verify-auth-challenge-response/resource.ts" +import { defineFunction, secret } from "@aws-amplify/backend" + +export const verifyAuthChallengeResponse = defineFunction({ + name: "verify-auth-challenge-response", + environment: { + GOOGLE_RECAPTCHA_SECRET_KEY: secret("GOOGLE_RECAPTCHA_SECRET_KEY"), + }, + resourceGroupName: 'auth' +}) +``` + +After creating the resource file, create the handler with the following contents: + +```ts title="amplify/auth/verify-auth-challenge-response/handler.ts" +import type { VerifyAuthChallengeResponseTriggerHandler } from "aws-lambda" +import { env } from "$amplify/env/verify-auth-challenge-response" + +/** + * Google ReCAPTCHA verification response + * @see https://developers.google.com/recaptcha/docs/v3#site_verify_response + */ +type GoogleRecaptchaVerifyResponse = { + // whether this request was a valid reCAPTCHA token for your site + success: boolean + // the score for this request (0.0 - 1.0) + score: number + // the action name for this request (important to verify) + action: string + // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ) + challenge_ts: string + // the hostname of the site where the reCAPTCHA was solved + hostname: string + // optional error codes + "error-codes"?: unknown[] +} + +export const handler: VerifyAuthChallengeResponseTriggerHandler = async ( + event +) => { + if (!event.request.challengeAnswer) { + throw new Error("Missing challenge answer") + } + + // https://developers.google.com/recaptcha/docs/verify#api_request + const url = new URL("https://www.google.com/recaptcha/api/siteverify") + const params = new URLSearchParams({ + secret: env.GOOGLE_RECAPTCHA_SECRET_KEY, + response: event.request.challengeAnswer, + }) + url.search = params.toString() + + const request = new Request(url, { + method: "POST", + }) + + const response = await fetch(request) + const result = (await response.json()) as GoogleRecaptchaVerifyResponse + + if (!result.success) { + throw new Error("Verification failed", { cause: result["error-codes"] }) + } + + // indicate whether the answer is correct + event.response.answerCorrect = result.success + + return event +} +``` + +## Configure auth resource + +Finally, import and set the three triggers on your auth resource: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" +import { createAuthChallenge } from "./create-auth-challenge/resource" +import { defineAuthChallenge } from "./define-auth-challenge/resource" +import { verifyAuthChallengeResponse } from "./verify-auth-challenge-response/resource" + +/** + * Define and configure your auth resource + * @see https://docs.amplify.aws/gen2/build-a-backend/auth + */ +export const auth = defineAuth({ + loginWith: { + email: true, + }, + triggers: { + createAuthChallenge, + defineAuthChallenge, + verifyAuthChallengeResponse, + }, +}) +``` + +--- + +--- +title: "Amazon Kinesis Data Streams" +section: "build-a-backend/functions/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-01T20:22:18.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/kinesis-stream/" +--- + +With AWS Lambda, you can seamlessly integrate various event sources, such as Amazon Kinesis, Amazon SQS, and others, to trigger Lambda functions in response to real-time events. This feature enables you to build responsive, event-driven applications that react to changes in data or system state without the need for polling services. + +In this guide, let us configure a Lambda function with a Kinesis data stream as an event source. The Lambda function is automatically triggered whenever new data is published to the stream - whether you're processing streaming data, reacting to application events, or automating workflows. + +To get started, install the AWS Lambda Powertools Logger, which provides structured logging capabilities for your Lambda function, and the `aws-lambda` package, which is used to define the handler type. + +```bash title="Terminal" showLineNumbers={false} +npm add @aws-lambda-powertools/logger @types/aws-lambda +``` + +Second, create a new directory and a resource file, `amplify/functions/kinesis-function/resource.ts`. Then, define the function with `defineFunction`: + +```ts title="amplify/functions/kinesis-function/resource.ts" +import { defineFunction } from "@aws-amplify/backend"; + +export const myKinesisFunction = defineFunction({ + name: "kinesis-function", +}); +``` + +Third, create the corresponding handler file, `amplify/functions/kinesis-function/handler.ts`, file with the following contents: + +```ts title="amplify/functions/kinesis-function/handler.ts" +import type { + KinesisStreamBatchResponse, + KinesisStreamHandler, + KinesisStreamRecordPayload, +} from "aws-lambda"; +import { Buffer } from "node:buffer"; +import { Logger } from "@aws-lambda-powertools/logger"; + +const logger = new Logger({ + logLevel: "INFO", + serviceName: "kinesis-stream-handler", +}); + +export const handler: KinesisStreamHandler = async ( + event, + context +): Promise => { + for (const record of event.Records) { + try { + logger.info(`Processed Kinesis Event - EventID: ${record.eventID}`); + const recordData = await getRecordDataAsync(record.kinesis); + logger.info(`Record Data: ${recordData}`); + } catch (err) { + logger.error(`An error occurred ${err}`); + /* + When processing stream data, if any item fails, returning the failed item's position immediately + prompts Lambda to retry from this item forward, ensuring continuous processing without skipping data. + */ + return { + batchItemFailures: [{ itemIdentifier: record.kinesis.sequenceNumber }], + }; + } + } + logger.info(`Successfully processed ${event.Records.length} records.`); + return { batchItemFailures: [] }; +}; + +async function getRecordDataAsync( + payload: KinesisStreamRecordPayload +): Promise { + const data = Buffer.from(payload.data, "base64").toString("utf-8"); + await Promise.resolve(1); // Placeholder for an async process + return data; +} +``` + +Lastly, create the Kinesis stream and add it as a event source in the `amplify/backend.ts` file: + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend"; +import { Stream } from "aws-cdk-lib/aws-kinesis"; +import { StartingPosition } from "aws-cdk-lib/aws-lambda"; +import { KinesisEventSource } from "aws-cdk-lib/aws-lambda-event-sources"; +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; +import { myKinesisFunction } from "./functions/kinesis-function/resource"; + +const backend = defineBackend({ + auth, + data, + myKinesisFunction, +}); + +const kinesisStack = backend.createStack("kinesis-stack"); + +const kinesisStream = new Stream(kinesisStack, "KinesisStream", { + streamName: "myKinesisStream", + shardCount: 1, +}); + +const eventSource = new KinesisEventSource(kinesisStream, { + startingPosition: StartingPosition.LATEST, + reportBatchItemFailures: true, +}); + +backend.myKinesisFunction.resources.lambda.addEventSource(eventSource); +``` + +For examples on streaming analytics data to the Kinesis stream from your frontend, see the [Streaming analytics data](/[platform]/build-a-backend/add-aws-services/analytics/streaming-data/) documentation. + + +--- + +--- +title: "DynamoDB Streams" +section: "build-a-backend/functions/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-12-09T21:42:11.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/dynamo-db-stream/" +--- + +With AWS Lambda, you can seamlessly integrate various event sources, such as Amazon DynamoDB, Amazon SQS, and others, to trigger Lambda functions in response to real-time events. This feature enables you to build responsive, event-driven applications that react to changes in data or system state without the need for polling services. + +In this guide, lets configure a Lambda function with an Amazon DynamoDB stream as an event source. The Lambda function is automatically triggered whenever an item is added, updated, or deleted from the table, enabling you to build real-time applications that react to changes in your data. In this example, we will use a `Todo` table created by a data model on the GraphQL API. + +To get started, install the AWS Lambda Powertools Logger, which provides structured logging capabilities for your Lambda function, and the `aws-lambda` package, which is used to define the handler type. + +```bash title="Terminal" showLineNumbers={false} +npm add --save-dev @aws-lambda-powertools/logger @types/aws-lambda +``` + +Second, create a new directory and a resource file, `amplify/functions/dynamoDB-function/resource.ts`. Then, define the function with `defineFunction`: + +```ts title="amplify/functions/dynamoDB-function/resource.ts" +import { defineFunction } from "@aws-amplify/backend"; + +export const myDynamoDBFunction = defineFunction({ + name: "dynamoDB-function", + resourceGroupName: "data", +}); +``` + +Third, create the corresponding handler file, `amplify/functions/dynamoDB-function/handler.ts`, file with the following contents: + +```ts title="amplify/functions/dynamoDB-function/handler.ts" +import type { DynamoDBStreamHandler } from "aws-lambda"; +import { Logger } from "@aws-lambda-powertools/logger"; + +const logger = new Logger({ + logLevel: "INFO", + serviceName: "dynamodb-stream-handler", +}); + +export const handler: DynamoDBStreamHandler = async (event) => { + for (const record of event.Records) { + logger.info(`Processing record: ${record.eventID}`); + logger.info(`Event Type: ${record.eventName}`); + + if (record.eventName === "INSERT") { + // business logic to process new records + logger.info(`New Image: ${JSON.stringify(record.dynamodb?.NewImage)}`); + } + } + logger.info(`Successfully processed ${event.Records.length} records.`); + + return { + batchItemFailures: [], + }; +}; +``` + +Lastly, create DynamoDB table as event source in the `amplify/backend.ts` file: + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend"; +import { Stack } from "aws-cdk-lib"; +import { Policy, PolicyStatement, Effect } from "aws-cdk-lib/aws-iam"; +import { StartingPosition, EventSourceMapping } from "aws-cdk-lib/aws-lambda"; +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; +import { myDynamoDBFunction } from "./functions/dynamoDB-function/resource"; + +const backend = defineBackend({ + auth, + data, + myDynamoDBFunction, +}); + +const todoTable = backend.data.resources.tables["Todo"]; +const policy = new Policy( + Stack.of(todoTable), + "MyDynamoDBFunctionStreamingPolicy", + { + statements: [ + new PolicyStatement({ + effect: Effect.ALLOW, + actions: [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:ListStreams", + ], + resources: ["*"], + }), + ], + } +); +backend.myDynamoDBFunction.resources.lambda.role?.attachInlinePolicy(policy); + +const mapping = new EventSourceMapping( + Stack.of(todoTable), + "MyDynamoDBFunctionTodoEventStreamMapping", + { + target: backend.myDynamoDBFunction.resources.lambda, + eventSourceArn: todoTable.tableStreamArn, + startingPosition: StartingPosition.LATEST, + } +); + +mapping.node.addDependency(policy); +``` + +--- + +--- +title: "S3 Upload confirmation" +section: "build-a-backend/functions/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-12-09T21:42:11.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/s3-upload-confirmation/" +--- + +You can use `defineStorage` and `defineFunction` to create a function trigger to confirm uploading a file. + +To get started, install the `@types/aws-lambda` [package](https://www.npmjs.com/package/@types/aws-lambda), which contains types for different kinds of Lambda handlers, events, and responses. + +```bash title="Terminal" showLineNumbers={false} +npm add --save @types/aws-lambda +``` + +Update your storage definition to define the onUpload trigger as below: + +```ts title="amplify/storage/resource.ts" +import { defineFunction, defineStorage } from "@aws-amplify/backend"; + +export const storage = defineStorage({ + name: 'myProjectFiles', + triggers: { + onUpload: defineFunction({ + entry: './on-upload-handler.ts' + resourceGroupName: 'storage', + }) + } +}); +``` + +Next, create a file named `amplify/storage/on-upload-handler.ts` and use the following code to log the object keys whenever an object is uploaded to the bucket. You can add your custom logic to this function as needed. + +```ts title="amplify/storage/on-upload-handler.ts" +import type { S3Handler } from 'aws-lambda'; + +export const handler: S3Handler = async (event) => { + const objectKeys = event.Records.map((record) => record.s3.object.key); + console.log(`Upload handler invoked for objects [${objectKeys.join(', ')}]`); +}; +``` + +Now, when you deploy your backend, this function will be invoked whenever an object is uploaded to the bucket. + +--- + +--- +title: "Custom Auth Challenge" +section: "build-a-backend/functions/examples" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-12-09T21:42:11.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/custom-auth-flows/" +--- + +Secure Remote Password (SRP) is a cryptographic protocol enabling password-based authentication without transmitting the password over the network. In Amazon Cognito custom authentication flows, CUSTOM_WITH_SRP incorporates SRP steps for enhanced security, while CUSTOM_WITHOUT_SRP bypasses these for a simpler process. The choice between them depends on your application's security needs and performance requirements. +This guide demonstrates how to implement both types of custom authentication flows using AWS Amplify with Lambda triggers. + +You can use `defineAuth` and `defineFunction` to create an auth experience that uses `CUSTOM_WITH_SRP` and `CUSTOM_WITHOUT_SRP`. This can be accomplished by leveraging [Amazon Cognito's feature to define a custom auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#Custom-authentication-flow-and-challenges) and 3 triggers: + +1. [Create auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-create-auth-challenge.html) +2. [Define auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html) +3. [Verify auth challenge response](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-verify-auth-challenge-response.html) + +To get started, install the `aws-lambda` package, which is used to define the handler type. + +```bash title="Terminal" showLineNumbers={false} +npm add --save-dev @types/aws-lambda +``` + +## Create auth challenge trigger + +To get started, create the first of the three triggers, `create-auth-challenge`. This is the trigger responsible for creating the reCAPTCHA challenge after a password is verified. + +```ts title="amplify/auth/create-auth-challenge/resource.ts" +import { defineFunction } from "@aws-amplify/backend" + +export const createAuthChallenge = defineFunction({ + name: "create-auth-challenge", + resourceGroupName: 'auth' +}) +``` + +After creating the resource file, create the handler with the following contents: + +```ts title="amplify/auth/create-auth-challenge/handler.ts" +import type { CreateAuthChallengeTriggerHandler } from "aws-lambda"; + +export const handler: CreateAuthChallengeTriggerHandler = async (event) => { + if (event.request.challengeName === "CUSTOM_CHALLENGE") { + // Generate a random code for the custom challenge + const challengeCode = "123456"; + + event.response.challengeMetadata = "TOKEN_CHECK"; + + event.response.publicChallengeParameters = { + trigger: "true", + code: challengeCode, + }; + + event.response.privateChallengeParameters = { trigger: "true" }; + event.response.privateChallengeParameters.answer = challengeCode; + } + return event; +}; +``` + +## Define auth challenge trigger + +Next, you will want to create the trigger responsible for _defining_ the auth challenge flow, `define-auth-challenge`. + +```ts title="amplify/auth/define-auth-challenge/resource.ts" +import { defineFunction } from "@aws-amplify/backend" + +export const defineAuthChallenge = defineFunction({ + name: "define-auth-challenge", + resourceGroupName: 'auth' +}) +``` + +After creating the resource file, create the handler with the following contents if you are using `CUSTOM_WITHOUT_SRP`: + +```ts title="amplify/auth/define-auth-challenge/handler.ts" +import type { DefineAuthChallengeTriggerHandler } from "aws-lambda" + +export const handler: DefineAuthChallengeTriggerHandler = async (event) => { + // Check if this is the first authentication attempt + if (event.request.session.length === 0) { + // For the first attempt, we start with the custom challenge + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = "CUSTOM_CHALLENGE"; + } else if ( + event.request.session.length === 1 && + event.request.session[0].challengeName === "CUSTOM_CHALLENGE" && + event.request.session[0].challengeResult === true + ) { + // If this is the second attempt (session length 1), + // it was a CUSTOM_CHALLENGE, and the result was successful + event.response.issueTokens = true; + event.response.failAuthentication = false; + } else { + // If we reach here, it means either: + // 1. The custom challenge failed + // 2. We've gone through more attempts than expected + // In either case, we fail the authentication + event.response.issueTokens = false; + event.response.failAuthentication = true; + } + + return event; +}; +``` + +Or if you are using `CUSTOM_WITH_SRP`: + +```ts title="amplify/auth/define-auth-challenge/handler.ts" +import type { DefineAuthChallengeTriggerHandler } from "aws-lambda" + +export const handler: DefineAuthChallengeTriggerHandler = async (event) => { + // First attempt: Start with SRP_A (Secure Remote Password protocol, step A) + if (event.request.session.length === 0) { + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = "SRP_A"; + } else if ( + event.request.session.length === 1 && + event.request.session[0].challengeName === "SRP_A" && + event.request.session[0].challengeResult === true + ) { + // Second attempt: SRP_A was successful, move to PASSWORD_VERIFIER + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = "PASSWORD_VERIFIER"; + } else if ( + event.request.session.length === 2 && + event.request.session[1].challengeName === "PASSWORD_VERIFIER" && + event.request.session[1].challengeResult === true + ) { + // Third attempt: PASSWORD_VERIFIER was successful, move to CUSTOM_CHALLENGE + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = "CUSTOM_CHALLENGE"; + } else if ( + event.request.session.length === 3 && + event.request.session[2].challengeName === "CUSTOM_CHALLENGE" && + event.request.session[2].challengeResult === true + ) { + // Fourth attempt: CUSTOM_CHALLENGE was successful, authentication complete + event.response.issueTokens = true; + event.response.failAuthentication = false; + } else { + // If we reach here, it means one of the challenges failed or + // we've gone through more attempts than expected + event.response.issueTokens = false; + event.response.failAuthentication = true; + } + + return event; +}; +``` + +## Verify auth challenge response trigger + +Lastly, create the trigger responsible for _verifying_ the challenge response. For the purpose of this example, the verification check will always return true. + +```ts title="amplify/auth/verify-auth-challenge-response/resource.ts" +import { defineFunction, secret } from "@aws-amplify/backend" + +export const verifyAuthChallengeResponse = defineFunction({ + name: "verify-auth-challenge-response", + resourceGroupName: 'auth' +}) +``` + +After creating the resource file, create the handler with the following contents: + +```ts title="amplify/auth/verify-auth-challenge-response/handler.ts" +import type { VerifyAuthChallengeResponseTriggerHandler } from "aws-lambda" + +export const handler: VerifyAuthChallengeResponseTriggerHandler = async ( + event +) => { + event.response.answerCorrect = true; + return event; +}; + +``` + +## Configure auth resource + +Finally, import and set the three triggers on your auth resource: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" +import { createAuthChallenge } from "./create-auth-challenge/resource" +import { defineAuthChallenge } from "./define-auth-challenge/resource" +import { verifyAuthChallengeResponse } from "./verify-auth-challenge-response/resource" + +/** + * Define and configure your auth resource + * @see https://docs.amplify.aws/gen2/build-a-backend/auth + */ +export const auth = defineAuth({ + loginWith: { + email: true, + }, + triggers: { + createAuthChallenge, + defineAuthChallenge, + verifyAuthChallengeResponse, + }, +}) +``` + +After deploying the changes, whenever a user attempts to sign in with `CUSTOM_WITH_SRP` or `CUSTOM_WITHOUT_SRP`, the Lambda challenges will be triggered. + +--- + +--- +title: "Modify Amplify-generated Lambda resources with CDK" +section: "build-a-backend/functions" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-05-16T15:59:30.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/modify-resources-with-cdk/" +--- + +Amplify Functions utilize the [`NodejsFunction`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html) construct from the [AWS Cloud Development Kit (CDK)](https://aws.amazon.com/cdk/). The underlying resources can be modified, overridden, or extended using CDK after setting the resource on your backend. + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { myFunction } from './functions/my-function'; + +const backend = defineBackend({ + myFunction +}) + +// CDK constructs can be accessed via +backend.myFunction.resources + +// where the Lambda function can be found on +backend.myFunction.resources.lambda +``` + +The Lambda resource available is a representation of [`IFunction`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.IFunction.html). + +## Adding IAM Policies + +To learn how to add IAM policies to a Function's execution role, visit the [documentation for granting access to other resources](/[platform]/build-a-backend/functions/grant-access-to-other-resources#using-cdk). + +--- + +--- +title: "Custom functions" +section: "build-a-backend/functions" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2025-01-24T20:19:47.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/functions/custom-functions/" +--- + +AWS Amplify Gen 2 functions are AWS Lambda functions that can be used to perform tasks and customize workflows in your Amplify app. Functions can be written in Node.js, Python, Go, or any [other language supported by AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html). + +> **Warning:** **Note:** [Fullstack Git-based environments](https://docs.amplify.aws/react/how-amplify-works/concepts/#fullstack-git-based-environments) do not support Docker for functions bundling out of the box. To learn more [skip to the Docker section](#docker). + +> **Info:** **Note:** The following options in `defineFunction` are not supported for Custom Functions: +> - [Environment variables and secrets](/[platform]/build-a-backend/functions/environment-variables-and-secrets/) +> - [Scheduling configuration](/[platform]/build-a-backend/functions/scheduling-functions/) +> - [Lambda layers](/[platform]/build-a-backend/functions/add-lambda-layers/) +> - [Function options](/[platform]/build-a-backend/functions/configure-functions/) +> +> You'll need to configure these options directly in your CDK Function definition instead. However, `resourceGroupName` property is supported and can be used to group related resources together in your `defineFunction` definition. + +In this guide, you will learn how to create Python and Go functions with Amplify functions. The examples shown in this guide do not use Docker to build functions. Instead, the examples use commands that run on your host system to build, and as such require the necessary tooling for the language you are using for your functions. + +## Python + +To get started, create a new directory and a resource file, `amplify/functions/say-hello/resource.ts`. Then, define the function with `defineFunction`: +```ts title="amplify/functions/say-hello/resource.ts" +import { execSync } from "node:child_process"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import { defineFunction } from "@aws-amplify/backend"; +import { DockerImage, Duration } from "aws-cdk-lib"; +import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda"; + +const functionDir = path.dirname(fileURLToPath(import.meta.url)); + +export const sayHelloFunctionHandler = defineFunction( + (scope) => + new Function(scope, "say-hello", { + handler: "index.handler", + runtime: Runtime.PYTHON_3_9, // or any other python version + timeout: Duration.seconds(20), // default is 3 seconds + code: Code.fromAsset(functionDir, { + bundling: { + image: DockerImage.fromRegistry("dummy"), // replace with desired image from AWS ECR Public Gallery + local: { + tryBundle(outputDir: string) { + execSync( + `python3 -m pip install -r ${path.join(functionDir, "requirements.txt")} -t ${path.join(outputDir)} --platform manylinux2014_x86_64 --only-binary=:all:` + ); + execSync(`cp -r ${functionDir}/* ${path.join(outputDir)}`); + return true; + }, + }, + }, + }), + }), + { + resourceGroupName: "auth" // Optional: Groups this function with auth resource + } +); +``` + +Next, create the corresponding handler file at `amplify/functions/say-hello/index.py`. This is where your function code will go. + +```ts title="amplify/functions/say-hello/index.py" +import json + +def handler(event, context): + return { + "statusCode": 200, + "body": json.dumps({ + "message": "Hello World", + }), + } +``` + +The handler file _must_ export a function named "handler". This is the entry point to your function. For more information on writing functions, refer to the [AWS documentation for Lambda function handlers using Python](https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html). + +If you need Python packages, you can add them to a `requirements.txt` file in the same directory as your handler file. The `bundling` option in the `Code.fromAsset` method will install these packages for you. +Create a `requirements.txt` file in the same directory as your handler file. This file should contain the names of the packages you want to install. For example: + +```txt title="amplify/functions/say-hello/requirements.txt" +request==2.25.1 +some-other-package>=1.0.0 +``` + +You're now ready to deploy your python function. Next is the same process as the Node.js/TypeScript function. Go to [Common steps for all languages](#common-steps-for-all-languages) to continue. + +## Go +To get started, Create a new directory and a resource file, `amplify/functions/say-hello/resource.ts`. Then, define the function with `defineFunction`: + +```ts title="amplify/functions/say-hello/resource.ts" +import { execSync } from "node:child_process"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import { defineFunction } from "@aws-amplify/backend"; +import { DockerImage, Duration } from "aws-cdk-lib"; +import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda"; + +const functionDir = path.dirname(fileURLToPath(import.meta.url)); + +export const sayHelloFunctionHandler = defineFunction( + (scope) => + new Function(scope, "say-hello", { + handler: "bootstrap", + runtime: Runtime.PROVIDED_AL2023, + timeout: Duration.seconds(3), // default is 3 seconds + code: Code.fromAsset(functionDir, { + bundling: { + image: DockerImage.fromRegistry("dummy"), + local: { + tryBundle(outputDir: string) { + execSync(`rsync -rLv ${functionDir}/* ${path.join(outputDir)}`); + execSync( + `cd ${path.join(outputDir)} && GOARCH=amd64 GOOS=linux go build -tags lambda.norpc -o ${path.join(outputDir)}/bootstrap ${functionDir}/main.go` + ); + return true; + }, + }, + }, + }), + }), + { + resourceGroupName: "auth" // Optional: Groups this function with auth resource + } +); +``` + +Next, create the corresponding handler file at `amplify/functions/say-hello/main.go`. This is where your function code will go. + +```go title="amplify/functions/say-hello/main.go" +package main + +import ( + "context" + "fmt" + + "github.com/aws/aws-lambda-go/lambda" +) + +type Event struct { + Arguments Arguments `json:"arguments"` +} + +type Arguments struct { + Title string `json:"phone"` + Msg string `json:"msg"` +} + +func HandleRequest(ctx context.Context, event Event) (string, error) { + fmt.Println("Received event: ", event) + + // fmt.Println("Message sent to: ", event.Arguments.Msg) + // You can use lambda arguments in your code + + return "Hello World!", nil +} + +func main() { + lambda.Start(HandleRequest) +} +``` + +Then you should run the following command to build the go function: +```bash title="terminal" showLineNumbers={false} +go mod init lambda +``` +then run to install the dependencies. + +```bash title="terminal" showLineNumbers={false} +go mod tidy +``` + +You're now ready to deploy your golang function. Next is the same process as the Node.js/TypeScript function. + +## Common steps for all languages + +Regardless of the language used, your function needs to be added to your backend. +```ts title="amplify/backend.ts" +// highlight-next-line +import { sayHelloFunctionHandler } from './functions/say-hello/resource'; + +defineBackend({ + // highlight-next-line + sayHelloFunctionHandler, +}); +``` + +Now when you run `npx ampx sandbox` or deploy your app on Amplify, it will include your function. + +To invoke your function, we recommend adding your [function as a handler for a custom query with your Amplify Data resource](/[platform]/build-a-backend/data/custom-business-logic/). To get started, open your `amplify/data/resource.ts` file and specify a new query in your schema: + +```ts title="amplify/data/resource.ts" +import { sayHelloFunctionHandler } from "../functions/say-hello/resource" + +const schema = a.schema({ + // highlight-start + sayHello: a + .query() + .arguments({ + name: a.string(), + }) + .returns(a.string()) + .handler(a.handler.function(sayHelloFunctionHandler)), + // highlight-end +}) + +export type Schema = ClientSchema + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "iam", + }, +}) +``` + +## Docker + +Custom function may require [Docker](https://www.docker.com/) in order to build and bundle function's code. A deployment failing with `CustomFunctionProviderDockerError` error indicates that a custom function requires Docker but the Docker daemon was not found. In that case you need to provide a working Docker installation at runtime. + +### Personal sandboxes + +Ensure that Docker is installed on your computer and that Docker daemon is running. You can check if Docker daemon is running using the following command: +```bash title="terminal" showLineNumbers={false} +docker info +``` + +### Fullstack Git-based environments + +Amplify does not provide Docker daemon out of the box in branch deployments. However, you have an option to provide [your own image that meets Amplify requirements](https://docs.aws.amazon.com/amplify/latest/userguide/custom-build-image.html) and includes a Docker installation. + +For example, the `aws/codebuild/amazonlinux-x86_64-standard:5.0` image ([see definition](https://github.com/aws/aws-codebuild-docker-images)) meets Amplify requirements and includes Docker installation. + +--- + +--- +title: "Server-Side Rendering" +section: "build-a-backend" +platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] +gen: 2 +last-updated: "2025-04-11T22:29:14.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/server-side-rendering/" +--- + +This guide walks through how to use Amplify Auth and Data APIs from Next.js server-side runtimes. + + +Before you begin: + +- [Follow the Next.js App Router tutorial](/[platform]/build-a-backend/server-side-rendering/nextjs-app-router-server-components) + + +## Install the Amplify Next.js adapter + +> **Warning:** **Note:** Amplify JS v6 supports Next.js with the version range: `>=13.5.0 <16.0.0`. +> Ensure you have the correct version to integrate with Amplify. + +To use Amplify APIs server-side, you need to install the Amplify Next.js adapter in addition to the Amplify libraries: + +```bash title="Terminal" showLineNumbers={false} +npm add aws-amplify @aws-amplify/adapter-nextjs +``` + +## Configure Amplify in Next.js + +#### [Configure Amplify for server-side usage] + +You will need to create a `runWithAmplifyServerContext` function to use Amplify APIs on the server-side of your Next.js app. + +You can create an `amplifyServerUtils.ts` file under a `utils` folder in your codebase. In this file, you will import the Amplify backend outputs from the `amplify_outputs.json` file that is generated by the Amplify CLI, and use the `createServerRunner` function to create the `runWithAmplifyServerContext` function. + +For example, the `utils/amplifyServerUtils.ts` file may contain the following content: + +```typescript title="src/utils/amplifyServerUtils.ts" +import { createServerRunner } from '@aws-amplify/adapter-nextjs'; +import outputs from '@/amplify_outputs.json'; + +export const { runWithAmplifyServerContext } = createServerRunner({ + config: outputs +}); +``` + +You can use the exported `runWithAmplifyServerContext` function to call Amplify APIs within isolated request contexts. You can review examples under the [Calling Amplify category APIs on the server side](#calling-amplify-category-apis-on-the-server-side) section. + + +**Tip:** You only need to call the `createServerRunner` function once and reuse the `runWithAmplifyServerContext` function throughout. + + +#### [Configure Amplify for client-side usage] + + +**Tip**: You only need do this step if you are using Amplify APIs on the client side of your Next.js app, for example, calling Amplify Auth `signIn` API to sign in a user, or use GraphQL subscriptions on the client side. + + +When you use the Amplify library on the client-side of your Next.js app, you will need to configure Amplify by calling the `Amplify.configure` as you would to use Amplify in a single-page application. + + + +**Note:** To use the Amplify library on the client side in a Next.js app, you will need to set `ssr` to `true` when calling `Amplify.configure`. This instructs the Amplify library to store tokens in the cookie store of a browser. Cookies will be sent along with requests to your Next.js server for authentication. + + + +```typescript +'use client'; + +import outputs from '@/amplify_outputs.json'; +import { Amplify } from 'aws-amplify'; + +Amplify.configure(outputs, { + ssr: true // required when using Amplify with Next.js +}); + +export default function RootLayoutThatConfiguresAmplifyOnTheClient({ + children +}: { + children: React.ReactNode; +}) { + return children; +} +``` + +> **Warning:** Make sure you call `Amplify.configure` as early as possible in your application’s life-cycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. Review the [Library Not Configured Troubleshooting guide](/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/) for possible causes of this issue. + + + +To avoid repetitive calls to `Amplify.configure`, you can call it once in a top-level client-side rendered layout component. + + + + + +If you're using the Next.js App Router, you can create a client component to configure Amplify and import it into your root layout. + +`ConfigureAmplifyClientSide.ts`: + +```tsx title="src/components/ConfigureAmplifyClientSide.tsx" +'use client'; + +import { Amplify } from 'aws-amplify'; +import outputs from '../amplify_outputs.json'; + +Amplify.configure(outputs, { ssr: true }); + +export default function ConfigureAmplifyClientSide() { + return null; +} +``` + +`layout.tsx`: + +```tsx title="src/app/layout.tsx" +import ConfigureAmplifyClientSide from '@/components/ConfigureAmplifyClientSide'; +import './globals.css'; + +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Create Next App', + description: 'Generated by create next app', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + <> + + {children} + + + + ); +} +``` + + + +## Authentication with Next.js server-side runtime + +### (Experimental) Perform authentication on the server side and enable HttpOnly cookies + +> **Warning:** **Warning:** This feature is experimental and may change in future releases. +> +> Once you enable the server-side sign-in feature, auth tokens are stored in HttpOnly cookies and you may not change the HttpOnly attribute. Since these cookies are inaccessible from client-side scripts, you won’t be able to use any Amplify JS APIs on the client side. Therefore, you don’t need to configure Amplify on the client side. You can keep using [these Amplify JS server-side APIs](/[platform]/build-a-backend/server-side-rendering/#supported-apis-for-nextjs-server-side-usage) on the server side. + +Additional setup is required to enable server-side authentication flows in your Next.js app. + +#### Step 1 - Specify the origin of your app in environment variables + +Add the following environment variable to your Next.js app. For example in a `.env` file: + +```shell title=".env" showLineNumbers={false} +AMPLIFY_APP_ORIGIN=https://myapp.com +``` + +Ensure this environment variable is accessible in your Next.js app's server runtime. + +> **Info:** **Note:** Token cookies are transmitted via server-side authentication flows. In production environments, it is recommended to use HTTPS as the origin for enhanced security. + +#### Step 2 - Export the `createAuthRouteHandlers` function + +The `createAuthRouteHandlers` function is created by the `createServerRunner` function call when you configure Amplify for server-side usage. You can export this function from your `amplifyServerUtils.ts` file. You can also configure cookie attributes with the `runtimeOptions` parameter. + +```typescript title="src/utils/amplifyServerUtils.ts" +import { createServerRunner } from '@aws-amplify/adapter-nextjs'; +import outputs from '@/amplify_outputs.json'; + +export const { + runWithAmplifyServerContext, + // highlight-start + createAuthRouteHandlers, + // highlight-end +} = createServerRunner({ + config: outputs, + // highlight-start + runtimeOptions: { + cookies: { + domain: '.myapp.com', // making cookies available to all subdomains + sameSite: 'strict', + maxAge: 60 * 60 * 24 * 7 // 7 days + } + } + // highlight-end +}); +``` + +#### Step 3 - Set up the Auth API routes + +Create an API route using the `createAuthRouteHandlers` function. For example: + +#### [App router] +```typescript title="src/app/api/auth/[slug]/route.ts" +import { createAuthRouteHandlers } from "@/utils/amplifyServerUtils"; + +export const GET = createAuthRouteHandlers({ + redirectOnSignInComplete: "/home", + redirectOnSignOutComplete: "/sign-in", +}); +``` + +#### [Pages router] +```typescript title="src/pages/api/auth/[slug].ts" +import { createAuthRouteHandlers } from "@/utils/amplifyServerUtils"; + +export default createAuthRouteHandlers({ + redirectOnSignInComplete: "/home", + redirectOnSignOutComplete: "/sign-in", +}); +``` + +With the above example, Amplify generates the following API routes: + +| API Routes | What it does | +| --------------------------------------------------- | ------------------------------------------------------------ | +| `/api/auth/sign-up` | Upon navigating an end user to this route, they’ll be redirected to the Amazon Cognito Managed Login sign-up form. After sign-up and sign-in, they’ll be redirected back to the route `/api/auth/sign-in-callback`. | +| `/api/auth/sign-in` | Upon navigating an end user to this route, they’ll be redirected to the Amazon Cognito Managed Login sign-in form. After sign-in, they’ll be redirected back to the route `/api/auth/sign-in-callback`. | +| `/api/auth/sign-in?provider=` | Upon navigating an end user to this route, they’ll be redirected first to the Amazon Cognito Managed Login and then the specified social provider sign-in page. After sign-in, they’ll be redirected back to the route `/api/auth/sign-in-callback`. | +| `/api/auth/sign-out` | Upon navigating an end user to this route, the end user will be signed out and redirected to the route `/api/auth/sign-out-callback`. | +| `/api/auth/sign-in-callback` | Amazon Cognito Managed Login redirects an end user back to this route after signing in. Amplify exchanges auth tokens and stores them as HttpOnly cookies in the browser cookie store, then redirects the end user back to the route specified by the `redirectOnSignInComplete` parameter. | +| `/api/auth/sign-out-callback` | Amazon Cognito Managed Login redirects an end user back to this route after signing out, Amplify revokes access token and refresh token and removes token cookies from browser cookie store, then redirects the end user back to the route specified by the `redirectOnSignOutComplete` parameter. | + +To customize the language of the Amazon Cognito Managed Login pages, you can add the `lang` query parameter to the `/api/auth/sign-in` and `/api/auth/sign-up` routes. For example, `/api/auth/sign-in?lang=fr`. Refer to the [Managed login localization documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-managed-login.html#managed-login-localization) for more information on the supported languages. + +> **Info:** **Note:** A signing-out call involves multiple steps, including signing out from Amazon Cognito Managed Login, revoking tokens, and removing cookies. If the user closes the browser during the process, the following may occur: +> +> 1. auth token have not been revoked - user remains signed in +> 2. auth token have been revoked but cookies have not been removed - cookies will be removed when the user visits the app again + +#### Step 4 - Provide the redirect URLs to the Auth Resource in Amplify + +You can provide the callback API routes as the redirect URLs in the Auth resource configuration. For example: + +```ts title="amplify/auth/resource.ts" +export const auth = defineAuth({ + loginWith: { + email: true, + // highlight-start + externalProviders: { + callbackUrls: ["https://myapp.com/api/auth/sign-in-callback"], + logoutUrls: ["https://myapp.com/api/auth/sign-out-callback"], + }, + // highlight-end + }, +}); +``` + +This enables Amazon Cognito Hosted UI to support the server-side authentication flows. You may upgrade to the latest Amazon Cognito Managed Login Branding to customize the sign-in and sign-up pages. See [Amazon Cognito user pool managed login](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-managed-login.html) for more information. + +#### Step 5 - Use Anchor link for initiating server-side authentication flows + +Use HTML anchor links to navigate users to the sign-in and sign-up routes. For example: + +#### [Sign in button] +```tsx title="src/components/SignInButton.tsx" +export default function SignInButton() { + return ( + + Sign In + + ); +} +``` + +#### [Sign in with Google button] +```tsx title="src/components/SignInWithGoogleButton.tsx" +export default function SignInWithGoogleButton() { + return ( + + Sign In with Google + + ); +} +``` + +#### [Sign up button] +```tsx title="src/components/SignUpButton.tsx" +export default function SignUpButton() { + return ( + + Sign Up + + ); +} +``` + +#### [Sign out button] +```tsx title="src/components/SignOutButton.tsx" +export default function SignOutButton() { + return ( + + Sign Out + + ); +} +``` + +When an end user clicks on the buttons above, a corresponding server-side authentication flow will be initiated. + +### Validate user session with the Next.js Middleware + +You can use the `fetchAuthSession` API to check the auth sessions that are attached to the incoming requests in the middleware of your Next.js app to protect your routes. For example: + +```typescript title="src/middleware.ts" +import { fetchAuthSession } from 'aws-amplify/auth/server'; +import { NextRequest, NextResponse } from 'next/server'; +import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils'; + +export async function middleware(request: NextRequest) { + const response = NextResponse.next(); + + const authenticated = await runWithAmplifyServerContext({ + nextServerContext: { request, response }, + operation: async (contextSpec) => { + try { + const session = await fetchAuthSession(contextSpec); + return ( + session.tokens?.accessToken !== undefined && + session.tokens?.idToken !== undefined + ); + } catch (error) { + console.log(error); + return false; + } + } + }); + + if (authenticated) { + return response; + } + + return NextResponse.redirect(new URL('/sign-in', request.url)); +} + +export const config = { + matcher: [ + /* + * Match all request paths except for the ones starting with: + * - api (API routes) + * - _next/static (static files) + * - _next/image (image optimization files) + * - favicon.ico (favicon file) + */ + '/((?!api|_next/static|_next/image|favicon.ico|sign-in).*)' + ] +}; +``` + +In this example, if the incoming request is not associated with a valid user session the request will be redirected to the `/sign-in` route. + + + +**Note:** When calling `fetchAuthSession` with a `response` context, it will send the refreshed tokens (if any) back to the client via the `Set-Cookie` header in the response. + + + +## Calling Amplify category APIs on the server side + +For the **Auth** categories to use Amplify APIs on the server in your Next.js app, you will need to: + +1. Import the API from the `/server` sub path. +2. Use the `runWithAmplifyServerContext` helper function created by calling the `createServerRunner` function exported from `@aws-amplify/adapter-nextjs` to call the Amplify API in an isolated server context. + +For the **GraphQL API** category, review [Connect to data from Server-side Runtimes](/[platform]/build-a-backend/data/connect-from-server-runtime/). + + + +**Note:** A subset of Amplify APIs can now be called on the server side of a Next.js app. These APIs are exported from the `/server` sub paths. See [the full list](#supported-apis-for-nextjs-server-side-usage) of supported APIs. + + + + + +**Note:** If you use the Amplify server-side APIs in a [server action](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) and encounter the following error running `next build`: + +> ./node_modules/@aws-amplify/core/node_modules/@aws-crypto/sha256-js/build/module/index.js + 12 modules + +> Cannot get final name for export 'fromUtf8' of ./node_modules/@smithy/util-utf8/dist-es/index.js + +You can add the following to your `next.config.js`: + +```ts title="next.config.js" +/** @type {import('next').NextConfig} */ +const nextConfig = { + // highlight-start + serverComponentsPackages: ['@aws-crypto'], + // highlight-end +}; +``` + +See Next.js documentation on [`serverComponentsPackages`](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages) for more details. + + +### With Next.js App Router + +#### Dynamic rendering in React server component + +Dynamic rendering is based on a user session extracted from an incoming request. + +```jsx +import { cookies } from 'next/headers'; +import { getCurrentUser } from 'aws-amplify/auth/server'; +import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils'; + +// This page always dynamically renders per request +export const dynamic = 'force-dynamic'; + +export default async function AuthGetCurrentUserServer() { + try { + const currentUser = await runWithAmplifyServerContext({ + nextServerContext: { cookies }, + operation: (contextSpec) => getCurrentUser(contextSpec) + }); + + return ( +

    {`Hello, ${currentUser.username}`}

    + ); + } catch (error) { + console.error(error); + return

    Something went wrong...

    ; + } +} +``` + +#### Static rendering in React server component + +Static rendering does not require a user session, so you can specify the `nextServerContext` parameter as `null`. This is useful for some use cases; for example, when you are using the Storage API with guest access (if you have enabled it in your backend). + +```jsx +import { getUrl } from 'aws-amplify/storage/server'; +import Image from 'next/image'; +import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils'; + +// Re-render this page every 60 minutes +export const revalidate = 60 * 60; // in seconds + +export default async function StaticallyRenderedPage() { + try { + const splashUrl = await runWithAmplifyServerContext({ + nextServerContext: null, + operation: (contextSpec) => + getUrl(contextSpec, { + key: 'splash.png' + }) + }); + + return ( + Splash Image + ); + } catch (error) { + console.error(error); + return

    Something went wrong...

    ; + } +} +``` + + + +**Note:** The URL returned by the `getUrl` API expires in the above example. You may want to specify the `revalidate` parameter to rerender the page as required to ensure the URL gets regenerated. + + + +#### In Route Handlers + +In route handlers require implementing an API route that enables `GET /apis/get-current-user`. + +```typescript +import { getCurrentUser } from 'aws-amplify/auth/server'; +import { cookies } from 'next/headers'; +import { NextResponse } from 'next/server'; +import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils'; + +export async function GET() { + const user = await runWithAmplifyServerContext({ + nextServerContext: { cookies }, + operation: (contextSpec) => getCurrentUser(contextSpec) + }); + + return NextResponse.json({ user }); +} +``` + +When you call `fetch('/apis/get-current-user')` it returns a payload that contains the `user` data for the current signed-in user. + +### With Next.js Pages Router + +#### In `getServerSideProps` + +The following example extracts current user data from the request and provides them to a page react component via its props. + +```typescript +export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { + const currentUser = await runWithAmplifyServerContext({ + nextServerContext: { request: req, response: res }, + operation: (contextSpec) => getCurrentUser(contextSpec) + }); + + return { props: { currentUser } }; +}; +``` + +#### In `getStaticProps` + +Similar to static rendering with the App Router, you can pass `null` as the value of the `nextServerContext` parameter to use the Amplify Storage API with guest access. + +```typescript +export async function getStaticProps() { + const splashUrl = await runWithAmplifyServerContext({ + nextServerContext: null, + operation: (contextSpec) => getUrl(contextSpec, { key: 'splash.png' }) + }); + + return { + props: { imageUrl: splashUrl.url.toString() }, + revalidate: (splashUrl.expiresAt.getTime() - Date.now()) / 1000 // in seconds + }; +} +``` + +## Supported APIs for Next.js server-side usage + +All APIs that support use on the server are exported from the `aws-amplify//server` sub paths. You **must** use these APIs for any server-side use cases. + +| Category | APIs | Server (Node.js) Amplify Hosting/Vercel | Vercel Edge Runtime (middleware) | +| --- | --- | --- | --- | +| Auth | `fetchAuthSession` | βœ… | βœ… | +| Auth | `fetchUserAttributes` | βœ… | βœ… | +| Auth | `getCurrentUser` | βœ… | βœ… | +| Data | `generateServerClientUsingCookies` | βœ… | | +| Data | `generateServerClientUsingReqRes` | βœ… | | +| Storage | `getUrl` | βœ… | | +| Storage | `getProperties` | βœ… | | +| Storage | `list` | βœ… | | +| Storage | `remove` | βœ… | | +| Storage | `copy` | βœ… | | + +> **Info:** Have a server-side use case that isn't currently supported in Amplify JS? Consider using the [AWS SDK for JavaScript](https://aws.amazon.com/sdk-for-javascript/). + +--- + +--- +title: "Next.js App Router (Server Components)" +section: "build-a-backend/server-side-rendering" +platforms: ["javascript", "nextjs"] +gen: 2 +last-updated: "2024-11-12T21:56:50.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/server-side-rendering/nextjs-app-router-server-components/" +--- + +This Quickstart guide will walk you through how to build a task list application with TypeScript, Next.js **App Router with Server Components**, and React. If you are new to these technologies, we recommend you go through the official [React](https://react.dev/learn/tutorial-tic-tac-toe), [Next.js](https://nextjs.org/docs/app/getting-started), and [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html) tutorials first. + +## Build UI + +Let's add UI that connects to the backend data and auth resources. + +### Configure Amplify Client Side + +First, install the Amplify UI component library: + +```bash showLineNumbers={false} +npm add @aws-amplify/ui-react +``` + +Next, create a `components` folder in the root of your project and copy the contents below to a file called `ConfigureAmplify.tsx`. + +```ts title="components/ConfigureAmplify.tsx" +// components/ConfigureAmplify.tsx +"use client"; + +import { Amplify } from "aws-amplify"; + +import outputs from "@/amplify_outputs.json"; + +Amplify.configure(outputs, { ssr: true }); + +export default function ConfigureAmplifyClientSide() { + return null; +} +``` + +Update `app/layout.tsx` to import and render ``. This client component will configure Amplify for client pages in our application. + +```ts title="app/layout.tsx" +// app/layout.tsx +import "@aws-amplify/ui-react/styles.css"; +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +import ConfigureAmplifyClientSide from "@/components/ConfigureAmplify"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + {children} + + + ); +} +``` + +### Configure Amplify Server Side + +First, install the Amplify Next.js adapter: + +```bash showLineNumbers={false} +npm add @aws-amplify/adapter-nextjs +``` + +Next, create a `utils/amplify-utils.ts` file from the root of the project and paste the code below. `runWithAmplifyServerContext`, `cookiesClient`, and `AuthGetCurrentUserServer` are declared here and will be used to gain access to Amplify assets from the server. + +```ts title="utils/amplify-utils.ts" +// utils/amplify-utils.ts +import { cookies } from "next/headers"; + +import { createServerRunner } from "@aws-amplify/adapter-nextjs"; +import { generateServerClientUsingCookies } from "@aws-amplify/adapter-nextjs/api"; +import { getCurrentUser } from "aws-amplify/auth/server"; + +import { type Schema } from "@/amplify/data/resource"; +import outputs from "@/amplify_outputs.json"; + +export const { runWithAmplifyServerContext } = createServerRunner({ + config: outputs, +}); + +export const cookiesClient = generateServerClientUsingCookies({ + config: outputs, + cookies, +}); + +export async function AuthGetCurrentUserServer() { + try { + const currentUser = await runWithAmplifyServerContext({ + nextServerContext: { cookies }, + operation: (contextSpec) => getCurrentUser(contextSpec), + }); + return currentUser; + } catch (error) { + console.error(error); + } +} +``` + +### Add server authentication routes + +First, create a client-side `Login` component in the `components` folder that will be wrapped in `withAuthenticator`. If the user is logged in, they will be redirected to the index route; otherwise, the [Amplify UI Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator) will be rendered. + +```ts title="components/Login.tsx" +// components/Login.tsx +"use client"; + +import { withAuthenticator } from "@aws-amplify/ui-react"; +import { AuthUser } from "aws-amplify/auth"; +import { redirect } from "next/navigation"; +import { useEffect } from "react"; + +function Login({ user }: { user?: AuthUser }) { + useEffect(() => { + if (user) { + redirect("/"); + } + }, [user]); + return null; +} + +export default withAuthenticator(Login); +``` + +Next, create a new route under `app/login/page.tsx` to render the `Login` component. + +```ts title="app/login/page.tsx" +// app/login/page.tsx + +import Login from "@/components/Login"; + +export default function LoginPage() { + return ; +} +``` + +
    Custom example + +Some applications require more customization for the `` component. The following example shows how to add a custom Header to the ``. For this use, you will not need the `Login` file in the `components` folder, but can do everything through the `page` file in the `app/login/` folder. For more customization options, see [Authenticator Customization](https://ui.docs.amplify.aws/react/connected-components/authenticator/customization). + +```ts title="app/login/page.tsx" +// app/login/page.tsx - Custom + +"use client"; + +import { + Authenticator, + Text, + View, + useAuthenticator, +} from "@aws-amplify/ui-react"; +import { redirect } from "next/navigation"; +import { useEffect } from "react"; + +const components = { + Header() { + return ( + + Authenticator Header + + ); + }, +}; + +function CustomAuthenticator() { + const { user } = useAuthenticator((context) => [context.user]); + + useEffect(() => { + if (user) { + redirect("/"); + } + }, [user]); + + return ; +} + +export default function Login() { + return ( + + + + ); +} + +``` + +
    + +Finally, create a `Logout` component to be used later. + +```ts title="components/Logout.tsx" +// components/Logout.tsx + +"use client"; + +import { signOut } from "aws-amplify/auth"; +import { useRouter } from "next/navigation"; + +export default function Logout() { + const router = useRouter(); + + return ( + + ); +} +``` + +> **Info:** **Note**: If using [Amplify UI Social Providers](https://ui.docs.amplify.aws/react/connected-components/authenticator/configuration#social-providers), set the `callbackUrls` for the `/login` route when [configuring social sign-in for your Gen 2 backend](https://docs.amplify.aws/gen2/build-a-backend/auth/add-social-provider/#configure-social-sign-in-backend), as shown below. +> +> ```ts title="amplify/auth/resource.ts" +import { defineAuth, secret } from '@aws-amplify/backend'; + +export const auth = defineAuth({ + loginWith: { + externalProviders: { + // ... + callbackUrls: [ + 'http://localhost:3000/login', + 'https://mywebsite.com/login' + ], + logoutUrls: ['http://localhost:3000/logout', 'https://mywebsite.com/logout'] + } + } +}); +``` + +### Add middleware for server-side redirect + +Create `middleware.ts` in the root of the project with the contents below. + +This middleware runs `fetchAuthSession` wrapped in `runWithAmplifyServerContext` and will redirect to `/login` when a user is not logged in. + +```ts title="middleware.ts" +// middleware.ts +import { NextRequest, NextResponse } from "next/server"; + +import { fetchAuthSession } from "aws-amplify/auth/server"; + +import { runWithAmplifyServerContext } from "@/utils/amplify-utils"; + +export async function middleware(request: NextRequest) { + const response = NextResponse.next(); + + const authenticated = await runWithAmplifyServerContext({ + nextServerContext: { request, response }, + operation: async (contextSpec) => { + try { + const session = await fetchAuthSession(contextSpec, {}); + return session.tokens !== undefined; + } catch (error) { + console.log(error); + return false; + } + }, + }); + + if (authenticated) { + return response; + } + + return NextResponse.redirect(new URL("/login", request.url)); +} + +export const config = { + matcher: [ + /* + * Match all request paths except for the ones starting with: + * - api (API routes) + * - _next/static (static files) + * - _next/image (image optimization files) + * - favicon.ico (favicon file) + * - login + */ + "/((?!api|_next/static|_next/image|favicon.ico|login).*)", + ], +}; +``` + +Run your application with `npm run dev` and navigate to `http://localhost:3000`. You should now see the authenticator, which is already configured and ready for your first sign-up! Create a new user account, confirm the account through email, and then sign in. + +### View list of to-do items + +Now, let's display data on our app's frontend. + +The code below uses the `cookiesClient` to provide access to the `Todo` model defined in the backend. + +Modify your app's home page file, `app/page.tsx`, with the following code: + +```ts title="app/page.tsx" +// app/page.tsx + +import { cookiesClient } from "@/utils/amplify-utils"; + +async function App() { + const { data: todos } = await cookiesClient.models.Todo.list(); + + return ( + <> +

    Hello, Amplify πŸ‘‹

    +
      + {todos && todos.map((todo) =>
    • {todo.content}
    • )} +
    + + ); +} + +export default App; +``` + +Once you save the file and navigate back to `http://localhost:3000`, you should see "Hello, Amplify" with a blank page for now because you have only an empty list of to-dos. + +### Create a new to-do item + +Let's update the component to have a form for prompting the user for the title for creating a new to-do list item and run the `addTodo` method on form submission. In a production app, the additional fields of the `Todo` model would be added to the form. + +After creating a to-do, `revalidatePath` is run to clear the Next.js cache for this route to instantly update the results from the server without a full page reload. + +```ts title="app/page.tsx" +// app/page.tsx + +import { revalidatePath } from "next/cache"; + +import { AuthGetCurrentUserServer, cookiesClient } from "@/utils/amplify-utils"; + +import Logout from "@/components/Logout"; + +async function App() { + const user = await AuthGetCurrentUserServer(); + const { data: todos } = await cookiesClient.models.Todo.list(); + + async function addTodo(data: FormData) { + "use server"; + const title = data.get("title") as string; + await cookiesClient.models.Todo.create({ + content: title, + done: false, + priority: "medium", + }); + revalidatePath("/"); + } + + return ( + <> +

    Hello, Amplify πŸ‘‹

    + {user && } +
    + + +
    + +
      + {todos && todos.map((todo) =>
    • {todo.content}
    • )} +
    + + ); +} + +export default App; +``` + +### Terminate dev server + +Go to `localhost` in the browser to make sure you can now log in and create and list to-dos. You can end your development session by shutting down the frontend dev server and cloud sandbox. The sandbox prompts you to delete your backend resources. While you can retain your backend, we recommend deleting all resources so you can start clean again next time. + +--- + +--- +title: "Use Amplify categories APIs from Nuxt 3" +section: "build-a-backend/server-side-rendering" +platforms: ["javascript", "vue"] +gen: 2 +last-updated: "2025-03-12T00:03:13.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/server-side-rendering/nuxt/" +--- + +If you have not already done so, please read the introduction documentation, [Use Amplify Categories APIs in Server-Side Rendering](/gen1/[platform]/build-a-backend/server-side-rendering/), to learn about how to use Amplify categories' APIs in server-side rendering. + +This documentation provides a getting started guide to using the generic `runWithAmplifyServerContext` adapter (exported from `aws-amplify/adapter-core`) to enable Amplify in a Nuxt 3 project. The examples in this documentation may not present best practices for your Nuxt project. You are welcome to provide suggestions and contributions to improve this documentation or to create a Nuxt adapter package for Amplify and let others use it. + + + +**Note:** This guide assumes that you have deep knowledge of Nuxt 3. + + + +## Start using Amplify in your Nuxt 3 project + +You can install relevant Amplify libraries by following the [manual installation](/[platform]/start/manual-installation/) guide. + +## Set up the AmplifyAPIs plugin + +Nuxt 3 offers universal rendering by default, where your data fetching logic may be executed in both client and server runtimes. Amplify offers APIs that are capable of running within a server context to support use cases such as server-side rendering (SSR) and static site generation (SSG), though Amplify's client-side APIs and server-side APIs are slightly different. You can set up an `AmplifyAPIs` plugin to make your data fetching logic run smoothly across the client and server. + +1. If you haven’t already done so, create a `plugins` directory under the root of your Nuxt project +2. Create two files `01.amplifyApis.client.ts` and `01.amplifyApis.server.ts` under the `plugins` directory + + + +**Note:** the leading number in the filenames indicates the plugin loading order, details see https://nuxt.com/docs/guide/directory-structure/plugins#registration-order. The `.client` and `.server` indicate the runtime that the logic contained in the file will run on, client or server. For details see: https://nuxt.com/docs/guide/directory-structure/plugins + + + +In these files, you will register both client-specific and server-specific Amplify APIs that you will use in your Nuxt project as a plugin. You can then access these APIs via the `useNuxtApp` composable. + +### Implement `01.amplifyApis.client.ts` + +Example implementation: + +```ts title="plugins/01.amplifyApis.client.ts" +import type { Schema } from '~/amplify/data/resource'; +import { Amplify } from 'aws-amplify'; +import { + fetchAuthSession, + fetchUserAttributes, + signIn, + signOut +} from 'aws-amplify/auth'; +import { generateClient } from 'aws-amplify/api'; +import { list } from 'aws-amplify/storage'; + +import outputs from '../amplify_outputs.json'; + +const client = generateClient(); + +export default defineNuxtPlugin({ + name: 'AmplifyAPIs', + enforce: 'pre', + + setup() { + // This configures Amplify on the client side of your Nuxt app + Amplify.configure(outputs, { ssr: true }); + + return { + provide: { + // You can add the Amplify APIs that you will use on the client side + // of your Nuxt app here. + // + // You can call the API by via the composable `useNuxtApp()`. For example: + // `useNuxtApp().$Amplify.Auth.fetchAuthSession()` + Amplify: { + Auth: { + fetchAuthSession, + fetchUserAttributes, + signIn, + signOut + }, + Storage: { + list + }, + GraphQL: { + client + } + } + } + }; + } +}); +``` + + + +Make sure you call `Amplify.configure` as early as possible in your application’s lifecycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. Review the [Library Not Configured Troubleshooting guide](/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/) for possible causes of this issue. + + + +### Implement `01.amplifyApis.server.ts` + +Example implementation: + +```ts title="plugins/01.amplifyApis.server.ts" +import type { FetchAuthSessionOptions } from 'aws-amplify/auth'; +import type { ListPaginateWithPathInput } from 'aws-amplify/storage'; +import type { CookieRef } from 'nuxt/app'; +import type { Schema } from '~/amplify/data/resource'; + +import { + createAWSCredentialsAndIdentityIdProvider, + createKeyValueStorageFromCookieStorageAdapter, + createUserPoolsTokenProvider, + runWithAmplifyServerContext +} from 'aws-amplify/adapter-core'; +import { generateClient } from 'aws-amplify/api/server'; +import { + fetchAuthSession, + fetchUserAttributes, + getCurrentUser +} from 'aws-amplify/auth/server'; +import { list } from 'aws-amplify/storage/server'; +import { parseAmplifyConfig } from 'aws-amplify/utils'; + +import outputs from '../amplify_outputs.json'; + +// parse the content of `amplify_outputs.json` into the shape of ResourceConfig +const amplifyConfig = parseAmplifyConfig(outputs); + +// create the Amplify used token cookies names array +const userPoolClientId = amplifyConfig.Auth!.Cognito.userPoolClientId; +const lastAuthUserCookieName = `CognitoIdentityServiceProvider.${userPoolClientId}.LastAuthUser`; + +// create a GraphQL client that can be used in a server context +const gqlServerClient = generateClient({ config: amplifyConfig }); + +// extract the model operation function types for creating wrapper function later +type RemoveFirstParam = Params extends [infer _, ...infer Rest] ? Rest : never; +type TodoListInput = RemoveFirstParam>; +type TodoCreateInput = RemoveFirstParam>; +type TodoUpdateInput = RemoveFirstParam>; + +const getAmplifyAuthKeys = (lastAuthUser: string) => + ['idToken', 'accessToken', 'refreshToken', 'clockDrift'] + .map( + (key) => + `CognitoIdentityServiceProvider.${userPoolClientId}.${lastAuthUser}.${key}` + ) + .concat(lastAuthUserCookieName); + +// define the plugin +export default defineNuxtPlugin({ + name: 'AmplifyAPIs', + enforce: 'pre', + setup() { + // The Nuxt composable `useCookie` is capable of sending cookies to the + // client via the `SetCookie` header. If the `expires` option is left empty, + // it sets a cookie as a session cookie. If you need to persist the cookie + // on the client side after your end user closes your Web app, you need to + // specify an `expires` value. + // + // We use 30 days here as an example (the default Cognito refreshToken + // expiration time). + const expires = new Date(); + expires.setDate(expires.getDate() + 30); + + // Get the last auth user cookie value + // + // We use `sameSite: 'lax'` in this example, which allows the cookie to be + // sent to your Nuxt server when your end user gets redirected to your Web + // app from a different domain. You should choose an appropriate value for + // your own use cases. + const lastAuthUserCookie = useCookie(lastAuthUserCookieName, { + sameSite: 'lax', + expires, + secure: true + }); + + // Get all Amplify auth token cookie names + const authKeys = lastAuthUserCookie.value + ? getAmplifyAuthKeys(lastAuthUserCookie.value) + : []; + + // Create a key-value map of cookie name => cookie ref + // + // Using the composable `useCookie` here in the plugin setup prevents + // cross-request pollution. + const amplifyCookies = authKeys + .map((name) => ({ + name, + cookieRef: useCookie(name, { sameSite: 'lax', expires, secure: true }) + })) + .reduce>>( + (result, current) => ({ + ...result, + [current.name]: current.cookieRef + }), + {} + ); + + // Create a key value storage based on the cookies + // + // This key value storage is responsible for providing Amplify Auth tokens to + // the APIs that you are calling. + // + // If you implement the `set` method, when Amplify needed to refresh the Auth + // tokens on the server side, the new tokens would be sent back to the client + // side via `SetCookie` header in the response. Otherwise the refresh tokens + // would not be propagate to the client side, and Amplify would refresh + // the tokens when needed on the client side. + // + // In addition, if you decide not to implement the `set` method, you don't + // need to pass any `CookieOptions` to the `useCookie` composable. + const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter({ + get(name) { + const cookieRef = amplifyCookies[name]; + + if (cookieRef && cookieRef.value) { + return { name, value: cookieRef.value }; + } + + return undefined; + }, + getAll() { + return Object.entries(amplifyCookies).map(([name, cookieRef]) => { + return { name, value: cookieRef.value ?? undefined }; + }); + }, + set(name, value) { + const cookieRef = amplifyCookies[name]; + if (cookieRef) { + cookieRef.value = value; + } + }, + delete(name) { + const cookieRef = amplifyCookies[name]; + + if (cookieRef) { + cookieRef.value = null; + } + } + }); + + // Create a token provider + const tokenProvider = createUserPoolsTokenProvider( + amplifyConfig.Auth!, + keyValueStorage + ); + + // Create a credentials provider + const credentialsProvider = createAWSCredentialsAndIdentityIdProvider( + amplifyConfig.Auth!, + keyValueStorage + ); + + // Create the libraryOptions object + const libraryOptions = { + Auth: { + tokenProvider, + credentialsProvider + } + }; + + return { + provide: { + // You can add the Amplify APIs that you will use on the server side of + // your Nuxt app here. You must only use the APIs exported from the + // `aws-amplify//server` subpaths. + // + // You can call the API by via the composable `useNuxtApp()`. For example: + // `useNuxtApp().$Amplify.Auth.fetchAuthSession()` + // + // Recall that Amplify server APIs are required to be called in a isolated + // server context that is created by the `runWithAmplifyServerContext` + // function. + Amplify: { + Auth: { + fetchAuthSession: (options: FetchAuthSessionOptions) => + runWithAmplifyServerContext( + amplifyConfig, + libraryOptions, + (contextSpec) => fetchAuthSession(contextSpec, options) + ), + fetchUserAttributes: () => + runWithAmplifyServerContext( + amplifyConfig, + libraryOptions, + (contextSpec) => fetchUserAttributes(contextSpec) + ), + getCurrentUser: () => + runWithAmplifyServerContext( + amplifyConfig, + libraryOptions, + (contextSpec) => getCurrentUser(contextSpec) + ) + }, + Storage: { + list: (input: ListPaginateWithPathInput) => + runWithAmplifyServerContext( + amplifyConfig, + libraryOptions, + (contextSpec) => list(contextSpec, input) + ) + }, + GraphQL: { + client: { + models: { + Todo: { + list(...input: TodoListInput) { + return runWithAmplifyServerContext( + amplifyConfig, + libraryOptions, + (contextSpec) => gqlServerClient.models.Todo.list(contextSpec, ...input) + ) + }, + create(...input: TodoCreateInput) { + return runWithAmplifyServerContext( + amplifyConfig, + libraryOptions, + (contextSpec) => gqlServerClient.models.Todo.create(contextSpec, ...input) + ) + }, + update(...input: TodoUpdateInput) { + return runWithAmplifyServerContext( + amplifyConfig, + libraryOptions, + (contextSpec) => gqlServerClient.models.Todo.update(contextSpec, ...input) + ) + } + } + } + } + } + } + } + }; + } +}); +``` + +#### Usage example + +Using the Storage `list` API in `pages/storage-list.vue`: + +```ts title="pages/storage-list.vue" + +// `useAsyncData` and `useNuxtApp` are Nuxt composables +// `$Amplify` is generated by Nuxt according to the `provide` key in the plugins +// we've added above + + + +``` + +Using the GraphQL API in `pages/todos-list.vue`: + +```ts title="pages/todos-list.vue" + + + +``` + +The above two pages can be rendered on both the client and server by default. `useNuxtApp().$Amplify` will pick up the correct implementation of `01.amplifyApis.client.ts` and `01.amplifyApis.server.ts` to use, depending on the runtime. + +> **Warning:** Only a subset of Amplify APIs are usable on the server side, and as the libraries evolve, `amplify-apis.client` and `amplify-apis.server` may diverge further. You can guard your API calls to ensure an API is available in the context where you use it (e.g., you can use `if (process.client)` to ensure that a client-only API isn't executed on the server). + +## Set up Auth middleware to protect your routes + +The auth middleware will use the plugin configured in the previous step as a dependency; therefore you can add the auth middleware via another plugin that will be loaded after the previous one. + +1. Create a `02.authRedirect.ts` file under plugins directory + + + +**Note:** This file will run on both client and server, details see: https://nuxt.com/docs/guide/directory-structure/middleware#when-middleware-runs. The `02` name prefix ensures this plugin loads after the previous so `useNuxtApp().$Amplify` becomes available. + + + +### Implement `02.authRedirect.ts` + +Example implementation: + +```ts title="plugins/02.authRedirect.ts" +import { Amplify } from 'aws-amplify'; + +import outputs from '~/amplify_outputs.json'; + +// Amplify.configure() only needs to be called on the client side +if (process.client) { + Amplify.configure(outputs, { ssr: true }); +} + +export default defineNuxtPlugin({ + name: 'AmplifyAuthRedirect', + enforce: 'pre', + setup() { + addRouteMiddleware( + 'AmplifyAuthMiddleware', + defineNuxtRouteMiddleware(async (to) => { + try { + const session = await useNuxtApp().$Amplify.Auth.fetchAuthSession(); + + // If the request is not associated with a valid user session + // redirect to the `/sign-in` route. + // You can also add route match rules against `to.path` + if (session.tokens === undefined && to.path !== '/sign-in') { + return navigateTo('/sign-in'); + } + + if (session.tokens !== undefined && to.path === '/sign-in') { + return navigateTo('/'); + } + } catch (e) { + if (to.path !== '/sign-in') { + return navigateTo('/sign-in'); + } + } + }), + { global: true } + ); + } +}); +``` + + + +Make sure you call `Amplify.configure` as early as possible in your application’s life-cycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. Review the [Library Not Configured Troubleshooting guide](/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/) for possible causes of this issue. + + + +## Set Up Amplify for API Route Use Cases + +Following the specification of Nuxt, your API route handlers will live under `~/server`, which is a separate environment from other parts of your Nuxt app; hence, the plugins created in the previous sections are not usable here, and extra work is required. + +### Set up Amplify server context utility + +1. If you haven’t already done so, create a `utils` directory under the server directory of your Nuxt project +2. Create an `amplifyUtils.ts` file under the `utils` directory + +In this file, you will create a helper function to call Amplify APIs that are capable of running on the server side with context isolation. + +Example implementation: + +```ts title="utils/amplifyUtils.ts" +import type { H3Event, EventHandlerRequest } from 'h3'; + +import { + AmplifyServer, + CookieStorage, + createAWSCredentialsAndIdentityIdProvider, + createKeyValueStorageFromCookieStorageAdapter, + createUserPoolsTokenProvider, + runWithAmplifyServerContext, +} from 'aws-amplify/adapter-core'; +import { parseAmplifyConfig } from 'aws-amplify/utils'; + +import outputs from '~/amplify_outputs.json'; + +const amplifyConfig = parseAmplifyConfig(outputs); + +const createCookieStorageAdapter = ( + event: H3Event +): CookieStorage.Adapter => { + // `parseCookies`, `setCookie` and `deleteCookie` are Nuxt provided functions + const readOnlyCookies = parseCookies(event); + + return { + get(name) { + if (readOnlyCookies[name]) { + return { name, value: readOnlyCookies[name] }; + } + }, + set(name, value, options) { + setCookie(event, name, value, options); + }, + delete(name) { + deleteCookie(event, name); + }, + getAll() { + return Object.entries(readOnlyCookies).map(([name, value]) => { + return { name, value }; + }); + } + }; +}; + +const createLibraryOptions = ( + event: H3Event +) => { + const cookieStorage = createCookieStorageAdapter(event); + const keyValueStorage = + createKeyValueStorageFromCookieStorageAdapter(cookieStorage); + const tokenProvider = createUserPoolsTokenProvider( + amplifyConfig.Auth!, + keyValueStorage + ); + const credentialsProvider = createAWSCredentialsAndIdentityIdProvider( + amplifyConfig.Auth!, + keyValueStorage + ); + + return { + Auth: { + tokenProvider, + credentialsProvider + } + }; +}; + +export const runAmplifyApi = ( + // we need the event object to create a context accordingly + event: H3Event, + operation: ( + contextSpec: AmplifyServer.ContextSpec + ) => Result | Promise +) => { + return runWithAmplifyServerContext( + amplifyConfig, + createLibraryOptions(event), + operation + ); +}; +``` + +You can then use `runAmplifyApi` function to call Amplify APIs in an isolated server context. + +#### Usage example + +Take implementing an API route `GET /api/current-user` , in `server/api/current-user.ts`: + +```ts title="server/api/current-user.ts" +import { getCurrentUser } from 'aws-amplify/auth/server'; + +import { runAmplifyApi } from '~/server/utils/amplifyUtils'; + +export default defineEventHandler(async (event) => { + const user = await runAmplifyApi(event, (contextSpec) => + getCurrentUser(contextSpec) + ); + + return user; +}); +``` + +Then you can fetch data from this route, for example, `fetch('http://localhost:3000/api/current-user')`. + +## Set up server middleware to protect your API routes + +Similar to API routes, the previously added auth middleware are not usable under `/server`, hence extra work is required to set up a auth middleware to protect your routes. + +1. If you haven’t already done so, create a `middleware` directory under the `server` directory of your Nuxt project +2. Create an `amplifyAuthMiddleware.ts` file under the `middleware` directory + +This middleware will be executed before a request reach your API route. + +Example implementation: + +```ts title="middleware/amplifyAuthMiddleware.ts" +import { fetchAuthSession } from 'aws-amplify/auth/server'; + +export default defineEventHandler(async (event) => { + if (event.path.startsWith('/api/')) { + try { + const session = await runAmplifyApi(event, (contextSpec) => + fetchAuthSession(contextSpec) + ); + + // You can add extra logic to match the requested routes to apply + // the auth protection + if (session.tokens === undefined) { + setResponseStatus(event, 403); + return { + error: 'Access denied!' + }; + } + } catch (error) { + return { + error: 'Access denied!' + }; + } + } +}); +``` + +With this middleware, when executing `fetch('http://localhost:3000/api/current-user')` without signing in a user on the client side, the `fetch` will receive a 403 error, and the request won’t reach route `/api/current-user`. + +--- + +--- +title: "Add any AWS service" +section: "build-a-backend" +platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] +gen: 2 +last-updated: "2024-02-21T20:06:17.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/" +--- + + + +--- + +--- +title: "Analytics" +section: "build-a-backend/add-aws-services" +platforms: ["javascript", "react-native", "swift", "android", "flutter", "angular", "nextjs", "react", "vue"] +gen: 2 +last-updated: "2024-04-08T21:54:24.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/analytics/" +--- + + + +--- + +--- +title: "Set up Amplify Analytics" +section: "build-a-backend/add-aws-services/analytics" +platforms: ["javascript", "react-native", "swift", "android", "flutter", "angular", "nextjs", "react", "vue"] +gen: 2 +last-updated: "2025-11-13T16:29:27.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/analytics/set-up-analytics/" +--- + +Amplify enables you to collect analytics data for your app. In order to use Analytics, you will enable [Amazon Kinesis](https://aws.amazon.com/kinesis/) or [Amazon Pinpoint](https://aws.amazon.com/pinpoint/) using the AWS Cloud Development Kit (AWS CDK). The Analytics category uses [Amazon Cognito identity pools](https://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html) to _identify_ users in your app. Cognito allows you to receive data from authenticated, and unauthenticated users in your app. + + +## Prerequisites + +An application with Amplify libraries integrated and a minimum target of any of the following: +- **iOS 13.0**, using **Xcode 14.1** or later. +- **macOS 10.15**, using **Xcode 14.1** or later. +- **tvOS 13.0**, using **Xcode 14.3** or later. +- **watchOS 9.0**, using **Xcode 14.3** or later. +- **visionOS 1.0**, using **Xcode 15** or later. (Preview support - see below for more details.) + + + +visionOS support is currently in **preview** and can be used by using the latest [Amplify Release](https://github.com/aws-amplify/amplify-swift/releases). +As new Xcode and visionOS versions are released, the support will be updated with any necessary fixes on a best effort basis. + + + + + +## Prerequisites + +* An Android application targeting Android API level 24 (Android 7.0) or above + + + +Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Refer to [Flutter's supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) when targeting Web, Windows, or Linux. + +## Set up Analytics backend + +Use the [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/home.html) to create an analytics resource powered by [Amazon Pinpoint](https://aws.amazon.com/pinpoint/). + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend" +import { auth } from "./auth/resource"; +import { data } from "./data/resource"; +import { Policy, PolicyStatement } from "aws-cdk-lib/aws-iam"; +import { CfnApp } from "aws-cdk-lib/aws-pinpoint"; +import { Stack } from "aws-cdk-lib/core"; + +const backend = defineBackend({ + auth, + data, + // additional resources +}); + +const analyticsStack = backend.createStack("analytics-stack"); + +// create a Pinpoint app +const pinpoint = new CfnApp(analyticsStack, "Pinpoint", { + name: "myPinpointApp", +}); + +// create an IAM policy to allow interacting with Pinpoint +const pinpointPolicy = new Policy(analyticsStack, "PinpointPolicy", { + policyName: "PinpointPolicy", + statements: [ + new PolicyStatement({ + actions: ["mobiletargeting:UpdateEndpoint", "mobiletargeting:PutEvents"], + resources: [pinpoint.attrArn + "/*"], + }), + ], +}); + +// apply the policy to the authenticated and unauthenticated roles +backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy(pinpointPolicy); +backend.auth.resources.unauthenticatedUserIamRole.attachInlinePolicy(pinpointPolicy); + +// patch the custom Pinpoint resource to the expected output configuration +backend.addOutput({ + analytics: { + amazon_pinpoint: { + app_id: pinpoint.ref, + aws_region: Stack.of(pinpoint).region, + } + }, +}); +``` + +## Install Amplify Libraries + + +First, install the `aws-amplify` library: + +```sh title="Terminal" showLineNumbers={false} +npm add aws-amplify +``` + + + +1. To install the Amplify Libraries in your application, open your project in Xcode and select **File > Add Packages...**. + +2. Enter the **Amplify Library for Swift** GitHub repo URL (`https://github.com/aws-amplify/amplify-swift`) into the search bar and click **Add Package**. + + + + **Note:** **Up to Next Major Version** should be selected from the **Dependency Rule** dropdown. + + +3. Lastly, add **AWSPinpointAnalyticsPlugin**, **AWSCognitoAuthPlugin**, and **Amplify** to your target. Then click **Add Package**. + + + +Expand **Gradle Scripts**, open **build.gradle.kts (Module :app)**. You will already have configured Amplify by following the steps in the quickstart guide. + +Add Analytics by adding these libraries into the dependencies block: +```kotlin title="app/build.gradle.kts" +android { + compileOptions { + // Support for modern Java features + isCoreLibraryDesugaringEnabled = true + } +} + +dependencies { + // Amplify API dependencies + // highlight-start + implementation("com.amplifyframework:aws-analytics-pinpoint:ANDROID_VERSION") + implementation("com.amplifyframework:aws-auth-cognito:ANDROID_VERSION") + // highlight-end + // ... other dependencies + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:ANDROID_DESUGAR_VERSION") +} +``` + +Click **Sync Now**. + + + +In your Flutter project directory, open **pubspec.yaml**. +Add Analytics by adding these libraries into your dependencies block: + +```yaml +dependencies: + amplify_analytics_pinpoint: ^2.0.0 + amplify_auth_cognito: ^2.0.0 + amplify_flutter: ^2.0.0 +``` + + +## Initialize Amplify Analytics + +Import and load the configuration file in your app. It's recommended you add the Amplify configuration step to your app's root entry point. + + +```js title="src/index.js" +import { Amplify } from 'aws-amplify'; +import outputs from '../amplify_outputs.json'; + +Amplify.configure(outputs); +``` + + + +```js title="pages/_app.tsx" +import { Amplify } from 'aws-amplify'; +import outputs from '@/amplify_outputs.json'; + +Amplify.configure(outputs); +``` + + + +> **Warning:** Make sure to generate the `amplify_outputs.json` file by running the following command: +> +> ```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` +> +> Next, move the file to your project. You can do this by dragging and dropping the file into your Xcode project. + +To use the Amplify Analytics and Authentication categories in your app, you need to create and configure their corresponding plugins by calling the `Amplify.add(plugin:)` and `Amplify.configure(with:)` methods. + +#### [SwiftUI] + +Add the following imports to the top of your main `App` file: + +```swift +import Amplify +import AWSCognitoAuthPlugin +import AWSPinpointAnalyticsPlugin +``` + +Add the following code to its initializer. If there is none, you can create a default `init`: + +```swift +init() { + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.add(plugin: AWSPinpointAnalyticsPlugin()) + try Amplify.configure(with: .amplifyOutputs) + print("Amplify configured with Auth and Analytics plugins") + } catch { + print("Failed to initialize Amplify with \(error)") + } +} +``` + +#### [UIKit] + +Add the following imports to the top of your `AppDelegate.swift` file: + +```swift +import Amplify +import AWSCognitoAuthPlugin +import AWSPinpointAnalyticsPlugin +``` + +Add the following code to the `application:didFinishLaunchingWithOptions` method: + +```swift +func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? +) -> Bool { + do { + try Amplify.add(plugin: AWSCognitoAuthPlugin()) + try Amplify.add(plugin: AWSPinpointAnalyticsPlugin()) + try Amplify.configure(with: .amplifyOutputs) + print("Amplify configured with Auth and Analytics plugins") + } catch { + print("Failed to initialize Amplify with \(error)") + } + + return true +} +``` + +Upon building and running this application you should see the following in your console window: + +```console +Amplify configured with Auth and Analytics plugin +``` + + + +Add the Auth and Analytics plugin, along with any other plugins you may have added as described in the **Project Setup** section; + +```dart +import 'package:amplify_analytics_pinpoint/amplify_analytics_pinpoint.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; + +import 'amplify_outputs.dart'; + +Future _configureAmplify() async { + // Add Pinpoint and Cognito Plugins, and any other plugins you want to use + final analyticsPlugin = AmplifyAnalyticsPinpoint(); + final authPlugin = AmplifyAuthCognito(); + await Amplify.addPlugins([analyticsPlugin, authPlugin]); +} +``` + + + +When running your app on macOS you will need to enable keychain sharing in Xcode, as described in the [Project setup guide](/gen1/[platform]/start/project-setup/platform-setup/#enable-keychain). + + + +Make sure that the amplify_outputs.dart file generated in the project setup is included and sent to Amplify.configure: + +```dart +import 'package:amplify_analytics_pinpoint/amplify_analytics_pinpoint.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:flutter/material.dart'; + +import 'amplify_outputs.dart'; + +Future _configureAmplify() async { + // ... + await Amplify.addPlugins([analyticsPlugin, authPlugin]); + + // Once Plugins are added, configure Amplify + // Note: Amplify can only be configured once. + try { + await Amplify.configure(amplifyConfig); + } on AmplifyAlreadyConfiguredException { + safePrint( + 'Tried to reconfigure Amplify; this can occur when your app restarts on Android.', + ); + } +} +``` + +Your class will look like this: + +```dart +import 'package:amplify_flutter/amplify_flutter.dart'; +import 'package:amplify_analytics_pinpoint/amplify_analytics_pinpoint.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:flutter/material.dart'; + +import 'amplify_outputs.dart'; + +Future _configureAmplify() async { + // Add any Amplify plugins you want to use + final analyticsPlugin = AmplifyAnalyticsPinpoint(); + final authPlugin = AmplifyAuthCognito(); + await Amplify.addPlugins([analyticsPlugin, authPlugin]); + + // Once Plugins are added, configure Amplify + // Note: Amplify can only be configured once. + try { + await Amplify.configure(amplifyConfig); + } on AmplifyAlreadyConfiguredException { + safePrint( + 'Tried to reconfigure Amplify; this can occur when your app restarts on Android.', + ); + } +} + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await _configureAmplify(); + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({Key? key}): super(key: key); + + // ... +} +``` + + + +To initialize the Amplify Auth and Analytics categories you call `Amplify.addPlugin()` method for each category. To complete initialization call `Amplify.configure()`. + +Add the following code to your `onCreate()` method in your application class: + +> **Warning:** Before calling the `Amplify.configure` function, make sure to either download the `amplify_outputs.json` file from the console, or generate it with the following command: +> +> ```bash title="Terminal" showLineNumbers={false} +npx ampx generate outputs --app-id --branch main --out-dir app/src/main/res/raw +``` +> +> Next, be sure the file you generated or downloaded is in the appropriate resource directory for your application (for example, `app/src/main/res/raw`) in your Android project. Otherwise, you will not be able to compile your application. + +#### [Java] + +```java +import android.util.Log; +import com.amplifyframework.AmplifyException; +import com.amplifyframework.analytics.pinpoint.AWSPinpointAnalyticsPlugin; +import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin; +import com.amplifyframework.core.Amplify; +import com.amplifyframework.core.configuration.AmplifyOutputs; + +``` + +```java +Amplify.addPlugin(new AWSCognitoAuthPlugin()); +Amplify.addPlugin(new AWSPinpointAnalyticsPlugin()); +``` + +Your class will look like this: + +```java +public class MyAmplifyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + try { + // Add these lines to add the AWSCognitoAuthPlugin and AWSPinpointAnalyticsPlugin plugins + Amplify.addPlugin(new AWSCognitoAuthPlugin()); + Amplify.addPlugin(new AWSPinpointAnalyticsPlugin()); + Amplify.configure(AmplifyOutputs.fromResource(R.raw.amplify_outputs), getApplicationContext()); + + Log.i("MyAmplifyApp", "Initialized Amplify"); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error); + } + } +} +``` + +#### [Kotlin] + +```kotlin +import android.util.Log +import com.amplifyframework.AmplifyException +import com.amplifyframework.analytics.pinpoint.AWSPinpointAnalyticsPlugin +import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin +import com.amplifyframework.core.Amplify +import com.amplifyframework.core.configuration.AmplifyOutputs +``` + +```kotlin +Amplify.addPlugin(AWSCognitoAuthPlugin()) +Amplify.addPlugin(AWSPinpointAnalyticsPlugin()) +``` + +Your class will look like this: + +```kotlin +class MyAmplifyApp : Application() { + override fun onCreate() { + super.onCreate() + + try { + // Add these lines to add the AWSCognitoAuthPlugin and AWSPinpointAnalyticsPlugin plugins + Amplify.addPlugin(AWSCognitoAuthPlugin()) + Amplify.addPlugin(AWSPinpointAnalyticsPlugin()) + Amplify.configure(AmplifyOutputs.fromResource(R.raw.amplify_outputs), applicationContext) + + Log.i("MyAmplifyApp", "Initialized Amplify") + } catch (error: AmplifyException) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error) + } + } +} +``` + +#### [RxJava] + +```java +import android.util.Log; +import com.amplifyframework.AmplifyException; +import com.amplifyframework.analytics.pinpoint.AWSPinpointAnalyticsPlugin; +import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin; +import com.amplifyframework.core.configuration.AmplifyOutputs; +import com.amplifyframework.rx.RxAmplify; +``` + +```java +RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); +RxAmplify.addPlugin(new AWSPinpointAnalyticsPlugin()); +``` + +Your class will look like this: + +```java +public class MyAmplifyApp extends Application { + @Override + public void onCreate() { + super.onCreate(); + + try { + // Add these lines to add the AWSCognitoAuthPlugin and AWSPinpointAnalyticsPlugin plugins + RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); + RxAmplify.addPlugin(new AWSPinpointAnalyticsPlugin()); + RxAmplify.configure(AmplifyOutputs.fromResource(R.raw.amplify_outputs), getApplicationContext()); + + Log.i("MyAmplifyApp", "Initialized Amplify"); + } catch (AmplifyException error) { + Log.e("MyAmplifyApp", "Could not initialize Amplify", error); + } + } +} +``` + + + +Next Steps: + +Congratulations! Now that you have Analytics' backend provisioned and Analytics library installed. Check out the following links to see Amplify Analytics use cases: + +- [Record Events](/[platform]/build-a-backend/add-aws-services/analytics/record-events/) +- [Track Sessions](/[platform]/build-a-backend/add-aws-services/analytics/auto-track-sessions/) +- [Identify User](/[platform]/build-a-backend/add-aws-services/analytics/identify-user/) + +### References + +[Amazon Pinpoint Construct Library](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_pinpoint-readme.html) + + +## Known Issues + +You may encounter the following error when starting the bundler when using Amazon Kinesis (`aws-amplify/analytics/kinesis`), Amazon Kinesis Data Firehose (`aws-amplify/analytics/kinesis-firehose`), Personalize Event (`aws-amplify/analytics/personalize`): + +> Error: Unable to resolve module stream from /path/to/node_modules/@aws-sdk/... This is a [known issue](https://github.com/aws/aws-sdk-js-v3/issues/4877). Please follow [the steps](https://github.com/aws/aws-sdk-js-v3/issues/4877#issuecomment-1656007484) outlined in the issue to resolve the error. + + +--- + +--- +title: "Record events" +section: "build-a-backend/add-aws-services/analytics" +platforms: ["javascript", "react-native", "swift", "android", "flutter", "angular", "nextjs", "react", "vue"] +gen: 2 +last-updated: "2025-06-26T13:57:52.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/analytics/record-events/" +--- + + +## Record event + +The Amplify Analytics plugin makes it easy to record custom events within an app. The plugin handles retry logic in the event that the device loses network connectivity, and it automatically batches requests to reduce network bandwidth. + +#### [Java] + +```java +AnalyticsEvent event = AnalyticsEvent.builder() + .name("PasswordReset") + .addProperty("Channel", "SMS") + .addProperty("Successful", true) + .addProperty("ProcessDuration", 792) + .addProperty("UserAge", 120.3) + .build(); + +Amplify.Analytics.recordEvent(event); +``` + +#### [Kotlin] + +```kotlin +val event = AnalyticsEvent.builder() + .name("PasswordReset") + .addProperty("Channel", "SMS") + .addProperty("Successful", true) + .addProperty("ProcessDuration", 792) + .addProperty("UserAge", 120.3) + .build() + +Amplify.Analytics.recordEvent(event) +``` + +#### [RxJava] + +```java +AnalyticsEvent event = AnalyticsEvent.builder() + .name("PasswordReset") + .addProperty("Channel", "SMS") + .addProperty("Successful", true) + .addProperty("ProcessDuration", 792) + .addProperty("UserAge", 120.3) + .build(); + +RxAmplify.Analytics.recordEvent(event); +``` + + + +The Amazon Pinpoint event count updates in minutes after recording your event. + +However, it can take upwards of 30 minutes for the event to display in the Filter section, and for its custom attributes to appear in Pinpoint. + + + +## Flush events + +Events have a default configuration to flush out to the network every 30 seconds. You can change this value by passing the `autoFlushEventsInterval` option to the `AWSPinpointAnalyticsPlugin`. The option value is measured in milliseconds. + +```kotlin +val options = AWSPinpointAnalyticsPlugin.Options { + autoFlushEventsInterval = 60_000 +} +Amplify.addPlugin(AWSPinpointAnalyticsPlugin(options)) +Amplify.configure(AmplifyOutputs(R.raw.amplify_outputs), this) +``` + +To manually flush events, call: + +#### [Java] + +```java +Amplify.Analytics.flushEvents(); +``` + +#### [Kotlin] + +```kotlin +Amplify.Analytics.flushEvents() +``` + +#### [RxJava] + +```java +RxAmplify.Analytics.flushEvents(); +``` + +When flushing events, a [Hub event](/[platform]/build-a-backend/auth/connect-your-frontend/listen-to-auth-events/) is sent containing the events which were successfully sent to the Pinpoint service. To receive a list of these events, subscribe to the `HubChannel.ANALYTICS` channel and handle an event of the type `AnalyticsChannelEventName.FLUSH_EVENTS`. + +## Global Properties + +You can register global properties which will be sent along with all invocations of `Amplify.Analytics.recordEvent`. + +#### [Java] + +```java +Amplify.Analytics.registerGlobalProperties( + AnalyticsProperties.builder() + .add("AppStyle", "DarkMode") + .build()); +``` + +#### [Kotlin] + +```kotlin +Amplify.Analytics.registerGlobalProperties( + AnalyticsProperties.builder() + .add("AppStyle", "DarkMode") + .build()) +``` + +#### [RxJava] + +```java +RxAmplify.Analytics.registerGlobalProperties( + AnalyticsProperties.builder() + .add("AppStyle", "DarkMode") + .build()); +``` + +To unregister a global property, call `Amplify.Analytics.unregisterGlobalProperties()`: + +#### [Java] + +```java +Amplify.Analytics.unregisterGlobalProperties("AppStyle", "OtherProperty"); +``` + +#### [Kotlin] + +```kotlin +Amplify.Analytics.unregisterGlobalProperties("AppStyle", "OtherProperty") +``` + +#### [RxJava] + +```java +RxAmplify.Analytics.unregisterGlobalProperties("AppStyle", "OtherProperty"); +``` + + + + +## Record event + +The Amplify analytics plugin also makes it easy to record custom events within the app. The plugin handles retry logic in the event the device looses network connectivity and automatically batches requests to reduce network bandwidth. + +```dart +Future recordCustomEvent() async { + final event = AnalyticsEvent('PasswordReset'); + + event.customProperties + ..addStringProperty('Channel', 'SMS') + ..addBoolProperty('Successful', true); + + // You can also add the properties one by one like the following + event.customProperties.addIntProperty('ProcessDuration', 792); + event.customProperties.addDoubleProperty('doubleKey', 120.3); + + await Amplify.Analytics.recordEvent(event: event); +} +``` + + + +The Amazon Pinpoint event count updates in minutes after recording your event. + +However, it can take upwards of 30 minutes for the event to display in the Filter section, and for its custom attributes to appear in Amazon Pinpoint. + + + +## Flush events + +Events have default configuration to flush out to the network every 30 seconds. If you would like to change this, update `amplify_outputs.dart` with the value in milliseconds you would like for `autoFlushEventsInterval`. This configuration will flush events every 10 seconds: + +```json +{ + "Version": "1.0", + "analytics": { + "plugins": { + "awsPinpointAnalyticsPlugin": { + "pinpointAnalytics": { + "appId": "", + "region": "" + }, + "pinpointTargeting": { + "region": "" + }, + "autoFlushEventsInterval": 10 + } + } + } +} +``` + +> **Note** +> +> Setting `autoFlushEventsInterval` to 0 will **disable** the automatic flush of events and you will be responsible for submitting them. + +To manually flush events, call: + +```dart +await Amplify.Analytics.flushEvents(); +``` + +## Global Properties + +You can register global properties which will be sent along with all invocations of `Amplify.Analytics.recordEvent`. + +```dart +Future registerGlobalProperties() async { + final properties = CustomProperties() + ..addStringProperty('AppStyle', 'DarkMode'); + await Amplify.Analytics.registerGlobalProperties( + globalProperties: properties, + ); +} +``` + +To unregister a global property, call `Amplify.Analytics.unregisterGlobalProperties()`: + +```dart +Future unregisterGlobalProperties() async { + await Amplify.Analytics.unregisterGlobalProperties( + propertyNames: ['AppStyle', 'OtherProperty'], + ); +} +``` + +Furthermore, you can remove all global properties by calling `unregisterGlobalProperties` without `propertyNames`: + +```dart +Future unregisterAllGlobalProperties() async { + await Amplify.Analytics.unregisterGlobalProperties(); +} +``` + + + +## Record Event + +The Amplify Analytics plugin provides a simple interface to record custom events within your app: + +```swift +let properties: AnalyticsProperties = [ + "eventPropertyStringKey": "eventPropertyStringValue", + "eventPropertyIntKey": 123, + "eventPropertyDoubleKey": 12.34, + "eventPropertyBoolKey": true +] + +let event = BasicAnalyticsEvent( + name: "eventName", + properties: properties +) + +Amplify.Analytics.record(event: event) +``` + + + +The Amazon Pinpoint event count updates in minutes after recording your event. + +However, it can take upwards of 30 minutes for the event to display in the Filter section, and for its custom attributes to appear in Pinpoint. + + + +## Flush Events + +By default, events are automatically flushed out to the network every 60 seconds. + +You can change this through the `options` parameter when initializing the plugin, by creating a `AWSPinpointAnalyticsPlugin.Options` instance and setting its `autoFlushEventsInterval` property to the desired value, expressed in seconds: + +```swift +let options = AWSPinpointAnalyticsPlugin.Options( + autoFlushEventsInterval: 60 +) +try Amplify.add(plugin: AWSPinpointAnalyticsPlugin(options: options)) +``` + +> **Note** +> +> Setting `autoFlushEventsInterval` to 0 will **disable** the automatic flush of events and you will be responsible for submitting them. + +To manually submit the recoded events to the backend, call: + +```swift +Amplify.Analytics.flushEvents() +``` + +The plugin automatically batches requests in order to reduce network bandwidth and handles the retry logic if the device loses connectivity. + +## Authentication events + +Indicate how frequently users authenticate with your application. + +On the **Analytics** page, the **Users** tab displays charts for **Sign-ins, Sign-ups, and Authentication failures**. + +To learn how frequently users authenticate with your app, update your application code so that Pinpoint receives the following standard event types for authentication: + + - `_userauth.sign_in` + - `_userauth.sign_up` + - `_userauth.auth_fail` + +You can report these events by doing the following: + +```swift +let event = BasicAnalyticsEvent( + name: "_userauth.sign_in" // Or any of the accepted values +) +Amplify.Analytics.record(event: event) +``` + +## Global Properties + +You can register properties which will be included across all `Amplify.Analytics.record(event:)` calls. + +```swift +let globalProperties: AnalyticsProperties = [ + "globalPropertyKey": "value" +] +Amplify.Analytics.registerGlobalProperties(globalProperties) +``` + +To unregister global properties, call `Amplify.Analytics.unregisterGlobalProperties()`: + +```swift +// When called with no arguments, it unregisters all global properties +Amplify.Analytics.unregisterGlobalProperties() + +// Or you can specify which properties to unregister +let globalProperties = ["globalPropertyKey1", "globalPropertyKey2"] +Amplify.Analytics.unregisterGlobalProperties(globalProperties) +``` + + + +## Recording Custom Events + +To record custom events call the `record` API: + +```javascript title="src/index.js" +import { record } from 'aws-amplify/analytics'; + +record({ + name: 'albumVisit', +}); +``` + + + +Analytics events are buffered in memory and periodically sent to the service and not saved locally between application sessions. If the session is ended before a buffered event is sent, it will be lost. Use the `flushEvents` API to manually send buffered events to the service. + + + +## Record a Custom Event with Attributes + +The `record` API lets you add additional attributes to an event. For example, to record _artist_ information with an _albumVisit_ event: + +```javascript title="src/index.js" +import { record } from 'aws-amplify/analytics'; + +record({ + name: 'albumVisit', + attributes: { genre: '', artist: '' }, +}); +``` + +Recorded events will be buffered and periodically sent to Amazon Pinpoint. + +## Record Engagement Metrics + +Metrics can also be added to an event: + +```javascript title="src/index.js" +import { record } from 'aws-amplify/analytics'; + +record({ + name: 'albumVisit', + metrics: { minutesListened: 30 }, +}); +``` + +Metric values must be a `Number` type such as a float or integer. + + + +The Amazon Pinpoint event count updates in minutes after recording your event. + +However, it can take upwards of 30 minutes for the event to display in the Filter section, and for its custom attributes to appear in Amazon Pinpoint. + + + +## Flush events + +The recorded events are saved in a buffer and sent to the remote server periodically. If needed, you have the option to manually clear all the events from the buffer by using the 'flushEvents' API. + +```javascript title="src/index.js" +import { flushEvents } from 'aws-amplify/analytics'; + +flushEvents(); +``` + + +--- + +--- +title: "Identify user" +section: "build-a-backend/add-aws-services/analytics" +platforms: ["swift", "android", "flutter", "javascript", "react-native", "angular", "nextjs", "react", "vue"] +gen: 2 +last-updated: "2024-05-03T20:06:21.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/analytics/identify-user/" +--- + + +This call sends information that you have specified about the user to Amazon Pinpoint. This could be for an unauthenticated or an authenticated user. + +In addition, `customProperties` and `userAttributes` can also be provided when invoking `identifyUser`. The Amazon Pinpoint console makes that data available as part of the criteria for segment creation. Attributes passed in via `customProperties` will appear under **Custom Endpoint Attributes**, while `userAttributes` will appear under **Custom User Attributes**. See the [Amazon Pinpoint documentation](https://docs.aws.amazon.com/pinpoint/latest/userguide/segments-building.html#choosecriteria) for more information on segment creation. + +You can get the current user's ID from the Amplify Auth category as shown below. Be sure you have it added and setup per the [Auth category documentation](/[platform]/build-a-backend/auth/set-up-auth/). + +If you have asked for location access and received permission, you can also provide that in `UserProfile.Location`. + +#### [Java] + +```java +UserProfile.Location location = UserProfile.Location.builder() + .latitude(47.606209) + .longitude(-122.332069) + .postalCode("98122") + .city("Seattle") + .region("WA") + .country("USA") + .build(); + +AnalyticsProperties customProperties = AnalyticsProperties.builder() + .add("property1", "Property value") + .build(); + +AnalyticsProperties userAttributes = AnalyticsProperties.builder() + .add("someUserAttribute", "User attribute value") + .build(); + +AWSPinpointUserProfile profile = AWSPinpointUserProfile.builder() + .name("test-user") + .email("user@test.com") + .plan("test-plan") + .location(location) + .customProperties(customProperties) + .userAttributes(userAttributes) + .build(); + +Amplify.Auth.getCurrentUser(authUser -> { + String userId = authUser.getUserId(); + Amplify.Analytics.identifyUser(userId, profile); +}, exception -> { + Log.e("MyAmplifyApp", "Error getting current user", exception); +}); + +``` + +#### [Kotlin] + +```kotlin +val location = UserProfile.Location.builder() + .latitude(47.606209) + .longitude(-122.332069) + .postalCode("98122") + .city("Seattle") + .region("WA") + .country("USA") + .build(); + +val customProperties = AnalyticsProperties.builder() + .add("property1", "Property value") + .build(); + +val userAttributes = AnalyticsProperties.builder() + .add("someUserAttribute", "User attribute value") + .build(); + +val profile = AWSPinpointUserProfile.builder() + .name("test-user") + .email("user@test.com") + .plan("test-plan") + .location(location) + .customProperties(customProperties) + .userAttributes(userAttributes) + .build(); + +Amplify.Auth.getCurrentUser({ authUser -> + Amplify.Analytics.identifyUser(authUser.userId, profile); +}, { exception -> + Log.e("MyAmplifyApp", "Error getting current user", exception) +}) +``` + +#### [RxJava] + +```java +UserProfile.Location location = UserProfile.Location.builder() + .latitude(47.606209) + .longitude(-122.332069) + .postalCode("98122") + .city("Seattle") + .region("WA") + .country("USA") + .build(); + +AnalyticsProperties customProperties = AnalyticsProperties.builder() + .add("property1", "Property value") + .build(); + +AnalyticsProperties userAttributes = AnalyticsProperties.builder() + .add("someUserAttribute", "User attribute value") + .build(); + +AWSPinpointUserProfile profile = AWSPinpointUserProfile.builder() + .name("test-user") + .email("user@test.com") + .plan("test-plan") + .location(location) + .customProperties(customProperties) + .userAttributes(userAttributes) + .build(); + +RxAmplify.Auth.getCurrentUser() + .subscribe( + result -> { + String userId = result.getUserId(); + RxAmplify.Analytics.identifyUser(userId, profile); + }, + error -> Log.e("AuthQuickStart", error.toString()) + ); +``` + + + + +This call sends information about the current user (which could be unauthenticated or authenticated) to Amazon Pinpoint. + +You can provide `name`, `email` and `plan`, as well as location information with `AnalyticsUserProfile.Location`. You can also send additional custom attributes using `AnalyticsProperties`. + +If the user is signed in through [Amplify.Auth.signIn](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/), then you can retrieve the current user's ID as shown below: + +```swift + +let user = try await Amplify.Auth.getCurrentUser() + +let location = AnalyticsUserProfile.Location( + latitude: 47.606209, + longitude: -122.332069, + postalCode: "98122", + city: "Seattle", + region: "WA", + country: "USA" +) + +let properties: AnalyticsProperties = [ + "phoneNumber": "+11234567890", + "age": 25 +] + +let userProfile = AnalyticsUserProfile( + name: "username", + email: "name@example.com", + plan: "plan", + location: location, + properties: properties +) + +Amplify.Analytics.identifyUser( + userId: user.userId, + userProfile: userProfile +) +``` + + + +This call sends information that you have specified about a user to Amazon Pinpoint. This could be for an unauthenticated (guest) or an authenticated user. + +You can get the current user's ID from the Amplify Auth category as shown per the Auth category documentation. Be sure to have it ready before you set it as shown below (Check out the [Authentication Getting Started](/[platform]/build-a-backend/auth/set-up-auth/) guide for detailed explanation). + +If you have asked for location access and received permission, you can also provide that in `UserProfileLocation` + + + +Breaking changes from v0 to v1: + +The Analytics- prefix of the original `AnalyticsUserProfile` and `AnalyticsUserProfileLocation` classes is removed. Furthermore, `AnalyticsProperties` is renamed to `CustomProperties`. + + + +```dart +Future addAnalyticsWithLocation({ + required String userId, + required String name, + required String email, + required String phoneNumber, + required int age, +}) async { + final userProfile = UserProfile( + name: name, + email: email, + location: const UserProfileLocation( + latitude: 47.606209, + longitude: -122.332069, + postalCode: '98122', + city: 'Seattle', + region: 'WA', + country: 'USA', + ), + customProperties: CustomProperties() + ..addStringProperty('phoneNumber', phoneNumber) + ..addIntProperty('age', age), + ); + + await Amplify.Analytics.identifyUser( + userId: userId, + userProfile: userProfile, + ); +} +``` + + + +This API sends information about the current user to Amazon Pinpoint. + +Additional information such as the user's name, email, location, and device can be included by specifying the `UserProfile`. Custom attributes can also be included by setting `UserProfile.customProperties`. + +If the user was signed in through [signIn](/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/) you can retrieve the current user's ID as shown below: + +```js title="src/index.js" +import { identifyUser } from 'aws-amplify/analytics'; +import { getCurrentUser } from 'aws-amplify/auth'; + +const location = { + latitude: 47.606209, + longitude: -122.332069, + postalCode: '98122', + city: 'Seattle', + region: 'WA', + country: 'USA' +}; + +const customProperties = { + plan: ['plan'], + phoneNumber: ['+11234567890'], + age: ['25'] +}; + +const userProfile = { + location, + name: 'username', + email: 'name@example.com', + customProperties +}; + +async function sendUserData() { + const user = await getCurrentUser(); + + identifyUser({ + userId: user.userId, + userProfile + }); +} +``` + + +Sending user information allows you to associate a user to their user profile and activities or actions in your app. The user's actions and attributes can also tracked across devices and platforms by using the same `userId`. + +Some scenarios for identifying a user and their associated app activities are: +* When a user completes app sign up +* When a user completes sign in process +* When a user launches your app +* When a user modifies or updates their user profile + +--- + +--- +title: "Automatically track sessions" +section: "build-a-backend/add-aws-services/analytics" +platforms: ["javascript", "react-native", "swift", "android", "flutter", "angular", "nextjs", "react", "vue"] +gen: 2 +last-updated: "2024-04-16T15:18:17.000Z" +url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/analytics/auto-track-sessions/" +--- + + +Analytics auto tracking helps you to automatically track user behaviors like sessions' start/stop, page view change and web events like clicking or mouseover. + +## Session Tracking + +You can track the session both in a web app or a React Native app by using Analytics. A web session can be defined in different ways. To keep it simple, we define a web session as being active when the page is not hidden and inactive when the page is hidden. A session in a React Native app is active when the app is in the foreground and inactive when the app is in the background. + +For example: + +```javascript title="src/index.js" +import { configureAutoTrack } from 'aws-amplify/analytics'; + +configureAutoTrack({ + // REQUIRED, turn on/off the auto tracking + enable: true, + // REQUIRED, the event type, it's one of 'event', 'pageView' or 'session' + type: 'session', + // OPTIONAL, additional options for the tracked event. + options: { + // OPTIONAL, the attributes of the event + attributes: { + customizableField: 'attr' + } + } +}); +``` + +By default, when the page/app transitions to the foreground, the Analytics module will send an event to the Amazon Pinpoint Service. + +```json +{ + "eventType": "_session_start", + "attributes": { + "customizableField": "attr" + } +} +``` + +This behavior can be disabled by calling `configureAutoTrack`: + +```javascript title="src/index.js" +import { configureAutoTrack } from 'aws-amplify/analytics'; + +configureAutoTrack({ + enable: false, + type: 'session' +}); +``` + +## Page View Tracking + +Use this feature to track the most frequently viewed page/url in your webapp. It automatically sends events containing url information when a page is visited. + +This behavior can be enabled by calling `configureAutoTrack`: +```javascript title="src/index.js" +import { configureAutoTrack } from 'aws-amplify/analytics'; + +configureAutoTrack({ + // REQUIRED, turn on/off the auto tracking + enable: true, + // REQUIRED, the event type, it's one of 'event', 'pageView' or 'session' + type: 'pageView', + // OPTIONAL, additional options for the tracked event. + options: { + // OPTIONAL, the attributes of the event + attributes: { + customizableField: 'attr' + }, + + // OPTIONAL, the event name. By default, this is 'pageView' + eventName: 'pageView', + + // OPTIONAL, the type of app under tracking. By default, this is 'multiPageApp'. + // You will need to change it to 'singlePage' if your app is a single-page app like React + appType: 'multiPageApp', + + // OPTIONAL, provide the URL for the event. + urlProvider: () => { + // the default function + return window.location.origin + window.location.pathname; + } + } +}); +``` + +This behavior can be disabled by calling `configureAutoTrack`: +```javascript title="src/index.js" +import { configureAutoTrack } from 'aws-amplify/analytics'; + +configureAutoTrack({ + enable: false, + type: 'pageView' +}); +``` + +## Page Event Tracking + +Use page event tracking to track user interactions with specific elements on a page. Attach the specified selectors to your DOM element and turn on the auto tracking. + +This behavior can be enabled by calling `configureAutoTrack`: +```javascript title="src/index.js" +import { configureAutoTrack } from 'aws-amplify/analytics'; + +configureAutoTrack({ + // REQUIRED, turn on/off the auto tracking + enable: true, + // REQUIRED, the event type, it's one of 'event', 'pageView' or 'session' + type: 'event', + // OPTIONAL, additional options for the tracked event. + options: { + // OPTIONAL, the attributes of the event + attributes: { + customizableField: 'attr' + }, + // OPTIONAL, events you want to track. By default, this is 'click' + events: ['click'], + + // OPTIONAL, the prefix of the selectors. By default, this is 'data-amplify-analytics-' + // Per https://www.w3schools.com/tags/att_global_data.asp, always start + // the prefix with 'data' to avoid collisions with the user agent + selectorPrefix: 'data-amplify-analytics-' + } +}); +``` + +For example: + +```html + + -
      - {todos.map(todo =>
    • deleteTodo(todo.id)} - key={todo.id}> - {todo.content} -
    • )} -
    -
    - πŸ₯³ App successfully hosted. Try creating a new todo. -
    - Review next step of this tutorial. -
    - - ) -} -``` - -Try out the deletion functionality now by starting the local dev server: - -```bash title="Terminal" showLineNumbers={false} -npm run dev -``` - -This should start a local dev server at http://localhost:5173. - -### 6. Implement login UI - -The starter application already has a pre-configured auth backend defined in the **amplify/auth/resource.ts** file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. - -The fastest way to get your login experience up and running is to use our Authenticator UI component. In your **src/main.tsx** file, import the Authenticator UI component and wrap your `` component. - -```tsx title="src/main.tsx" -import React from 'react'; -import ReactDOM from 'react-dom/client'; -// highlight-next-line -import { Authenticator } from '@aws-amplify/ui-react'; -import { Amplify } from 'aws-amplify'; -import App from './App.tsx'; -import outputs from '../amplify_outputs.json'; -import './index.css'; -// highlight-next-line -import '@aws-amplify/ui-react/styles.css'; - -Amplify.configure(outputs); - -ReactDOM.createRoot(document.getElementById('root')!).render( - // highlight-start - - - - - - // highlight-end -); -``` - -The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. - -In your `src/App.tsx` file, add a button to enable users to sign out of the application. Import the [`useAuthenticator`](https://ui.docs.amplify.aws/react/connected-components/authenticator/advanced#access-auth-state) hook from the Amplify UI library to hook into the state of the Authenticator. - -```tsx title="src/App.tsx" -import type { Schema } from '../amplify/data/resource'; -// highlight-next-line -import { useAuthenticator } from '@aws-amplify/ui-react'; -import { useEffect, useState } from 'react'; -import { generateClient } from 'aws-amplify/data'; - -const client = generateClient(); - -function App() { - // highlight-next-line - const { signOut } = useAuthenticator(); - - // ... - - return ( -
    - {/* ... */} - // highlight-next-line - -
    - ); -} - -export default App; -``` - -Try out your application in your localhost environment again. You should be presented with a login experience now. - -To get these changes to the cloud, commit them to git and push the changes upstream. - -```bash title="Terminal" showLineNumbers={false} -git commit -am "added authenticator" -git push -``` - -Amplify automatically deploys the latest version of your app based on your git commits. In just a few minutes, when the application rebuilds, the hosted app will be updated to support the deletion functionality. - -## Make backend updates - -Let's update our backend to implement per-user authorization rules, allowing each user to only access their own to-dos. - -### 7. Set up local AWS credentials - -To make backend updates, we are going to require AWS credentials to deploy backend updates from our local machine. - -**Skip ahead to step 8**, if you already have an AWS profile with credentials on your local machine, and your AWS profile has the `AmplifyBackendDeployFullAccess` permission policy. - -Otherwise, **[set up local AWS credentials](/[platform]/start/account-setup/)** that grant Amplify permissions to deploy backend updates from your local machine. - -### 8. Deploy cloud sandbox - -To update your backend without affecting the production branch, use Amplify's cloud sandbox. This feature provides a separate backend environment for each developer on a team, ideal for local development and testing. - -To start your cloud sandbox, run the following command in a **new Terminal window**: - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox -``` - -Once the cloud sandbox has been fully deployed (~5 min), you'll see the `amplify_outputs.json` file updated with connection information to a new isolated authentication and data backend. - -> **Info:** The `npx ampx sandbox` command should run concurrently to your `npm run dev`. You can think of the cloud sandbox as the "localhost-equivalent for your app backend". - -### 9. Implement per-user authorization - -The to-do items in the starter are currently shared across all users, but, in most cases, you want data to be isolated on a per-user basis. - -To isolate the data on a per-user basis, you can use an "owner-based authorization rule". Let's apply the owner-based authorization rule to your to-do items: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a.model({ - content: a.string(), - // highlight-next-line - }).authorization(allow => [allow.owner()]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - // This tells the data client in your app (generateClient()) - // to sign API requests with the user authentication token. - // highlight-next-line - defaultAuthorizationMode: 'userPool', - }, -}); -``` - -In the application client code, let's also render the username to distinguish different users once they're logged in. Go to your **src/App.tsx** file and render the `user` property from the `useAuthenticator` hook. - -```tsx title="src/App.tsx" -// ... imports - -function App() { - // highlight-next-line - const { user, signOut } = useAuthenticator(); - - // ... - - return ( -
    - // highlight-next-line -

    {user?.signInDetails?.loginId}'s todos

    - {/* ... */} -
    - ) -} -``` - -Now, let's go back to your local application and test out the user isolation of the to-do items. - -You will need to sign up new users again because now you're working with the cloud sandbox instead of your production backend. - -To get these changes to the cloud, commit them to git and push the changes upstream. - -```bash title="Terminal" showLineNumbers={false} -git commit -am "added per-user data isolation" -git push -``` - -Once your build completes in the Amplify Console, the `main` backend will update to support the changes made within the cloud sandbox. The data in the cloud sandbox is fully isolated and won't pollute your production database. - - - -πŸ‘‹ Welcome to AWS Amplify! In this quickstart guide, you will: - -1. Deploy a Next.js app -2. Build and connect to a database with real-time data updates -3. Configure authentication and authorization rules - -We have two Quickstart guides you can follow: - - - - -πŸ‘‹ Welcome to AWS Amplify! In this quickstart guide, you will: - -1. Deploy a Vue.js app -2. Build and connect to a database with real-time data updates -3. Configure authentication and authorization rules - -## Deploy a fullstack app to AWS - -We've created a starter "To-do" application to help get started faster. First, you will create a repository in your GitHub account using our starter Vue template. - -### 1. Create the repository - -Use our starter template to create a repository in your GitHub account. This template scaffolds `create-vite-app` with Amplify backend capabilities. - - - -Create repository from template - - -Use the form in GitHub to finalize your repo's creation. - -### 2. Deploy the starter app - -Now that the repository has been created, deploy it with Amplify. - - - -Deploy to AWS - - -Select **GitHub**. After you give Amplify access to your GitHub account via the popup window, pick the repository and `main` branch to deploy. Make no other changes and click through the flow to **Save and deploy**. - -### 3. View deployed app - - - -Let's take a tour of the project structure in this starter repository by opening it on GitHub. The starter application has pre-written code for a to-do list app. It gives you a real-time database with a feed of all to-do list items and the ability to add new items. - -```text -β”œβ”€β”€ amplify/ # Folder containing your Amplify backend configuration -β”‚ β”œβ”€β”€ auth/ # Definition for your auth backend -β”‚ β”‚ └── resource.tsx -β”‚ β”œβ”€β”€ data/ # Definition for your data backend -β”‚ β”‚ └── resource.ts -| β”œβ”€β”€ backend.ts -β”‚ └── tsconfig.json -β”œβ”€β”€ src/ # Vue code -β”‚ β”œβ”€β”€ assets/ # Styling for your app -β”‚ β”œβ”€β”€ components/ # UI code to sync todos in real-time -β”‚ β”œβ”€β”€ App.vue # UI layout -β”‚ └── main.tsx # Entrypoint of the Amplify client library -β”œβ”€β”€ package.json -└── tsconfig.json -``` - - - When the build completes, visit the newly deployed branch by selecting "Visit deployed URL". Since the build deployed an API, database, and authentication backend, you will be able to create new to-do items. - -In the Amplify console, click into the deployment branch (in this case **main**) > select **Data** in the left-hand menu > **Data manager** to see the data entered in your database. - -## Make frontend updates - -Let's learn how to enhance the app functionality by creating a delete flow for to-do list items. - -### 4. Set up local environment - -Now let's set up our local development environment to add features to the frontend. Click on your deployed branch and you will land on the **Deployments** page which shows you your build history and a list of deployed backend resources. - -At the bottom of the page you will see a tab for **Deployed backend resources**. Click on the tab and then click the **Download amplify_outputs.json file** button. - -![](/images/gen2/getting-started/react/amplify-outputs-download.png) - -Clone the repository locally. - -```bash title="Terminal" showLineNumbers={false} -git clone https://github.com//amplify-vue-template.git -cd amplify-vue-template && npm install -``` - -Now move the `amplify_outputs.json` file you downloaded above to the root of your project. - -```text -β”œβ”€β”€ amplify -β”œβ”€β”€ src -β”œβ”€β”€ amplify_outputs.json <== backend outputs file -β”œβ”€β”€ package.json -└── tsconfig.json -``` - - -The **amplify_outputs.json** file contains backend endpoint information, publicly-viewable API keys, authentication flow information, and more. The Amplify client library uses this outputs file to connect to your Amplify Backend. You can review how the outputs file is imported within the `main.tsx` file and then passed into the `Amplify.configure(...)` function of the Amplify client library. - - -### 5. Implement delete functionality - -Go to the **components/Todos.vue** file and add in a new `deleteTodo` functionality and pass function into the `
  • ` element's `onClick` handler. - -```tsx title="components/Todos.vue" -function App() { - // ... - // highlight-start - function deleteTodo(id: string) { - client.models.Todo.delete({ id }) - } - // highlight-end - - -} -``` - -Try out the deletion functionality now by starting the local dev server: - -```bash title="Terminal" showLineNumbers={false} -npm run dev -``` - -This should start a local dev server at http://localhost:5173. - -### 6. Implement login UI - -The starter application already has a pre-configured auth backend defined in the **amplify/auth/resource.ts** file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. - -The fastest way to get your login experience up and running is to use our Authenticator UI component. - -```terminal showLineNumbers={false} -npm add @aws-amplify/ui-vue -``` -In your **src/App.vue** file, import the Authenticator UI component and wrap your `
    ` template. - -```tsx title="src/App.vue" - - - -``` - -The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. - -Try out your application in your localhost environment again. You should be presented with a login experience now. - -To get these changes to the cloud, commit them to git and push the changes upstream. - -```bash title="Terminal" showLineNumbers={false} -git commit -am "added authenticator" -git push -``` - -Amplify automatically deploys the latest version of your app based on your git commits. In just a few minutes, when the application rebuilds, the hosted app will be updated to support the deletion functionality. - -## Make backend updates - -Let's update our backend to implement per-user authorization rules, allowing each user to only access their own to-dos. - -### 7. Set up local AWS credentials - -To make backend updates, we are going to require AWS credentials to deploy backend updates from our local machine. - -**Skip ahead to step 8**, if you already have an AWS profile with credentials on your local machine, and your AWS profile has the `AmplifyBackendDeployFullAccess` permission policy. - -Otherwise, **[set up local AWS credentials](/[platform]/start/account-setup/)** that grant Amplify permissions to deploy backend updates from your local machine. - -### 8. Deploy cloud sandbox - -To update your backend without affecting the production branch, use Amplify's cloud sandbox. This feature provides a separate backend environment for each developer on a team, ideal for local development and testing. - -To start your cloud sandbox, run the following command in a **new Terminal window**: - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox -``` - -Once the cloud sandbox has been fully deployed (~5 min), you'll see the `amplify_outputs.json` file updated with connection information to a new isolated authentication and data backend. - -> **Info:** The `npx ampx sandbox` command should run concurrently to your `npm run dev`. You can think of the cloud sandbox as the "localhost-equivalent for your app backend". - -### 9. Implement per-user authorization - -The to-do items in the starter are currently shared across all users, but, in most cases, you want data to be isolated on a per-user basis. - -To isolate the data on a per-user basis, you can use an "owner-based authorization rule". Let's apply the owner-based authorization rule to your to-do items: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a.model({ - content: a.string(), - // highlight-next-line - }).authorization(allow => [allow.owner()]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - // This tells the data client in your app (generateClient()) - // to sign API requests with the user authentication token. - // highlight-next-line - defaultAuthorizationMode: 'userPool', - }, -}); -``` - -In the application client code, let's also render the username to distinguish different users once they're logged in. - -```tsx title="src/App.vue" - - - -``` - -Now, let's go back to your local application and test out the user isolation of the to-do items. - -You will need to sign up new users again because now you're working with the cloud sandbox instead of your production backend. - -To get these changes to the cloud, commit them to git and push the changes upstream. - -```bash title="Terminal" showLineNumbers={false} -git commit -am "added per-user data isolation" -git push -``` - -Once your build completes in the Amplify Console, the `main` backend will update to support the changes made within the cloud sandbox. The data in the cloud sandbox is fully isolated and won't pollute your production database. - - - -πŸ‘‹ Welcome to AWS Amplify! In this quickstart guide, you will: - -1. Deploy an Angular app -2. Build and connect to a database with real-time data updates -3. Configure authentication and authorization rules - -## Deploy a fullstack app to AWS - -We've created a starter "To-do" application to help get started faster. First, you will create a repository in your GitHub account using our starter Angular template. - -### 1. Create the repository - -Use our starter template to create a repository in your GitHub account. This template scaffolds a starter Angular application with Amplify backend capabilities. - - - -Create repository from template - - -Use the form in GitHub to finalize your repo's creation. - -### 2. Deploy the starter app - -Now that the repository has been created, deploy it with Amplify. - - - -Deploy to AWS - - -Select **GitHub**. After you give Amplify access to your GitHub account via the popup window, pick the repository and `main` branch to deploy. Make no other changes and click through the flow to **Save and deploy**. - -### 3. View deployed app - - - -Let's take a tour of the project structure in this starter repository by opening it on GitHub. The starter application has pre-written code for a to-do list app. It gives you a real-time database with a feed of all to-do list items and the ability to add new items. - -```text -β”œβ”€β”€ amplify/ # Folder containing your Amplify backend configuration -β”‚ β”œβ”€β”€ auth/ # Definition for your auth backend -β”‚ β”‚ └── resource.tsx -β”‚ β”œβ”€β”€ data/ # Definition for your data backend -β”‚ β”‚ └── resource.ts -| β”œβ”€β”€ backend.ts -β”‚ └── tsconfig.json -β”œβ”€β”€ src/app/ # Angular UI code -β”‚ β”œβ”€β”€ todos/ # UI code to sync todos in real-time -β”‚ β”œβ”€β”€ app.component.css # Styling for your app -β”‚ └── app.component.ts # Entrypoint of the Amplify client library -β”œβ”€β”€ package.json -└── tsconfig.json -``` - - - When the build completes, visit the newly deployed branch by selecting "Visit deployed URL". Since the build deployed an API, database, and authentication backend, you will be able to create new to-do items. - -In the Amplify console, click into the deployment branch (in this case **main**) > select **Data** in the left-hand menu > **Data manager** to see the data entered in your database. - -## Make frontend updates - -Let's learn how to enhance the app functionality by creating a delete flow for to-do list items. - -### 4. Set up local environment - -Now let's set up our local development environment to add features to the frontend. Click on your deployed branch and you will land on the **Deployments** page which shows you your build history and a list of deployed backend resources. - -At the bottom of the page you will see a tab for **Deployed backend resources**. Click on the tab and then click the **Download amplify_outputs.json file** button. - -![](/images/gen2/getting-started/react/amplify-outputs-download.png) - -Clone the repository locally. - -```bash title="Terminal" showLineNumbers={false} -git clone https://github.com//amplify-angular-template.git -cd amplify-angular-template && npm install -``` - -Now move the `amplify_outputs.json` file you downloaded above to the root of your project. - -```text -β”œβ”€β”€ amplify -β”œβ”€β”€ src -β”œβ”€β”€ amplify_outputs.json <== backend outputs file -β”œβ”€β”€ package.json -└── tsconfig.json -``` - - -The **amplify_outputs.json** file contains backend endpoint information, publicly-viewable API keys, authentication flow information, and more. The Amplify client library uses this outputs file to connect to your Amplify Backend. You can review how the outputs file is imported within the `app.component.ts` file and then passed into the `Amplify.configure(...)` function of the Amplify client library. - - -### 5. Implement delete functionality - -Go to the **src/app/todos/todos.component.ts** file and add a new `deleteTodo` function. - -```tsx title="src/todos/todos.component.ts" -export class TodosComponent implements OnInit { - // ... - // highlight-start - deleteTodo(id: string) { - client.models.Todo.delete({ id }) - } - // highlight-end -} -``` - -Call the `deleteTodo` function from the UI. - -```html title="src/app/todos/todos.component.html" -... -
      -
    • - {{ todo.content }} -
    • -
    -... -``` - -Try out the deletion functionality now by starting the local dev server: - -```bash title="Terminal" showLineNumbers={false} -npm run start -``` - -This should start a local dev server at http://localhost:4200. - -### 6. Implement login UI - -The starter application already has a pre-configured auth backend defined in the **amplify/auth/resource.ts** file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. - -The fastest way to get your login experience up and running is to use our Authenticator UI component. - -```terminal showLineNumbers={false} -npm add @aws-amplify/ui-angular -``` - -In your **src/app/app.component.ts** file, import the `AmplifyAuthenticatorModule`. - -```ts title="src/app/app.component.ts" -import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; -import { TodosComponent } from './todos/todos.component'; -import { Amplify } from 'aws-amplify'; -import outputs from '../../amplify_outputs.json'; -// highlight-next-line -import { AmplifyAuthenticatorModule, AuthenticatorService } from '@aws-amplify/ui-angular'; - -Amplify.configure(outputs); - -@Component({ - selector: 'app-root', - standalone: true, - templateUrl: './app.component.html', - styleUrl: './app.component.css', - // highlight-next-line - imports: [RouterOutlet, TodosComponent, AmplifyAuthenticatorModule], -}) -export class AppComponent { - title = 'amplify-angular-template'; - // highlight-start - constructor(public authenticator: AuthenticatorService) { - Amplify.configure(outputs); - } - // highlight-end -} -``` -Update the application UI and include styles. - -```html title="src/app/app.component.html" - - - - - - -``` - -```json title="angular.json" -... - "styles": [ - "node_modules/@aws-amplify/ui-angular/theme.css", - "src/styles.css" - ], -... -``` -The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. - -Try out your application in your localhost environment again. You should be presented with a login experience now. - -To get these changes to the cloud, commit them to git and push the changes upstream. - -```bash title="Terminal" showLineNumbers={false} -git commit -am "added authenticator" -git push -``` - -Amplify automatically deploys the latest version of your app based on your git commits. In just a few minutes, when the application rebuilds, the hosted app will be updated to support the deletion functionality. - -## Make backend updates - -Let's update our backend to implement per-user authorization rules, allowing each user to only access their own to-dos. - -### 7. Set up local AWS credentials - -To make backend updates, we are going to require AWS credentials to deploy backend updates from our local machine. - -**Skip ahead to step 8**, if you already have an AWS profile with credentials on your local machine, and your AWS profile has the `AmplifyBackendDeployFullAccess` permission policy. - -Otherwise, **[set up local AWS credentials](/[platform]/start/account-setup/)** that grant Amplify permissions to deploy backend updates from your local machine. - -### 8. Deploy cloud sandbox - -To update your backend without affecting the production branch, use Amplify's cloud sandbox. This feature provides a separate backend environment for each developer on a team, ideal for local development and testing. - -To start your cloud sandbox, run the following command in a **new Terminal window**: - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox -``` - -Once the cloud sandbox has been fully deployed (~5 min), you'll see the `amplify_outputs.json` file updated with connection information to a new isolated authentication and data backend. - -> **Info:** The `npx ampx sandbox` command should run concurrently to your `npm run dev`. You can think of the cloud sandbox as the "localhost-equivalent for your app backend". - -### 9. Implement per-user authorization - -The to-do items in the starter are currently shared across all users, but, in most cases, you want data to be isolated on a per-user basis. - -To isolate the data on a per-user basis, you can use an "owner-based authorization rule". Let's apply the owner-based authorization rule to your to-do items: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a.model({ - content: a.string(), - }) - // highlight-next-line - .authorization(allow => [allow.owner()]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - // This tells the data client in your app (generateClient()) - // to sign API requests with the user authentication token. - // highlight-next-line - defaultAuthorizationMode: 'userPool', - }, -}); -``` - -In the application client code, let's also render the username to distinguish different users once they're logged in. - -```html title="src/app/app.component.html" - - -

    Hello {{user?.signInDetails?.loginId}}'s todos

    - - -
    -
    -``` - -Now, let's go back to your local application and test out the user isolation of the to-do items. - -You will need to sign up new users again because now you're working with the cloud sandbox instead of your production backend. - -To get these changes to the cloud, commit them to git and push the changes upstream. - -```bash title="Terminal" showLineNumbers={false} -git commit -am "added per-user data isolation" -git push -``` - -Once your build completes in the Amplify Console, the `main` backend will update to support the changes made within the cloud sandbox. The data in the cloud sandbox is fully isolated and won't pollute your production database. - - - -## Prerequisites - -Before you get started, make sure you have the following installed: - -- [Node.js](https://nodejs.org/) v18.17 or later -- [npm](https://www.npmjs.com/) v9 or later -- [git](https://git-scm.com/) v2.14.1 or later -- You will also need to [create an AWS Account](https://portal.aws.amazon.com/billing/signup). Note that AWS Amplify is part of the [AWS Free Tier](https://aws.amazon.com/amplify/pricing/). -- Configure your AWS account to use with Amplify [instructions](/[platform]/start/account-setup/). -- A stable version of [Flutter](https://docs.flutter.dev/get-started/install). - -> **Info:** You can follow the [official documentation](https://flutter.dev/docs/get-started/install) to install Flutter on your machine and check the [editor documentation](https://docs.flutter.dev/get-started/editor) for setting up your editor. - -Once you have installed Flutter, you can create a new Flutter project using the following command: - -> **Info:** In this Quickstart guide, you will build the application for web. However, if you want to run the application on other platforms, be sure to follow the required setup [guide here](/[platform]/start/platform-setup/). - -```bash title="Terminal" showLineNumbers={false} -flutter create my_amplify_app -``` - -## Create Backend - -The easiest way to get started with AWS Amplify is through npm with `create-amplify` command. You can run it from your base project directory. First, go to the base project directory with the following command: - -```bash title="Terminal" showLineNumbers={false} -cd my_amplify_app -``` - -After that, run the following to create an Amplify project: - -```bash title="Terminal" showLineNumbers={false} -npm create amplify@latest -y -``` - -Running this command will scaffold Amplify backend files in your current project with the following files added: - -```text -β”œβ”€β”€ amplify/ -β”‚ β”œβ”€β”€ auth/ -β”‚ β”‚ └── resource.ts -β”‚ β”œβ”€β”€ data/ -β”‚ β”‚ └── resource.ts -β”‚ β”œβ”€β”€ backend.ts -β”‚ └── package.json -β”œβ”€β”€ node_modules/ -β”œβ”€β”€ .gitignore -β”œβ”€β”€ package-lock.json -β”œβ”€β”€ package.json -└── tsconfig.json -``` - -To deploy your backend use Amplify's per-developer cloud sandbox. This feature provides a separate backend environment for every developer on a team, ideal for local development and testing. To run your application with a sandbox environment, you can run the following command: - - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox - -``` - - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox --outputs-format dart --outputs-out-dir lib -``` - - -## Adding Authentication - -The initial scaffolding already has a pre-configured auth backend defined in the `amplify/auth/resource.ts` file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. - -The fastest way to get your login experience up and running is to use our Authenticator UI component available in the Amplify UI library. - -To use the Authenticator, you need to add the following dependencies to your project: - -```yaml title="pubspec.yaml" -dependencies: - amplify_flutter: ^2.0.0 - amplify_auth_cognito: ^2.0.0 - amplify_authenticator: ^2.0.0 -``` - -You will add: - -- `amplify_flutter` to connect your application with the Amplify resources. -- `amplify_auth_cognito` to connect your application with the Amplify Cognito resources. -- `amplify_authenticator` to use the Amplify UI components. - -After adding the dependencies, you need to run the following command to install the dependencies: - -```bash title="Terminal" showLineNumbers={false} -flutter pub get -``` - -Lastly update your main.dart file to use the Amplify UI components: - -```dart title="main.dart" -import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; -import 'package:amplify_authenticator/amplify_authenticator.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; - -import 'amplify_outputs.dart'; - -Future main() async { - try { - WidgetsFlutterBinding.ensureInitialized(); - await _configureAmplify(); - runApp(const MyApp()); - } on AmplifyException catch (e) { - runApp(Text("Error configuring Amplify: ${e.message}")); - } -} - -Future _configureAmplify() async { - try { - await Amplify.addPlugin(AmplifyAuthCognito()); - await Amplify.configure(amplifyConfig); - safePrint('Successfully configured'); - } on Exception catch (e) { - safePrint('Error configuring Amplify: $e'); - } -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - @override - Widget build(BuildContext context) { - return Authenticator( - child: MaterialApp( - builder: Authenticator.builder(), - home: const Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SignOutButton(), - Text('TODO Application'), - ], - ), - ), - ), - ), - ); - } -} -``` - -The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. - -Run your application in your local environment again. You should be presented with a login experience now. - -## Adding Data - -The initial scaffolding already has a pre-configured data backend defined in the `amplify/data/resource.ts` file. The default example will create a Todo model with `content` field. - -Let's modify this to add the following: -- A boolean `isDone` field. -- An authorization rules specifying owners, authenticated via your Auth resource can "create", "read", "update", and "delete" their own records. -- Update the `defaultAuthorizationMode` to sign API requests with the user authentication token. - -```typescript -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - isDone: a.boolean(), - }) - .authorization(allow => [allow.owner()]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "userPool", - }, -}); -``` -Next, let's implement UI to create, list, and delete the to-do items. - -Amplify can automatically generate code for interacting with the backend API. Run the command in the terminal to generate dart model classes from the Data schema under `lib/models`: - -```bash title="Terminal" showLineNumbers={false} -npx ampx generate graphql-client-code --format modelgen --model-target dart --out lib/models -``` - -Once you are done, add the API dependencies to your project. You will add `amplify_api` to connect your application with the Amplify API. - -```yaml title="pubspec.yaml" -dependencies: - amplify_api: ^2.0.0 -``` - -After adding the dependencies, update the `_configureAmplify` method in your `main.dart` file to use the Amplify API: - -```dart title="main.dart" -Future _configureAmplify() async { - try { - await Amplify.addPlugins( - [ - AmplifyAuthCognito(), - AmplifyAPI( - options: APIPluginOptions( - modelProvider: ModelProvider.instance, - ), - ), - ], - ); - await Amplify.configure(amplifyConfig); - safePrint('Successfully configured'); - } on Exception catch (e) { - safePrint('Error configuring Amplify: $e'); - } -} -``` - -Next create a new widget called `TodoScreen` and add the following code to the end of the **main.dart** file: - -```dart title="main.dart" - -class TodoScreen extends StatefulWidget { - const TodoScreen({super.key}); - - @override - State createState() => _TodoScreenState(); -} - -class _TodoScreenState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: FloatingActionButton.extended( - label: const Text('Add Random Todo'), - onPressed: () async { - final newTodo = Todo( - id: uuid(), - content: "Random Todo ${DateTime.now().toIso8601String()}", - isDone: false, - ); - final request = ModelMutations.create(newTodo); - final response = await Amplify.API.mutate(request: request).response; - if (response.hasErrors) { - safePrint('Creating Todo failed.'); - } else { - safePrint('Creating Todo successful.'); - } - }, - ), - body: const Placeholder(), - ); - } -} -``` - -This will create a random Todo every time a user clicks on the floating action button. You can see the `ModelMutations.create` method is used to create a new Todo. - -And update the `MyApp` widget in your **main.dart** file like the following: - -```dart title="main.dart" -class MyApp extends StatelessWidget { - const MyApp({super.key}); - @override - Widget build(BuildContext context) { - return Authenticator( - child: MaterialApp( - builder: Authenticator.builder(), - home: const SafeArea( - child: Scaffold( - body: Column( - children: [ - SignOutButton(), - Expanded(child: TodoScreen()), - ], - ), - ), - ), - ), - ); - } -} -``` - -Next add a `_todos` list in `_TodoScreenState` to add the results from the API and call the refresh function: - -```dart title="main.dart" -List _todos = []; - -@override -void initState() { - super.initState(); - _refreshTodos(); -} -``` - -and create a new function called `_refreshTodos`: - -```dart title="main.dart" -Future _refreshTodos() async { - try { - final request = ModelQueries.list(Todo.classType); - final response = await Amplify.API.query(request: request).response; - - final todos = response.data?.items; - if (response.hasErrors) { - safePrint('errors: ${response.errors}'); - return; - } - setState(() { - _todos = todos!.whereType().toList(); - }); - } on ApiException catch (e) { - safePrint('Query failed: $e'); - } -} -``` - -and update the `build` function like the following: - -```dart title="main.dart" -@override -Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: FloatingActionButton.extended( - label: const Text('Add Random Todo'), - onPressed: () async { - final newTodo = Todo( - id: uuid(), - content: "Random Todo ${DateTime.now().toIso8601String()}", - isDone: false, - ); - final request = ModelMutations.create(newTodo); - final response = await Amplify.API.mutate(request: request).response; - if (response.hasErrors) { - safePrint('Creating Todo failed.'); - } else { - safePrint('Creating Todo successful.'); - } - _refreshTodos(); - }, - ), - body: _todos.isEmpty == true - ? const Center( - child: Text( - "The list is empty.\nAdd some items by clicking the floating action button.", - textAlign: TextAlign.center, - ), - ) - : ListView.builder( - itemCount: _todos.length, - itemBuilder: (context, index) { - final todo = _todos[index]; - return Dismissible( - key: UniqueKey(), - confirmDismiss: (direction) async { - return false; - }, - child: CheckboxListTile.adaptive( - value: todo.isDone, - title: Text(todo.content!), - onChanged: (isChecked) async {}, - ), - ); - }, - ), - ); -} -``` - -Now let's add the update and delete functionality. - -For update, add the following code to the `onChanged` method of the `CheckboxListTile.adaptive` widget: - -```dart title="main.dart" -final request = ModelMutations.update( - todo.copyWith(isDone: isChecked!), -); -final response = - await Amplify.API.mutate(request: request).response; -if (response.hasErrors) { - safePrint('Updating Todo failed. ${response.errors}'); -} else { - safePrint('Updating Todo successful.'); - await _refreshTodos(); -} -``` - -This will call the `ModelMutations.update` method to update the Todo with a copied/updated version of the todo item. So now the checkbox will get an update as well. - -For delete functionality, add the following code to the `confirmDismiss` method of the `Dismissible` widget: - -```dart title="main.dart" -if (direction == DismissDirection.endToStart) { - final request = ModelMutations.delete(todo); - final response = - await Amplify.API.mutate(request: request).response; - if (response.hasErrors) { - safePrint('Updating Todo failed. ${response.errors}'); - } else { - safePrint('Updating Todo successful.'); - await _refreshTodos(); - return true; - } -} -return false; -``` - -This will delete the Todo item when the user swipes the item from right to left. Now if you run the application you should see the following flow. - -You can terminate the sandbox environment now to clean up the project. - -### Publishing changes to cloud - -Publishing changes to the cloud requires a remote git repository. Amplify offers fullstack branch deployments that allow you to automatically deploy infrastructure and application code changes from feature branches. To learn more, visit the [fullstack branch deployments guide](/[platform]/deploy-and-host/fullstack-branching/branch-deployments). - - - -## Prerequisites - -Before you get started, make sure you have the following installed: - -- [Node.js](https://nodejs.org/) v18.17 or later -- [npm](https://www.npmjs.com/) v9 or later -- [git](https://git-scm.com/) v2.14.1 or later -- You will also need to [create an AWS Account](https://portal.aws.amazon.com/billing/signup). Note that AWS Amplify is part of the [AWS Free Tier](https://aws.amazon.com/amplify/pricing/). -- Configure your AWS account to use with Amplify [instructions](/[platform]/start/account-setup/). -- You need to have [Xcode and Developer Tooling](https://developer.apple.com/xcode/) installed on your machine. - - -Open Xcode and select **Create New Project...** - -![Shows the Xcode starter video to start project](/images/lib/getting-started/ios/set-up-swift-1.png) - -In the next step select the **App** template under **iOS**. Click on next. - -![Shows the template of apps for iOS](/images/lib/getting-started/ios/set-up-swift-2.png) - -Next steps are: - -- Adding a _Product Name_ (e.g. MyAmplifyApp) -- Select a _Team_ (e.g. None) -- Select a _Organization Identifier_ (e.g. com.example) -- Select **SwiftUI** an _Interface_. -- Press **Next** - -![Shows the project details dialog](/images/lib/getting-started/ios/set-up-swift-3.png) - -Now you should have your project created. - -![Shows the base project for SwiftUI](/images/lib/getting-started/ios/set-up-swift-4.png) - - -## Create Backend - -The easiest way to get started with AWS Amplify is through npm with `create-amplify` command. You can run it from your base project directory. - -```bash title="Terminal" showLineNumbers={false} -cd my_amplify_app -npm create amplify@latest -? Where should we create your project? (.) # press enter -``` - -Running this command will scaffold Amplify backend files in your current project with the following files added: - -```text -β”œβ”€β”€ amplify/ -β”‚ β”œβ”€β”€ auth/ -β”‚ β”‚ └── resource.ts -β”‚ β”œβ”€β”€ data/ -β”‚ β”‚ └── resource.ts -β”‚ β”œβ”€β”€ backend.ts -β”‚ └── package.json -β”œβ”€β”€ node_modules/ -β”œβ”€β”€ .gitignore -β”œβ”€β”€ package-lock.json -β”œβ”€β”€ package.json -└── tsconfig.json -``` - -To deploy your backend use Amplify's per-developer cloud sandbox. This feature provides a separate backend environment for every developer on a team, ideal for local development and testing. To run your application with a sandbox environment, you can run the following command: - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox -``` - -Once the sandbox environment is deployed, it will create an `amplify_outputs.json`. However, Xcode won't be able to recognize them. For recognizing the files, you need to drag and drop the generated files to your project. - -## Adding Authentication - -The initial scaffolding already has a pre-configured auth backend defined in the `amplify/auth/resource`.ts file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. - -The fastest way to get your login experience up and running is to use our Authenticator UI component available in the Amplify UI library. - -To use the Authenticator, open your project in Xcode and select **File > Add Packages...** and add the following dependencies: - -![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-5.png) - -- Amplify Library for Swift: Enter its GitHub URL (https://github.com/aws-amplify/amplify-swift), select **Up to Next Major Version** and click **Add Package Dependencies...** and select the following libraries: - - - Amplify - - AWSCognitoAuthPlugin - -![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-6.png) - -- Amplify UI Swift - Authenticator: Enter its GitHub URL (https://github.com/aws-amplify/amplify-ui-swift-authenticator), select **Up to Next Major Version** and click **Add Package Dependencies...** and select the following libraries: - - Authenticator - -![Shows the Amplify library for Swift](/images/lib/getting-started/ios/set-up-swift-7.png) - -Now update the `MyAmplifyAppApp` class with the following code: - -```swift -import Amplify -import Authenticator -import AWSCognitoAuthPlugin -import SwiftUI - -@main -struct MyApp: App { - init() { - do { - try Amplify.add(plugin: AWSCognitoAuthPlugin()) - try Amplify.configure(with: .amplifyOutputs) - } catch { - print("Unable to configure Amplify \(error)") - } - } - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` - -Update `ContentView` with the following code: -```swift -import Amplify -import Authenticator - -struct ContentView: View { - var body: some View { - Authenticator { state in - VStack { - Button("Sign out") { - Task { - await state.signOut() - } - } - } - } - } -} -``` - -The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. - -Run your application in your local environment again. You should be presented with a login experience now. - -
    - ) -} -``` -
    See the complete amplify/data/resources.ts - -Open the `amplify/data/resource.ts` file in your text editor, and you will see a default data model generated for you. - -```ts showLineNumbers title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a - .model({ - content: a.string() - }) - .authorization(allow => [allow.owner(), allow.publicApiKey().to(['read'])]) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - // API Key is used for allow.publicApiKey() rules - apiKeyAuthorizationMode: { - expiresInDays: 30 - } - } -}); -``` - - The schema generated by Amplify is for a to-do app. A schema is a blueprint - for how our app's data will be organized. Within the schema, we will define - models that will correspond to a database tableβ€”`Todo` in the above code. - Finally, we will define fields, which are attributes that each data instance - will haveβ€”in the generated code, the field is `content`. Each - field will have a type attached to itβ€”in the above examples, we are stating - that the `content` field is a string. -
    - -Try out the deletion functionality now by starting the local dev server: - -```bash title="Terminal" showLineNumbers={false} -npm run dev -``` - -This should start a local dev server at http://localhost:3000. - -### 6. Implement login UI - -The starter application already has a pre-configured auth backend defined in the **amplify/auth/resource.ts** file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. - -The fastest way to get your login experience up and running is to use our Authenticator UI component. In your **pages/_app.tsx** file, import the Authenticator UI component and wrap your `` component. - -```tsx title="pages/_app.tsx" -import type { AppProps } from "next/app"; -// highlight-start -import { Authenticator } from '@aws-amplify/ui-react' -import '@aws-amplify/ui-react/styles.css' -// highlight-end -import "@/styles/app.css"; -import { Amplify } from "aws-amplify"; -import outputs from "@/amplify_outputs.json"; - -Amplify.configure(outputs); - -export default function App({ Component, pageProps }: AppProps) { - return( - // highlight-start - - ; - - // highlight-end - ) -} -``` -
    See the complete amplify/auth/resources.ts - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from '@aws-amplify/backend'; - -/** - * Define and configure your auth resource - * When used alongside data, it is automatically configured as an auth provider for data - * @see https://docs.amplify.aws/gen2/build-a-backend/auth - */ -export const auth = defineAuth({ - loginWith: { - email: true, - // add social providers - externalProviders: { - /** - * first, create your secrets using `ampx sandbox secret` - * then, import `secret` from `@aws-amplify/backend` - * @see https://docs.amplify.aws/gen2/deploy-and-host/sandbox-environments/features/#setting-secrets - */ - // loginWithAmazon: { - // clientId: secret('LOGINWITHAMAZON_CLIENT_ID'), - // clientSecret: secret('LOGINWITHAMAZON_CLIENT_SECRET'), - // } - } - }, - /** - * enable multifactor authentication - * @see https://docs.amplify.aws/gen2/build-a-backend/auth/manage-mfa - */ - // multifactor: { - // mode: 'OPTIONAL', - // sms: { - // smsMessage: (code) => `Your verification code is ${code}`, - // }, - // }, - userAttributes: { - /** request additional attributes for your app's users */ - // profilePicture: { - // mutable: true, - // required: false, - // }, - } -}); -``` -
    - -The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. - -In your **pages/index.tsx** file, add a button to enable users to sign out of the application. Import the [`useAuthenticator`](https://ui.docs.amplify.aws/react/connected-components/authenticator/advanced#access-auth-state) hook from the Amplify UI library to hook into the state of the Authenticator. - -```tsx title="pages/index.tsx" -import type { Schema } from "@/amplify/data/resource"; -// highlight-next-line -import { useAuthenticator } from "@aws-amplify/ui-react"; -import { useState, useEffect } from "react"; -import { generateClient } from "aws-amplify/data"; - -const client = generateClient(); - -export default function HomePage() { - - // highlight-start - const { signOut } = useAuthenticator(); - // highlight-end - - // ... - - return ( -
    - {/* ... */} - // highlight-next-line - -
    - ); -} -``` - -Try out your application in your localhost environment again. You should be presented with a login experience now. - -To get these changes to the cloud, commit them to git and push the changes upstream. - -```bash title="Terminal" showLineNumbers={false} -git commit -am "added authenticator" -git push -``` - -Amplify automatically deploys the latest version of your app based on your git commits. In just a few minutes, when the application rebuilds, the hosted app will be updated to support the deletion functionality. - -## Make backend updates - -Let's update our backend to implement per-user authorization rules, allowing each user to only access their own to-dos. - -### 7. Set up local AWS credentials - -To make backend updates, we are going to require AWS credentials to deploy backend updates from our local machine. - -**Skip ahead to step 8**, if you already have an AWS profile with credentials on your local machine, and your AWS profile has the `AmplifyBackendDeployFullAccess` permission policy. - -Otherwise, **[set up local AWS credentials](/[platform]/start/account-setup/)** that grant Amplify permissions to deploy backend updates from your local machine. - -### 8. Deploy cloud sandbox - -To update your backend without affecting the production branch, use Amplify's cloud sandbox. This feature provides a separate backend environment for each developer on a team, ideal for local development and testing. - -To start your cloud sandbox, run the following command in a **new Terminal window**: - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox -``` - -Once the cloud sandbox has been fully deployed (~5 min), you'll see the `amplify_outputs.json` file updated with connection information to a new isolated authentication and data backend. - -> **Info:** The `npx ampx sandbox` command should run concurrently to your `npm run dev`. You can think of the cloud sandbox as the "localhost-equivalent for your app backend". - -### 9. Implement per-user authorization - -The to-do items in the starter are currently shared across all users, but, in most cases, you want data to be isolated on a per-user basis. - -To isolate the data on a per-user basis, you can use an "owner-based authorization rule". Let's apply the owner-based authorization rule to your to-do items: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a.model({ - content: a.string(), - // highlight-next-line - }).authorization(allow => [allow.owner()]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - // This tells the data client in your app (generateClient()) - // to sign API requests with the user authentication token. - // highlight-next-line - defaultAuthorizationMode: 'userPool', - }, -}); -``` - -In the application client code, let's also render the username to distinguish different users once they're logged in. Go to your **pages/index.tsx** file and render the `user` property from the `useAuthenticator` hook. - -```tsx title="pages/index.tsx" -// ... imports - -function HomePage() { - // highlight-next-line - const { user, signOut } = useAuthenticator(); - - // ... - - return ( -
    - // highlight-next-line -

    {user?.signInDetails?.loginId}'s todos

    - {/* ... */} -
    - ) -} -``` - -Now, let's go back to your local application and test out the user isolation of the to-do items. - -You will need to sign up new users again because now you're working with the cloud sandbox instead of your production backend. - -To get these changes to the cloud, commit them to git and push the changes upstream. - -```bash title="Terminal" showLineNumbers={false} -git commit -am "added per-user data isolation" -git push -``` - -Once your build completes in the Amplify Console, the `main` backend will update to support the changes made within the cloud sandbox. The data in the cloud sandbox is fully isolated and won't pollute your production database. - -## πŸ₯³ Success - -That's it! You have successfully built a fullstack app on AWS Amplify. If you want to learn more about how to work with Amplify, here's the conceptual guide for [how Amplify works](/[platform]/how-amplify-works/concepts/). - ---- - ---- -title: "Next.js App Router" -section: "start/quickstart" -platforms: ["nextjs"] -gen: 2 -last-updated: "2025-07-24T13:26:03.000Z" -url: "https://docs.amplify.aws/react/start/quickstart/nextjs-app-router-client-components/" ---- - -## Pre-requisites - -This Quickstart guide will walk you through how to build a task list application with TypeScript, Next.js **App Router with Client Components**, and React. Before you begin, make sure you have the following installed: - -- [Node.js](https://nodejs.org/) v14.x or later -- [npm](https://www.npmjs.com/) v6.14.4 or later -- [git](https://git-scm.com/) v2.14.1 or later -- If you are new to these technologies, we recommend you go through the official [React](https://react.dev/learn/tutorial-tic-tac-toe), [Next.js](https://nextjs.org/docs/app/getting-started), and [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html) tutorials first. - -## Deploy a fullstack app to AWS - -We've created a starter "To-do" application to help get started faster. First, you will create a repository in your GitHub account using our starter Next template. - -### 1. Create the repository - -Use our starter template to create a repository in your GitHub account. This template scaffolds `create-next-app` with Amplify backend capabilities. - - - -Create repository from template - - -Use the form in GitHub to finalize your repo's creation. - -### 2. Deploy the starter app - -Now that the repository has been created, deploy it with Amplify. - - - -Deploy to AWS - - -Select **Start with an existing app** > **GitHub**. After you give Amplify access to your GitHub account via the popup window, pick the repository and `main` branch to deploy. Make no other changes and click through the flow to **Save and deploy**. - -### 3. View deployed app - - - -Let's take a tour of the project structure in this starter repository by opening it on GitHub. The starter application has pre-written code for a to-do list app. It gives you a real-time database with a feed of all to-do list items and the ability to add new items. - -```text -β”œβ”€β”€ amplify/ # Folder containing your Amplify backend configuration -β”‚ β”œβ”€β”€ auth/ # Definition for your auth backend -β”‚ β”‚ └── resource.tsx -β”‚ β”œβ”€β”€ data/ # Definition for your data backend -β”‚ β”‚ └── resource.ts -| β”œβ”€β”€ backend.ts -β”‚ └── tsconfig.json -β”œβ”€β”€ src/ # React UI code -β”‚ β”œβ”€β”€ App.tsx # UI code to sync todos in real-time -β”‚ β”œβ”€β”€ index.css # Styling for your app -β”‚ └── main.tsx # Entrypoint of the Amplify client library -β”œβ”€β”€ package.json -└── tsconfig.json -``` - - - When the build completes, visit the newly deployed branch by selecting "View deployed URL". Since the build deployed an API, database, and authentication backend, you will be able to create new to-do items. - -In the Amplify console, click into the deployment branch (in this case **main**) > select **Data** in the left-hand menu > **Data manager** to see the data entered in your database. - -## Make frontend updates - -Let's learn how to enhance the app functionality by creating a delete flow for to-do list items. - -### 4. Set up local environment - -Now let's set up our local development environment to add features to the frontend. Click on your deployed branch and you will land on the **Deployments** page which shows you your build history and a list of deployed backend resources. - -At the bottom of the page you will see a tab for **Deployed backend resources**. Click on the tab and then click the **Download amplify_outputs.json file** button. - -![](/images/gen2/getting-started/react/amplify-outputs-download.png) - -Clone the repository locally. - -```bash title="Terminal" showLineNumbers={false} -git clone https://github.com//amplify-next-template.git -cd amplify-next-template && npm install -``` - -Now move the `amplify_outputs.json` file you downloaded above to the root of your project. - -```text -β”œβ”€β”€ amplify -β”œβ”€β”€ src -β”œβ”€β”€ amplify_outputs.json <== backend outputs file -β”œβ”€β”€ package.json -└── tsconfig.json -``` - - -The **amplify_outputs.json** file contains backend endpoint information, publicly-viewable API keys, authentication flow information, and more. The Amplify client library uses this outputs file to connect to your Amplify Backend. You can review how the outputs file is imported within the `main.tsx` file and then passed into the `Amplify.configure(...)` function of the Amplify client library. - - -### 5. Implement delete functionality - -Go to the **app/page.tsx** file and add in a new `deleteTodo` functionality and pass function into the `
  • ` element's `onClick` handler. - -```tsx title="app/page.tsx" -function App() { - // ... - // highlight-start - function deleteTodo(id: string) { - client.models.Todo.delete({ id }) - } - // highlight-end - - return ( -
    -

    My todos

    - -
      - {todos.map(todo =>
    • deleteTodo(todo.id)} - key={todo.id}> - {todo.content} -
    • )} -
    -
    - πŸ₯³ App successfully hosted. Try creating a new todo. -
    - Review next step of this tutorial. -
    -
    - ) -} -``` -
    See the complete amplify/data/resources.ts - -Open the `amplify/data/resource.ts` file in your text editor, and you will see a default data model generated for you. - -```ts showLineNumbers title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a - .model({ - content: a.string() - }) - .authorization(allow => [allow.owner(), allow.publicApiKey().to(['read'])]) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - // API Key is used for allow.publicApiKey() rules - apiKeyAuthorizationMode: { - expiresInDays: 30 - } - } -}); -``` - - The schema generated by Amplify is for a to-do app. A schema is a blueprint - for how our app's data will be organized. Within the schema, we will define - models that will correspond to a database tableβ€”`Todo` in the above code. - Finally, we will define fields, which are attributes that each data instance - will haveβ€”in the generated code, the field is `content`. Each - field will have a type attached to itβ€”in the above examples, we are stating - that the `content` field is a string. -
    - -Try out the deletion functionality now by starting the local dev server: - -```bash title="Terminal" showLineNumbers={false} -npm run dev -``` - -This should start a local dev server at http://localhost:3000. - -### 6. Implement login UI - -The starter application already has a pre-configured auth backend defined in the **amplify/auth/resource.ts** file. We've configured it to support email and password login but you can extend it to support a variety of login mechanisms, including Google, Amazon, Sign In With Apple, and Facebook. - -The fastest way to get your login experience up and running is to use our Authenticator UI component. To properly integrate it with Next.js App Router, we'll create a client component wrapper and use it in the layout. - -First, create an AuthenticatorWrapper.tsx file in your app directory: - -```tsx title="app/AuthenticatorWrapper.tsx" -"use client" - -import { Authenticator } from "@aws-amplify/ui-react"; - -export default function AuthenticatorWrapper({ - children, -}: { - children: React.ReactNode; -}) { - return {children}; -} -``` - -Next, update your app/layout.tsx file to import and use the AuthenticatorWrapper component: - -```tsx title="app/layout.tsx" - -import React from "react"; -import { Amplify } from "aws-amplify"; -import "./app.css"; -// highlight-start -import AuthenticatorWrapper from "./AuthenticatorWrapper"; -import "@aws-amplify/ui-react/styles.css"; -// highlight-end -import outputs from "@/amplify_outputs.json"; - -Amplify.configure(outputs); - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - // highlight-start - - - - {children} - - - - // highlight-end - ); -} -``` - -The Authenticator component auto-detects your auth backend settings and renders the correct UI state based on the auth backend's authentication flow. - -In your **app/page.tsx** file, add a button to enable users to sign out of the application. Import the [`useAuthenticator`](https://ui.docs.amplify.aws/react/connected-components/authenticator/advanced#access-auth-state) hook from the Amplify UI library to hook into the state of the Authenticator. - -```tsx title="app/page.tsx" -import type { Schema } from "@/amplify/data/resource"; -// highlight-next-line -import { useAuthenticator } from "@aws-amplify/ui-react"; -import { useState, useEffect } from "react"; -import { generateClient } from "aws-amplify/data"; - -const client = generateClient(); - -export default function HomePage() { - - // highlight-start - const { signOut } = useAuthenticator(); - // highlight-end - - // ... - - return ( -
    - {/* ... */} - // highlight-next-line - -
    - ); -} -``` - -Try out your application in your localhost environment again. You should be presented with a login experience now. - -To get these changes to the cloud, commit them to git and push the changes upstream. - -```bash title="Terminal" showLineNumbers={false} -git commit -am "added authenticator" -git push -``` - -Amplify automatically deploys the latest version of your app based on your git commits. In just a few minutes, when the application rebuilds, the hosted app will be updated to support the deletion functionality. - -## Make backend updates - -Let's update our backend to implement per-user authorization rules, allowing each user to only access their own to-dos. - -### 7. Set up local AWS credentials - -To make backend updates, we are going to require AWS credentials to deploy backend updates from our local machine. - -**Skip ahead to step 8**, if you already have an AWS profile with credentials on your local machine, and your AWS profile has the `AmplifyBackendDeployFullAccess` permission policy. - -Otherwise, **[set up local AWS credentials](/[platform]/start/account-setup/)** that grant Amplify permissions to deploy backend updates from your local machine. - -### 8. Deploy cloud sandbox - -To update your backend without affecting the production branch, use Amplify's cloud sandbox. This feature provides a separate backend environment for each developer on a team, ideal for local development and testing. - -To start your cloud sandbox, run the following command in a **new Terminal window**: - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox -``` - -Once the cloud sandbox has been fully deployed (~5 min), you'll see the `amplify_outputs.json` file updated with connection information to a new isolated authentication and data backend. - -> **Info:** The `npx ampx sandbox` command should run concurrently to your `npm run dev`. You can think of the cloud sandbox as the "localhost-equivalent for your app backend". - -### 9. Implement per-user authorization - -The to-do items in the starter are currently shared across all users, but, in most cases, you want data to be isolated on a per-user basis. - -To isolate the data on a per-user basis, you can use an "owner-based authorization rule". Let's apply the owner-based authorization rule to your to-do items: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a.model({ - content: a.string(), - // highlight-next-line - }).authorization(allow => [allow.owner()]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - // This tells the data client in your app (generateClient()) - // to sign API requests with the user authentication token. - // highlight-next-line - defaultAuthorizationMode: 'userPool', - }, -}); -``` - -In the application client code, let's also render the username to distinguish different users once they're logged in. Go to your **app/page.tsx** file and render the `user` property from the `useAuthenticator` hook. - -```tsx title="app/page.tsx" -// ... imports - -function HomePage() { - // highlight-next-line - const { user, signOut } = useAuthenticator(); - - // ... - - return ( -
    - // highlight-next-line -

    {user?.signInDetails?.loginId}'s todos

    - {/* ... */} -
    - ) -} -``` - -Now, let's go back to your local application and test out the user isolation of the to-do items. - -You will need to sign up new users again because now you're working with the cloud sandbox instead of your production backend. - -To get these changes to the cloud, commit them to git and push the changes upstream. - -```bash title="Terminal" showLineNumbers={false} -git commit -am "added per-user data isolation" -git push -``` - -Once your build completes in the Amplify Console, the `main` backend will update to support the changes made within the cloud sandbox. The data in the cloud sandbox is fully isolated and won't pollute your production database. - -## πŸ₯³ Success - -That's it! You have successfully built a fullstack app on AWS Amplify. If you want to learn more about how to work with Amplify, here's the conceptual guide for [how Amplify works](/[platform]/how-amplify-works/concepts/). - ---- - ---- -title: "Configure AWS for local development" -section: "start" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-24T20:22:12.000Z" -url: "https://docs.amplify.aws/react/start/account-setup/" ---- - -> **Info:** **Note**: If you already have an AWS account and profile configured locally, you do not need to follow this guide. Please add the`AmplifyBackendDeployFullAccess` IAM role to your configured AWS profile. - -This guide will help you set up Temporary credentials with [IAM Identity Center](https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html) and [AWS Organizations](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_introduction.html), which will enable you to define Single-sign on (SSO), users, groups, permission sets, and more for your team. AWS Organizations can grow to house multiple AWS accounts. Users within the organization can traverse the AWS account(s) as their permission set allows. - -Amplify leverages the standard local credentials chain provider to simplify access to AWS services. While this guide highlights IAM Identity Center, you can explore additional methods for [authenticating with AWS locally](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html#getting-started-prereqs-keys). - -
    IAM Identity Center terminology - -IAM Identity Center enables users to sign in using a single user identity to access all their assigned AWS accounts, business applications, and custom applications in the AWS Cloud. This single sign-on capability reduces the complexity of managing multiple credentials and improves security by centralizing user authentication. - -### Users - -Users refers to the location where user identities and group information are stored and managed. IAM Identity Center can integrate with external identity sources like Microsoft Active Directory or use a built-in identity store provided by AWS. - -### Permission Set - -A collection of permissions that can be assigned to users or groups. Permission sets define what actions users are allowed to perform in your AWS accounts. They are similar to IAM policies but are used within the context of IAM Identity Center to manage access across multiple accounts. - -### AWS Organization - -AWS Organizations and IAM Identity Center work together to streamline management across multiple AWS accounts. AWS Organizations manages account structures and policies, while IAM Identity Center integrates with it to enable single sign-on and align permissions with organizational roles. This synergy ensures secure and consistent access control, simplifying user and permission management. - -### Local Profiles - -Credentials are typically resolved through the use of [AWS profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-using-profiles). Profiles can contain permanent credentials or SSO metadata, and can be set for use with Amplify by using the same techniques as the AWS CLI: - -- with the `--profile` flag -- with the `AWS_PROFILE` environment variable - -### Temporary credentials - -An alternative to permanent credentials, enable you to define permissions for a _session_. Sessions are created when you [_assume_ an IAM role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html) or sign in using AWS IAM Identity Center. These sessions come with an additional "session token" that is used to validate the temporary credentials and must be included on requests to AWS. As you are working locally, this will be presented as an additional environment variable. - -You can use temporary security credentials to make programmatic requests for AWS resources using the AWS CLI or AWS API (through the AWS SDKs). The temporary credentials provide the same permissions as long-term security credentials, such as IAM user credentials. However, there are a few differences, which are covered in the [AWS Identity and Access Management documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html). - -
    - -## Set up Identity Center - -Follow the steps below if **you have never set up AWS profiles before**. - -If you already have a profile, attach the [`AmplifyBackendDeployFullAccess`](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmplifyBackendDeployFullAccess.html) managed policy to your [IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_change-permissions.html#users_change_permissions-add-console). - -### 1. Create user with Amplify permissions - -Sign in to the AWS Console to access [IAM Identity Center page](https://console.aws.amazon.com/singlesignon/home) and choose **Enable**. - -![](/images/gen2/account-setup/sso-enable.png) - -A dialog will open, prompting you to "Choose how to configure IAM Identity Center in your AWS environment." Select **Enable with AWS Organizations** and choose **Continue**. - -![](/images/gen2/account-setup/sso-enable-dialog.png) - -Next, we are going to automate a number of steps that simulate the operations of setting up a user in the IAM Identity Center console. To get started open CloudShell, located in the console footer. - -Paste the following command in the CloudShell terminal and enter an email address you would like to associate with this AWS account: - -```bash title="CloudShell" showLineNumbers={false} -read -p "Enter email address: " user_email # hit enter -``` - -```console showLineNumbers={false} -Enter email address: -``` - -Now, run the following command - -```bash title="CloudShell" -response=$(aws sso-admin list-instances) -ssoId=$(echo $response | jq '.Instances[0].IdentityStoreId' -r) -ssoArn=$(echo $response | jq '.Instances[0].InstanceArn' -r) -email_json=$(jq -n --arg email "$user_email" '{"Type":"Work","Value":$email}') -response=$(aws identitystore create-user --identity-store-id $ssoId --user-name amplify-admin --display-name 'Amplify Admin' --name Formatted=string,FamilyName=Admin,GivenName=Amplify --emails "$email_json") -userId=$(echo $response | jq '.UserId' -r) -response=$(aws sso-admin create-permission-set --name amplify-policy --instance-arn=$ssoArn --session-duration PT12H) -permissionSetArn=$(echo $response | jq '.PermissionSet.PermissionSetArn' -r) -aws sso-admin attach-managed-policy-to-permission-set --instance-arn $ssoArn --permission-set-arn $permissionSetArn --managed-policy-arn arn:aws:iam::aws:policy/service-role/AmplifyBackendDeployFullAccess -accountId=$(aws sts get-caller-identity | jq '.Account' -r) -aws sso-admin create-account-assignment --instance-arn $ssoArn --target-id $accountId --target-type AWS_ACCOUNT --permission-set-arn $permissionSetArn --principal-type USER --principal-id $userId -# Hit enter -``` - -To validate that this worked, run the following command in the CloudShell. If something failed in this process, please **[report an issue](https://github.com/aws-amplify/amplify-backend/issues)**. Keep this information readily available for [the next step](#2-set-up-local-aws-profile). - -```bash title="CloudShell" showLineNumbers={false} -// highlight-next-line -printf "\n\nStart session url: https://$ssoId.awsapps.com/start\nRegion: $AWS_REGION\nUsername: amplify-admin\n\n" - -# you should see -Start session url: https://d-XXXXXXXXXX.awsapps.com/start -Region: us-east-1 -Username: amplify-admin -``` - -
    Prefer a manual set up? - -- After the AWS Organization is created and IAM Identity Center is enabled, you are presented with a dashboard. In the navigation pane, select **Permission sets**. - - ![AWS IAM Identity Center dashboard indicating "permission sets" in the navigation pane.](/images/gen2/account-setup/sso-dashboard-highlight-permission-sets.png) - -- Select **Create permission set**. -- When prompted for the permission set type, choose **Custom permission set**. Then choose **Next**. Expand **AWS Managed Policies (set)** and search for _amplify_. Select **AmplifyBackendDeployFullAccess** and choose **Next**. - - ![AWS IAM Identity Center custom permission set page with the "AmplifyBackendDeployFullAccess" AWS managed policy selected.](/images/gen2/account-setup/sso-permission-set-custom.png) - -- Name the permission set _amplify-policy_ and optionally change the session duration. Choose **Next**. - - ![AWS IAM Identity Center custom permission set details page with the name "AmplifySet".](/images/gen2/account-setup/sso-permission-set-custom-details.png) - -- Review the permission set and choose **Create**. -- Once the permission set is created, you will return to the IAM Identity Center dashboard. You are now ready to create your first user. Using the navigation pane, select **Users**. -- Enter the user details, then choose **Next**. - - ![AWS IAM Identity Center user creation with the username "amplify-admin".](/images/gen2/account-setup/sso-create-user.png) - -- Optionally create and add the user to a group, and choose **Next**. -- Review the user information and select **Add user**. The user will then need to verify their email using the email specified during user creation. -- Once the new user is created, you will return to the IAM Identity Center dashboard. The next step is to grant the user access to an AWS account. For this demo, we will use the AWS account we used to create the Organization, but you can create a new AWS account under your organization for use with Amplify. Select the checkbox next to the management account and choose **Assign users or groups**. - - ![AWS IAM Identity Center "AWS accounts" page with the management account checked.](/images/gen2/account-setup/sso-aws-accounts.png) - -- When prompted to assign a user or group, select the **Users** tab, select the user created in step 13, and choose **Next**. - - ![AWS IAM Identity Center "AWS accounts" page assigning "amplify-admin" to the management AWS account](/images/gen2/account-setup/sso-aws-accounts-add-user.png) - -- Assign the permission set created in step 9 and choose **Next**. -- Review the assignment information and choose **Submit**. -- Now you are ready to sign in to the access portal. Navigate back to the IAM Identity Center dashboard. Within the **Settings summary** pane, copy the URL for your **AWS access portal URL**. - - ![AWS IAM Identity Center dashboard highlighting the AWS access portal URL.](/images/gen2/account-setup/sso-dashboard-access-portal.png) - -- Navigate to the copied URL and sign in as your user, _amplify-admin_. After signing in, you should have access to an AWS account. - - ![AWS IAM Identity Center access portal displaying an AWS account.](/images/gen2/account-setup/sso-access-portal.png) - -
    - -### 2. Create password for user - -Now create a password for the user that we need for the next step. In the IdC console, navigate to _Users > amplify_admin > Reset password > Send an email to the user with instructions for resetting the password_. - -Check your email (make sure you also check your spam folder). Click on the _Reset password_ link and choose a password of your choice. When signing in make sure to use _amplify-admin_ as the _Username_. - -![](/images/gen2/account-setup/sso-reset-password.png) - -## Finish local setup - -Now, set up an AWS profile that is linked to the user you just created on your local machine. There are a few options for [getting IAM Identity Center user credentials](https://docs.aws.amazon.com/singlesignon/latest/userguide/howtogetcredentials.html), but we will use the AWS CLI configuration wizard. - -### 3. Install the AWS CLI - -Install the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). - -#### [Mac] -In your browser, download the macOS pkg file: - -[Install on Mac](https://awscli.amazonaws.com/AWSCLIV2.pkg) - -#### [Windows] -In your browser, Download and run the AWS CLI MSI installer for Windows (64-bit): - -[Install on Windows](https://awscli.amazonaws.com/AWSCLIV2.msi) - -To install the AWS CLI, run the following commands. - -#### [Linux] - -```bash showLineNumbers={false} -curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" -unzip awscliv2.zip -./aws/install -i /usr/local/aws-cli -b /usr/local/bin -``` - -### 4. Set up local AWS profile - -Open your terminal, you are ready to configure an AWS profile that uses the SSO user. Use the information from CloudShell to populate the information below. - -```console title="Terminal" showLineNumbers={false} -//highlight-next-line -aws configure sso - -| SSO session name (Recommended): amplify-admin -| SSO start URL: -| SSO region: -| SSO registration scopes [sso:account:access]: -| Attempting to automatically open the SSO authorization page in your default browser. -| If the browser does not open or you wish to use a different device to authorize this request, open the following URL: -| -| https://device.sso.us-east-2.amazonaws.com/ -| -| Then enter the code: -| -| SOME-CODE - -## browser opens -``` - -After you provide this information, the browser will automatically open asking you to sign in with the username and password you just created and configure a multi-factor device to authenticate. - -Now return to the terminal and enter the following information: - -```console title="Terminal" showLineNumbers={false} -The only AWS account available to you is: -Using the account ID -The only role available to you is: amplify-policy -Using the role name "amplify-policy" -CLI default client Region [us-east-1]: -CLI default output format [None]: -``` - -**Make sure to set the profile name to `default`**. Alternatively, remember the auto-generated profile name; you will need this later. - -```console title="Terminal" showLineNumbers={false} -CLI profile name [amplify-policy-]: default -To use this profile, specify the profile name using --profile, as shown: - -aws s3 ls --profile default -``` - -If you inspect `~/.aws/config`, you should now see the SSO profile: - -```ini title="~/.aws/config" -[profile default] -sso_session = amplify-admin -sso_account_id = -sso_role_name = AdministratorAccess -region = -[sso-session amplify-admin] -sso_start_url = https://xxxxxx.awsapps.com/start# -sso_region = -sso_registration_scopes = sso:account:access -``` - -### 5. Bootstrap your AWS account - -Now you are ready to use this AWS profile with AWS Amplify. Open your Amplify project and start the sandbox. If you have multiple local profiles or named your profile something other than `default`, you can specify a profile with `--profile`. - -```bash title="Terminal" showLineNumbers={false} -// highlight-next-line -npx ampx sandbox - -# OR - -// highlight-next-line -npx ampx sandbox --profile - -``` - -Before you can start deploying resources in the cloud sandbox environment, Amplify will need to complete a one-time bootstrap setup for the account and AWS Region before it can start deploying resources. - -
    What is bootstrapping? - -Bootstrapping is the process of provisioning resources for the AWS CDK before you can deploy AWS CDK apps into an AWS environment. These resources include an Amazon S3 bucket for storing files and IAM roles that grant permissions needed to perform deployments. The required resources are defined in an AWS CloudFormation stack, called the bootstrap stack, which is usually named `CDKToolkit`. Like any AWS CloudFormation stack, it appears in the AWS CloudFormation console once it is deployed. You can learn more about this process in the [CDK documentation](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html). - -
    - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox --profile amplify-admin -The region us-east-1 has not been bootstrapped. Sign in to the AWS console as a Root user or Admin to complete the bootstrap process, then restart the sandbox. -If this is not the region you are expecting to bootstrap, check for any AWS environment variables that may be set in your shell or use --profile to specify a profile with the correct region. -``` - -During the first-time setup, `npx ampx sandbox` will ask you to sign in to the AWS Management Console. You must sign in as the account **root user** or as a user that has **AdministratorAccess** permissions. Once signed in, you will be redirected to the Amplify console. On the **Create new app** page, choose **Initialize setup now**. It may take a few minutes for the bootstrapping process to complete. - -![](/images/gen2/account-setup/profile5.png) - -## Success - -You have successfully completed the bootstrapping process and you can now return to the terminal to create a new Amplify sandbox environment: - -```bash showLineNumbers={false} -npx ampx sandbox --profile -``` - ---- - ---- -title: "Manual installation" -section: "start" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-10-28T20:46:05.000Z" -url: "https://docs.amplify.aws/react/start/manual-installation/" ---- - -To get started with AWS Amplify we recommend that you use our [quickstart](/[platform]/start/quickstart) starter template. However, for some use cases, it may be preferable to start from scratch, either with a brand new directory or an existing frontend app. In that case we recommend to use [npm](https://npmjs.com) with [`create-amplify`](https://www.npmjs.com/package/create-amplify). - -```bash title="Terminal" showLineNumbers={false} -npm create amplify@latest -``` - -```console title="Terminal" showLineNumbers={false} -? Where should we create your project? (.) # press enter -``` - -Running this command will scaffold a lightweight Amplify project in your current project with the following files: - -```text -β”œβ”€β”€ amplify/ -β”‚ β”œβ”€β”€ auth/ -β”‚ β”‚ └── resource.ts -β”‚ β”œβ”€β”€ data/ -β”‚ β”‚ └── resource.ts -β”‚ β”œβ”€β”€ backend.ts -β”‚ β”œβ”€β”€ tsconfig.json -β”‚ └── package.json -β”œβ”€β”€ node_modules/ -β”œβ”€β”€ .gitignore -β”œβ”€β”€ package-lock.json -β”œβ”€β”€ package.json -└── tsconfig.json -``` - - If needed, you can manually install AWS Amplify without using `create-amplify` or the starter template. This guide will walk you through how to initialize your project, install dependencies, and author your first backend. - -## Manual setup - -First, if your frontend framework of choice doesn't have it already, create your project's `package.json` with `npm init -y`. Then, install the Amplify dependencies for building a backend: - -```bash title="Terminal" showLineNumbers={false} -npm add --save-dev @aws-amplify/backend@latest @aws-amplify/backend-cli@latest typescript -``` - -> **Info:** **Note**: TypeScript is not a requirement but is recommended for an optimal experience. - -Next, create the entry point for your backend, `amplify/backend.ts`, with the following code: - -```ts -import { defineBackend } from '@aws-amplify/backend'; - -defineBackend({}); -``` - -Now you can run `npx ampx sandbox` to create your first backend! - -> **Warning:** Amplify Gen 2 requires your backend to be configured for use with [ECMAScript modules (ESM)](https://nodejs.org/api/esm.html). If you encounter the following error during `ampx sandbox`, consider modifying your `package.json` with `"type": "module"`: -> -> ```text -The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("@aws-amplify/backend")' call instead. -``` -> -> Or, you can create a local file in the Amplify backend directory, `amplify/package.json`: -> -> ```json -{ - "type": "module" -} -``` - -You can use `define*` functions to _define_ your resources. For example, you can define authentication: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from '@aws-amplify/backend'; - -export const auth = defineAuth({ - loginWith: { - email: true - } -}); -``` - -Or define your data resource: - -```ts title="amplify/data/resource.ts" -import { a, defineData, type ClientSchema } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a.model({ - content: a.string(), - isDone: a.boolean() - }) - .authorization(allow => [allow.publicApiKey()]) -}); - -export type Schema = ClientSchema; -export const data = defineData({ - schema -}); -``` - -Each of these newly defined resources are then imported and set in the backend definition: - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { auth } from './auth/resource'; -import { data } from './data/resource'; - -defineBackend({ - auth, - data -}); -``` - -## Upgrade existing projects - -You can also update an existing frontend app. To upgrade existing Amplify code-first DX (Gen 2) apps, use your Node.js package manager (for example, `npm`) to update relevant backend packages: - -```bash title="Terminal" showLineNumbers={false} -npm update @aws-amplify/backend @aws-amplify/backend-cli -``` - -## Next steps - -We recommend the following next steps: - -- [Learn more about defining authentication](/[platform]/build-a-backend/auth) -- [Learn more about defining data](/[platform]/build-a-backend/data) -- [Get started with cloud sandbox](/[platform]/deploy-and-host/sandbox-environments) -- [Deploy and host your first app](/[platform]/deploy-and-host/fullstack-branching) - ---- - ---- -title: "Connect to AWS resources" -section: "start" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-08-07T21:03:28.000Z" -url: "https://docs.amplify.aws/react/start/connect-to-aws-resources/" ---- - -export async function getStaticPaths() { - return getCustomStaticPath(meta.platforms); -} - -Amplify client libraries provide you with the flexibility to directly connect your application to AWS resources such as AWS AppSync, Amazon Cognito, Amazon S3, and more. - -To get started, client libraries must be _configured_. This is typically done by using the [`amplify_outputs.json` file](/[platform]/reference/amplify_outputs) generated by the Amplify backend tooling, however using the client libraries does not require backend resources to be created by Amplify. - - -For JavaScript-based applications, the client library can be configured by using the generated outputs file: - -```ts title="src/main.ts" -import { Amplify } from "aws-amplify" -import outputs from "../amplify_outputs.json" - -Amplify.configure(outputs) -``` - -Or by configuring the library directly by passing a [`ResourcesConfig`](https://aws-amplify.github.io/amplify-js/api/interfaces/aws_amplify.index.ResourcesConfig.html) object. For example, to configure the client library for use with Amazon Cognito, specify the `Auth` configuration: - -```ts title="src/main.ts" -import { Amplify } from "aws-amplify" - -Amplify.configure({ - Auth: { - Cognito: { - userPoolId: "", - userPoolClientId: "", - identityPoolId: "", - loginWith: { - email: true, - }, - signUpVerificationMethod: "code", - userAttributes: { - email: { - required: true, - }, - }, - allowGuestAccess: true, - passwordFormat: { - minLength: 8, - requireLowercase: true, - requireUppercase: true, - requireNumbers: true, - requireSpecialCharacters: true, - }, - }, - }, -}) -``` - -By configuring the client library, Amplify automates the communication with the underlying AWS resources, and provides a friendly API to author your business logic. In the snippet below, the `signIn` function does not require passing information from your Cognito resource to initiate the sign-in flow. - -```ts title="src/main.ts" -import { signIn } from "aws-amplify/auth" - -await signIn({ - username: "john.doe@example.com", - password: "hunter2", -}) -``` - - -For mobile platforms, the client library can be configured by creating an `amplify_outputs.json` file in your project's directory. To get started, create the file and specify your resource configuration: - -```json title="amplify_outputs.json" -{ - "$schema": "https://raw.githubusercontent.com/aws-amplify/amplify-backend/main/packages/client-config/src/client-config-schema/schema_v1.json", - "version": "1", - "auth": { - "user_pool_id": "", - "aws_region": "", - "user_pool_client_id": "", - "identity_pool_id": "", - "mfa_methods": [], - "standard_required_attributes": [ - "email" - ], - "username_attributes": [ - "email" - ], - "user_verification_types": [ - "email" - ], - "mfa_configuration": "NONE", - "password_policy": { - "min_length": 8, - "require_lowercase": true, - "require_numbers": true, - "require_symbols": true, - "require_uppercase": true - }, - "unauthenticated_identities_enabled": true - } -} -``` - - -For more information about how to use the Amplify client libraries with existing AWS resources, visit the guides: - - - -[Connect to Cognito](/[platform]/build-a-backend/auth/use-existing-cognito-resources/) - -Connect to Cognito resources using Amplify Auth's client library - - - ---- - ---- -title: "Kotlin Coroutines support" -section: "start" -platforms: ["android"] -gen: 2 -last-updated: "2024-05-06T19:20:38.000Z" -url: "https://docs.amplify.aws/react/start/kotlin-coroutines/" ---- - -Amplify provides an optional and separate API surface which is entirely focused on using Kotlin's [coroutines](https://developer.android.com/kotlin/coroutines) and [flows](https://developer.android.com/kotlin/flow). - -To use it, import **`Amplify`** facade from `core-kotlin` instead of from `core`. See the Installation notes below for more details. - -With the Coroutines APIs, most Amplify functions are expressed as `suspend` functions. Suspending functions can be launched using one of the [lifecycle-aware coroutine scopes](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope) in the Android Architecture components: - -```kotlin -import com.amplifyframework.kotlin.core.Amplify -// ... - -val post = Post.builder() - .title("My First Post") - .build() - -lifecycleScope.launch { - try { - Amplify.DataStore.save(post) // This is suspending function! - Log.i("AmplifyKotlinDemo", "Saved a post") - } catch (failure: DataStoreException) { - Log.e("AmplifyKotlinDemo", "Save failed", failure) - } -} -``` - -Coroutines can greatly improve the readability of dependent, asynchronous calls. Moreover, you can use scopes, dispatchers, and other Kotlin coroutine primitives to get more control over your execution context. - -Let's consider what happens when you have three dependent operations. You want to save a `Post`, then an `Editor`, and finally a `PostEditor`. With Amplify's coroutines interface, you can write these operations sequentially: - -```kotlin -lifecycleScope.launch { - try { - listOf(post, editor, postEditor) - .forEach { Amplify.DataStore.save(it) } - Log.i("AmplifyKotlinDemo", "Post, Editor, and PostEditor saved") - } catch (failure: DataStoreException) { - Log.e("AmplifyKotlinDemo", "An item failed to save", failure) - } -} -``` - -In Amplify's vanilla APIs, this would have created a large block of code with three nested callbacks. - -## Installation - -Amplify's coroutine support is included in an optional module, `core-kotlin`. - -1. Under **Gradle Scripts**, open **build.gradle.kts (Module :app)**, and add the following line in `dependencies`: - - ```kotlin title="app/build.gradle.kts" - dependencies { - // Add the below line in `dependencies` - implementation("com.amplifyframework:core-kotlin:ANDROID_VERSION") - } - ``` - -2. Wherever you use the **`Amplify`** facade, import `com.amplifyframework.kotlin.core.Amplify` instead of `com.amplifyframework.core.Amplify`: - - ```kotlin - import com.amplifyframework.kotlin.core.Amplify - ``` - -## Usage - -Amplify tries to map the behavior of your callback-based APIs to Kotlin primitives in an intuitive way. Functions whose callbacks emit a single value (or error) are now expressed as suspending functions, returning the value instead. Functions whose callbacks emit a stream of values will now return Kotlin `Flow`s, instead. - -## Special cases - -Some APIs return an operation which can be cancelled. Examples include realtime subscriptions to an API, and uploading/downloading objects from Storage. - -### API subscriptions - -The API category's `subscribe()` function uses both a suspend function _and_ a Flow. The function suspends until the API subscription is established. Then, it starts emitting values over the Flow. - -```kotlin -lifecycleScope.async { - try { - Amplify.API.subscribe(request) // Suspends until subscription established - .catch { Log.e("AmplifyKotlinDemo", "Error on subscription", it) } - .collect { Log.i("AmplifyKotlinDemo", "Data on subscription = $it") } - } catch (error: ApiException) { - Log.e("AmplifyKotlinDemo", "Failed to establish subscription", error) - } -} -``` - -### Storage upload & download operations - -The Storage category's `downloadFile()` and `uploadFile()` functions are bit more complex. These APIs allow you to observe transfer progress, and also to obtain a result. Progress results are delivered over a Flow, returned from the `progress()` function. Completion events are delivered by a suspending `result()` function. - -```kotlin -// Download -val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example.txt"), localFile) - -lifecycleScope.async { - download - .progress() - .collect { Log.i("AmplifyKotlinDemo", "Download progress = $it") } -} - -lifecycleScope.async { - try { - val result = download.result() - Log.i("AmplifyKotlinDemo", "Download finished! ${result.file.path}") - } catch (failure: StorageException) { - Log.e("AmplifyKotlinDemo", "Download failed", failure) - } -} - -// Upload -val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example.txt"), localFile) - -lifecycleScope.async { - upload - .progress() - .collect { Log.i("AmplifyKotlinDemo", "Upload progress = $it") } -} -lifecycleScope.async { - try { - val result = upload.result() - Log.i("AmplifyKotlinDemo", "Upload finished! ${result.path}") - } catch (failure: StorageException) { - Log.e("AmplifyKotlinDemo", "Upload failed", failure) - } -} -``` - ---- - ---- -title: "Gen 2 for Gen 1 customers" -section: "start" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-06-24T18:00:35.000Z" -url: "https://docs.amplify.aws/react/start/migrate-to-gen2/" ---- - -## Migrating from Gen 1 to Gen 2 - -We are actively developing migration tooling to aid in transitioning your project from Gen 1 to Gen 2. Until then, we recommend you continue working with your Gen 1 Amplify project. We remain committed to supporting both Gen 1 and Gen 2 for the foreseeable future. For new projects, we recommend adopting Gen 2 to take advantage of its [enhanced capabilities](/[platform]/how-amplify-works/concepts/). Meanwhile, customers on Gen 1 will continue to receive support for high-priority bugs and essential security updates. - -## Gen 1 vs. Gen 2 feature matrix - -The tables below present a feature matrix for Gen 1 customers who are considering Gen 2 for their apps. This will help determine the support availability for various features. - -### Auth - -| Feature | Gen 1 | Gen 2 | -|---|---|---| -| Configure username | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | -| Configure email | Yes | Yes | -| Configure phone number | Yes | Yes | -| Facebook | Yes | Yes | -| Google | Yes | Yes | -| Amazon | Yes | Yes | -| Sign-in with Apple | Yes | Yes | -| Add user pool groups | Yes | Yes | -| User pool group preference | Yes | Yes | -| Email verification link redirect | Yes | Yes | -| Sign-up attributes | Yes | Yes | -| Auth trigger support | Yes | Yes | -| Auth trigger templates: Add Google reCaptcha Challenge | Yes | Yes | -| Auth trigger templates: Add user to Group | Yes | Yes | -| Auth trigger templates: Email Domain Filtering (denylist) | Yes | Yes | -| Auth trigger templates: Email Domain Filtering (allowlist) | Yes | Yes | -| Auth trigger templates: Override ID Token Claims | Yes | Yes | -| Auth trigger templates: Custom Auth Challenge Flow| Yes | No | -| Configure default password policy | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | -| Configure read/write capabilities for attributes | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | -| Oauth flow: Configure authorization v implicit grant | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | -| Admin queries | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | -| MFA login (on/off/optional) | Yes | Yes | -| MFA: SMS | Yes | Yes | -| MFA: TOTP | Yes | Yes | -| Zero-config Authenticator support | Yes | Yes | -| User management in console | Yes | Yes | -| Configure Oauth scopes | Yes | Yes | -| Email verification - code | Yes | Yes | -| Email Verification - Link | Yes | Yes | -| Oauth flow: Configure redirect URIs | Yes | Yes | -| Ability to set a friendly name for User Pool | Yes | Yes | -| Unauthenticated logins | Yes | Yes | -| Custom attributes | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | -| Oauth flow: Configure domain name prefix | Yes | [Yes with CDK](/[platform]/build-a-backend/auth/modify-resources-with-cdk/) | -| Auth configuration in console | Yes | No | -| First class OIDC support | No | Yes | -| First class SAML support | No | Yes | -| Import auth | Yes | No | - -### Data - -| Feature | Gen 1 | Gen2 | -|---|---|---| -| model | Yes | Yes | -| primaryKey | Yes | Yes | -| secondaryKey (name, sortKeyFields, query) | Yes | Yes | -| hasOne | Yes | Yes | -| hasMany | Yes | Yes | -| belongsTo | Yes | Yes | -| manyToMany | Yes | No | -| default | Yes | Yes | -| **auth - model level** | | | -| auth - public - apiKey | Yes | Yes | -| auth - public - iam | Yes | Yes | -| auth - owner - userPools | Yes | Yes | -| auth - owner - ownerField - userPools | Yes | Yes | -| auth - owner - ownerField as array - userPools | Yes | Yes | -| auth - owner - oidc | Yes | Yes | -| auth - owner - ownerField - oidc | Yes | Yes | -| auth - owner - ownerField as array - oidc | Yes | Yes | -| auth - private - userPools | Yes | Yes | -| auth - private - oidc | Yes | Yes | -| auth - private - iam | Yes | Yes | -| auth - group - userPools | Yes | Yes | -| auth - group - dynamic - userPools | Yes | Yes | -| auth - group - oidc | Yes | Yes | -| auth - group - dynamic - oidc | Yes | Yes | -| auth - custom - function | Yes | Yes | -| **auth - field level** | | | -| auth - public - apiKey | Yes | Yes | -| auth - public - iam | Yes | Yes | -| auth - owner - userPools | Yes | Yes | -| auth - owner - ownerField - userPools | Yes | Yes | -| auth - owner - ownerField as array - userPools | Yes | Yes | -| auth - owner - oidc | Yes | Yes | -| auth - owner - ownerField - oidc | Yes | Yes | -| auth - owner - ownerField as array - oidc | Yes | Yes | -| auth - private - userPools | Yes | Yes | -| auth - private - oidc | Yes | Yes | -| auth - private - iam | Yes | Yes | -| auth - group - userPools | Yes | Yes | -| auth - group - dynamic - userPools | Yes | Yes | -| auth - group - oidc | Yes | Yes | -| auth - group - dynamic - oidc | Yes | Yes | -| auth - custom - function | Yes | Yes | -| **other directives** | | | -| searchable | Yes | No but we offer a guide using Zero-ETL DynamoDB-to-OpenSearch | -| predictions | Yes | No but we offer a guide with AI service integrations | -| **Custom Mutations, Queries, Subscriptions** | Yes | Yes | -| VTL handler | Yes | Yes with CDK | -| JavaScript resolver handler | No | Yes | -| function handler | Yes | Yes | -| http handler | Yes | Yes - we support custom data sources including `http` | -| **Other configurations** | | | -| DataStore support | Yes | No but we'll offer a migration guide soon | -| Visual configuration | Yes | No - Gen 2 is code-first by design | -| @model queries, mutations, subscriptions, and timestamps modifiers | Yes | No | -| Custom GraphQL Transformer plugins | Yes | No | -| MySQL and PostgreSQL support | No | Yes | -| In-IDE end-to-end type safety | No | Yes | -| @hasOne, @hasMany, and @belongsTo on required fields | Yes | No | -| fields argument on @hasOne, @hasMany, and @belongsTo | Yes | No | - -### Storage - -| Feature | Gen 1 | Gen 2 | -|---|---|---| -| Ability to provision S3 bucket | Yes | Yes | -| Auth and Guest access | Yes | [Yes](/[platform]/build-a-backend/storage/authorization/#for-gen-1-public-protected-and-private-access-pattern) | -| Auth - Configure CRUD access | Yes | Yes | -| Configure Cognito Group CRUD access | Yes | Yes | -| Guest - Configure CRUD access | Yes | Yes | -| Lambda trigger for S3 bucket | Yes | Yes | -| Import an S3 bucket | Yes | Yes | -| File browser in console | Yes | Yes | -| Ability to override/custom | Yes | Yes | -| S3 Lambda triggers | Yes | Yes | -| Locally test | Yes | Yes - with sandbox environments | -| Visual configuration | Yes | No - Gen 2 is code-first by design | -| File Browser in console | Yes | Yes | -| Import S3 buckets | Yes | No | - -### Functions - -| Feature | Gen 1 | Gen 2 | -|---|---|---| -| Function runtime: TypeScript | No | Yes | -| Function resource access permissions: auth | Yes | Yes | -| Function resource access permissions: function | Yes | Yes | -| Function resource access permissions: API | Yes | Yes | -| Function resource access permissions CRUD operations | Yes | Yes | -| Function resource access permissions: custom | No | Yes | -| Environment variables | Yes | Yes | -| Secrets | Yes | Yes | -| Cron jobs | Yes | Yes | -| Configure memory size | Yes | Yes | -| Function build options for Node.js | Yes | Yes | -| Function templates: AWS AppSync - GraphQL API request (with IAM) | Yes | Yes | -| Function templates: CRUD function for DynamoDB (Integration with API Gateway) | Yes | Yes | -| Function templates: GraphQL Lambda Authorizer | Yes | Yes | -| Function templates: Hello World | Yes | Yes | -| Function templates: Lambda trigger | Yes | Yes | -| Function logs in console | Yes | Yes | -| Function resource access permissions: geo | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/grant-access-to-other-resources/#using-cdk) | -| Function resource access permissions: analytics | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/grant-access-to-other-resources/#using-cdk) | -| Function runtime: .NET 6 | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/custom-functions/) | -| Function runtime: Go | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/custom-functions/) | -| Function runtime: Java | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/custom-functions/) | -| Function runtime: JavaScript | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/custom-functions/) | -| Function runtime: Python | Yes | [Yes with CDK](/[platform]/build-a-backend/functions/custom-functions/) | -| Lambda layers | Yes | No | - -### Other categories - - -[Amplify JS Auth API documentation](/[platform]/build-a-backend/auth/connect-your-frontend/) - -| Feature | Gen 1 | Gen 2 | -|---|---|---| -| REST API| Yes| No -| Analytics| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/analytics/) -| Geo| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/geo/) -| Predictions | Yes| No -| Interactions| Yes| No - - - -| Feature | Gen 1 | Gen 2 | -|---|---|---| -| REST API| Yes| Yes with custom CDK -| Analytics| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/analytics/) -| Geo| No| No -| Predictions | No| No -| Interactions| No| No - - - -| Feature | Gen 1 | Gen 2 | -|---|---|---| -| REST API| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/rest-api/) -| Analytics| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/analytics/) -| Geo| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/geo/) -| Predictions | Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/predictions/) -| Interactions| Yes| [Yes with custom CDK](/[platform]/build-a-backend/add-aws-services/interactions/) - - ---- - ---- -title: "Platform setup" -section: "start" -platforms: ["flutter"] -gen: 2 -last-updated: "2025-12-29T21:54:06.000Z" -url: "https://docs.amplify.aws/react/start/platform-setup/" ---- - -## iOS - -Amplify requires a minimum deployment target of 13.0 and Xcode 15.0 or higher when targeting iOS. Follow the steps below to update the minimum deployment target. - -Open `ios/Podfile` and update the target iOS platform to 13.0 or higher. - -> **Info:** If there is no file located at `ios/Podfile`, add `amplify_flutter` to your `pubspec.yaml` and run `pub get`. This will automatically create the file. - -```diff title="ios/Podfile" -- # Uncomment this line to define a global platform for your project -- # platform :ios, '12.0' -+ platform :ios, '13.0' -``` - -Open your project in Xcode and select Runner, Targets -> Runner and then the "General" tab. Under the "Minimum Deployments" section, update the iOS version to 13.0 or higher. - -![Setting the iOS version to 13.0 or higher in the minimum deployments section of the Runner general window.](/images/project-setup/flutter/ios/target-min-deployment-version.png) - -Select Runner, Project -> Runner and then the "Build Settings" tab. Update "iOS Deployment Target" to 13.0 or higher. - -![Setting the iOS version to 13.0 or higher in the deployment targets section of the Runner info window.](/images/project-setup/flutter/ios/project-min-deployment-version.png) - -## Android - -Amplify Flutter supports API level 24+ (Android 7.0+), and requires Gradle 8+, Kotlin 1.9+, and Java 17+ when targeting Android. Follow the steps below to apply these changes in your app. - -> **Warning:** The steps below are intended for Flutter apps created with Flutter version 3.16+. If your app was created prior to version 3.16, please follow the guide [here](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) to migrate to Gradle's declarative plugins block before following the steps below. - -#### [Gradle Kotlin] -1. Open `android/settings.gradle.kts` and update the Android Gradle plugin and kotlin versions: - -```diff title="android/settings.gradle.kts" -plugins { - id("dev.flutter.flutter-plugin-loader") version "1.0.0" -- id("com.android.application") version "8.7.0" apply false -- id("org.jetbrains.kotlin.android") version "1.8.22" apply false -+ id("com.android.application") version "8.12.1" apply false -+ id("org.jetbrains.kotlin.android") version "2.2.0" apply false -} -``` - -2. Open `android/gradle/wrapper/gradle-wrapper.properties` and update the Gradle `distributionUrl`. - -```diff title="android/gradle/wrapper/gradle-wrapper.properties" -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists --distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip -+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip -``` - -3. Open `android/app/build.gradle.kts` and update the Java version and minimum Android SDK version. - -```diff title="android/app/build.gradle.kts" -android { - namespace = "com.example.myapp" - compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion - compileOptions { -- sourceCompatibility = JavaVersion.VERSION_1_8 -- targetCompatibility = JavaVersion.VERSION_1_8 -+ sourceCompatibility = JavaVersion.VERSION_17 -+ targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { -- jvmTarget = JavaVersion.VERSION_11.toString() -+ jvmTarget = JavaVersion.VERSION_17.toString() - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.myapp" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. -- minSdk = flutter.minSdkVersion -+ minSdk = 24 - targetSdk = flutter.targetSdkVersion - versionCode = flutterVersionCode.toInteger() - versionName = flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.debug - } - } -} -``` - -#### [Gradle Groovy] -1. Open `android/settings.gradle` and update the Android Gradle plugin and kotlin versions: - -```diff title="android/settings.gradle" -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" -- id "com.android.application" version "7.3.0" apply false -- id "org.jetbrains.kotlin.android" version "1.7.10" apply false -+ id "com.android.application" version "8.12.1" apply false -+ id "org.jetbrains.kotlin.android" version "2.2.0" apply false -} -``` - -2. Open `android/gradle/wrapper/gradle-wrapper.properties` and update the Gradle `distributionUrl`. - -```diff title="android/gradle/wrapper/gradle-wrapper.properties" -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists --distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip -+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip -``` - -3. Open `android/app/build.gradle` and update the Java version and minimum Android SDK version. - -```diff title="android/app/build.gradle" -android { - namespace = "com.example.myapp" - compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion - compileOptions { -- sourceCompatibility = JavaVersion.VERSION_11 -- targetCompatibility = JavaVersion.VERSION_11 -+ sourceCompatibility = JavaVersion.VERSION_17 -+ targetCompatibility = JavaVersion.VERSION_17 - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.myapp" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. -- minSdk = flutter.minSdkVersion -+ minSdk = 24 - targetSdk = flutter.targetSdkVersion - versionCode = flutterVersionCode.toInteger() - versionName = flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.debug - } - } -} -``` - -> **Info:** If you would like to use a higher version of Gradle or Android Gradle plugin see the compatibility matrix [here](https://developer.android.com/build/releases/gradle-plugin#updating-gradle). - -### Network Permissions for Release Builds - -Flutter apps have access to make network requests by default in debug mode. This permission needs to be added when building in release mode. To do this, open `android/app/src/main/AndroidManifest.xml` and make the following addition. - -```xml title="android/app/src/main/AndroidManifest.xml" - -// highlight-start - -// highlight-end -... - -``` - -## Web - -There are no Amplify specific requirements or setup instructions when targeting web. You will need to use a browser supported by Flutter. See the following Flutter docs for more info: - -- [Supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) -- [FAQ: Which web browsers are supported by Flutter?](https://docs.flutter.dev/development/platform-integration/web/faq#which-web-browsers-are-supported-by-flutter) - -## macOS - -Amplify requires a minimum deployment target of 10.15 and Xcode 15.0 or higher when targeting macOS. Additionally, you will need to enable networking, keychain entitlements, and code signing. - -### Update Minimum Version - -Open `macos/Podfile` and update the target macOS platform to 10.15 or higher. - -> **Info:** If there is no file located at `macos/Podfile`, add `amplify_flutter` to your `pubspec.yaml` and run `pub get`. This will automatically create the file. - -```diff title="ios/Podfile" -- platform :osx, '10.14' -+ platform :osx, '10.15' -``` - -Open your project in Xcode and select Runner, Targets -> Runner and then the "General" tab. Under the "Minimum Deployments" section, update the macOS version to 10.15 or higher. - -![Setting the macOS version to 10.15 or higher in the Minimum Deployments tab of the Runner general section.](/images/project-setup/flutter/mac/target-min-deployment-version.png) - -Select Runner, Project -> Runner and then the "Info" tab. Update "macOS Deployment Target" to 10.15 or higher. - -![Setting the macOS version to 10.15 or higher in the macOS Deployment Target tab of the Runner info section.](/images/project-setup/flutter/mac/project-min-deployment-version.png) - -### Enable Network Calls - -Open your project in Xcode and select Runner, Targets -> Runner and then the "Signing and Capabilities" tab. Under "App Sandbox" select "Outgoing Connections (Client)". - -![Selecting outgoing connections in the app sandbox section of the runner signing and capabilities tab.](/images/project-setup/flutter/mac/xcode-entitlements.png) - -For more info on the Networking entitlement, see Apple's documentation on [com.apple.security.network.client](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_network_client). - -### Enable Keychain Sharing - -> **Info:** This capability is required because Amplify uses the Data Protection Keychain on macOS as a platform best practice. -> See [TN3137: macOS keychain APIs and implementations](https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains) -> for more information on how Keychain works on macOS and the Keychain Sharing entitlement. - -Open your project in Xcode and select Runner, Targets -> Runner and then the "Signing and Capabilities" tab. - -1. Click the "+ icon". - -![Plus icon circled in the signing and capabilities section of the runner tab.](/images/project-setup/flutter/mac/enable-keychain-access.png) - -2. Search for "Keychain Sharing" in the subsequent modal, and add it. - -![Keychain Sharing search result after searching keychain.](/images/project-setup/flutter/mac/search-keychain-sharing.png) - -3. Scroll down to "Keychain Sharing" in the "Signing and Capabilities" and click the "+" icon. By default, your bundle ID will be used. - -![Plus icon highlighted in the keychain sharing section of the signing and capabilities section of runner.](/images/project-setup/flutter/mac/adding-keychain-access-group.png) - -4. Finally, add a development team and enable signing. - -![Team selector and Enable Development Signing button highlighted in the signing and capabilities section of the runner tab.](/images/project-setup/flutter/mac/enable-signing.png) - -## Windows - -There are no Amplify specific requirements or setup instructions when targeting Windows. You will need to use a Windows version supported by Flutter. See the following Flutter docs for more info: - -- [Supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) - -## Linux - -Amplify Flutter depends on the [libsecret](https://wiki.gnome.org/Projects/Libsecret) library when targeting Linux. - -### Local Development - -To run and debug an app that depends on Amplify Flutter, you must install `libsecret-1-dev`. Run the following commands to install `libsecret-1-dev`. this will also install dependencies of `libsecret-1-dev`, such as `libglib2.0-dev`. - -> **Info:** The command below is intended for Ubuntu. The command may vary on other Linux distributions. - -```terminal -sudo apt-get update -sudo apt-get install -y libsecret-1-dev -``` - -### Packaging Your App - -To include the required dependencies when packaging your app with Snapcraft, include them in your `snapcraft.yaml` file. For more info, see [Flutter's documentation on releasing to the Snap Store](https://docs.flutter.dev/deployment/linux). - -```yaml -parts: - my-app: - plugin: flutter - source: . - flutter-target: lib/main.dart - build-packages: - - libsecret-1-dev - stage-packages: - - libsecret-1-0 -``` - ---- - ---- -title: "Build with AI assistants" -section: "start" -platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] -gen: 2 -last-updated: "2025-12-11T15:26:24.000Z" -url: "https://docs.amplify.aws/react/start/mcp-server/" ---- - -AWS MCP Server brings the power of AI coding assistants to your Amplify development workflow. Using the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/)β€”an open protocol that enables AI tools to access external data and capabilitiesβ€”you can build, deploy, and manage fullstack Amplify applications faster than ever. - -Whether you're using Amazon Kiro, Cursor, Claude Desktop, or another MCP-compatible AI coding assistant, AWS MCP Server provides the context and tools your AI needs to help you build production-ready Amplify applications. - -## Why use AWS MCP Server? - -Building fullstack applications with Amplify involves many moving parts: authentication, data modeling, storage, serverless functions, and deployment. AWS MCP Server helps your AI coding assistant understand these components and guide you through implementing them correctly. - -Instead of copying and pasting from documentation or debugging configuration issues, you can describe what you want to build in natural language. Your AI assistant uses AWS MCP Server to access the right information and execute AWS operations on your behalf. - -## Core capabilities - -AWS MCP Server provides three core capabilities that enhance your AI-assisted development experience: - -### 1. Pre-built workflows - -Pre-built workflows (called "Agent SOPs" in AWS terminology) are step-by-step guides that help your AI assistant complete complex Amplify tasks following best practices. These workflows cover common scenarios like: - -- **Backend Implementation**: Setting up authentication, data models, storage, serverless functions, and AI features -- **Frontend Integration**: Connecting your frontend framework to Amplify backend services -- **Deployment Guide**: Configuring sandbox environments, production deployments, and CI/CD pipelines - -When you ask your AI assistant to help with one of these tasks, it follows the pre-built workflow to ensure you get a production-ready implementation. - -### 2. Documentation access - -AWS MCP Server gives your AI assistant direct access to the latest Amplify documentation. This means your assistant can provide accurate, up-to-date guidance without you needing to search through docs yourself. - -The documentation access includes: -- API references and code examples -- Configuration options and best practices -- Troubleshooting guides and common patterns - -### 3. AWS API execution - -With proper authentication, AWS MCP Server can execute AWS operations directly from your AI assistant. This enables workflows like: - -- Creating and configuring Amplify backends -- Deploying applications to AWS -- Managing AWS resources - -All operations respect your IAM permissions, so your AI assistant can only perform actions you're authorized to do. - -## Getting started - -Ready to supercharge your Amplify development with AI? Follow these guides to get started: - ---- - ---- -title: "Set up AWS MCP Server" -section: "start/mcp-server" -platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] -gen: 2 -last-updated: "2025-12-11T15:26:24.000Z" -url: "https://docs.amplify.aws/react/start/mcp-server/set-up-mcp/" ---- - -Follow the [AWS MCP Server setup guide](https://docs.aws.amazon.com/aws-mcp/latest/userguide/getting-started-aws-mcp-server.html) to configure AWS MCP Server with your AI coding assistant. The guide covers: - -- Installing prerequisites (AWS CLI, uv package manager) -- Configuring your MCP client (Amazon Kiro, Cursor, Claude Desktop, and others) -- Setting up AWS credentials and IAM permissions -- Testing your connection - -## Amplify-specific setup tips - -Once you have AWS MCP Server configured, here are some tips for Amplify development: - -### AWS credentials for Amplify - -If you haven't set up AWS credentials for Amplify development yet, follow the [Configure AWS for local development](/[platform]/start/account-setup/) guide. This ensures you have the right permissions for both AWS MCP Server and Amplify CLI operations. - -### IAM permissions - -The base AWS MCP Server permissions allow your AI assistant to access the guided workflows and documentation. For Amplify development, your credentials also need permissions to create and manage Amplify resources. - -If you're using the `AmplifyBackendDeployFullAccess` managed policy for Amplify development, you likely have sufficient permissions. If you encounter permission errors, check with your AWS administrator. - -### Verify Amplify workflows are available - -After setup, verify that the Amplify workflows are accessible by asking your AI assistant: - -``` -Which guided workflows are available for Amplify development? -``` - -Your assistant should describe the Backend Implementation, Frontend Integration, and Deployment Guide workflows. - -## Next steps - -Now that you have AWS MCP Server configured, learn about the [guided workflows](/[platform]/start/mcp-server/amplify-workflows/) available to accelerate your Amplify development. - ---- - ---- -title: "Guided workflows" -section: "start/mcp-server" -platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] -gen: 2 -last-updated: "2025-12-11T15:26:24.000Z" -url: "https://docs.amplify.aws/react/start/mcp-server/amplify-workflows/" ---- - -AWS MCP Server includes pre-built workflows (called "Agent SOPs" in AWS terminology) that guide your AI coding assistant through complex Amplify development tasks. These workflows encode best practices and ensure consistent, production-ready implementations. - -This page describes the three main workflows available and shows you how to use them effectively. - -## Available workflows - -AWS MCP Server provides three pre-built workflows designed for different stages of Amplify development. These workflows work with both new and existing projects. - -| Workflow | Purpose | -| --- | --- | -| **Backend Implementation** | Add or modify authentication, data models, storage, functions, and AI features in new or existing backends | -| **Frontend Integration** | Add Amplify to an existing frontend app or enhance an app that already uses Amplify | -| **Deployment Guide** | Configure sandbox environments, production deployments, and CI/CD for any Amplify project | - -## Backend Implementation workflow - -The Backend Implementation workflow helps you add or modify Amplify backend features following best practices. Whether you're starting fresh or adding features to an existing Amplify backend, this workflow guides you through the implementation. - -### Supported features - -
    Authentication (Auth) - -The workflow guides you through setting up Amplify Auth, including: - -- Configuring sign-up and sign-in flows -- Setting up multi-factor authentication (MFA) -- Configuring social sign-in providers (Google, Facebook, Apple, Amazon) -- Customizing authentication UI components -- Implementing password policies and account recovery - -**Example prompt:** -``` -Guide me through setting up Amplify authentication with email sign-in and Google social login. -``` - -
    - -
    Data modeling (Data) - -The workflow helps you design and implement your data layer: - -- Creating GraphQL schemas with proper relationships -- Setting up authorization rules for data access -- Configuring real-time subscriptions -- Implementing optimistic UI updates -- Setting up conflict resolution for offline scenarios - -**Example prompt:** -``` -Help me create a data model following Amplify best practices for a blog with posts, -comments, and user profiles. Posts should only be editable by their authors. -``` - -
    - -
    File storage (Storage) - -The workflow guides storage implementation: - -- Configuring S3 buckets with proper access controls -- Setting up file upload and download functionality -- Implementing access levels (public, protected, private) -- Configuring file validation and size limits - -**Example prompt:** -``` -Walk me through adding file storage so users can upload profile pictures with a 5MB limit. -``` - -
    - -
    Serverless functions (Functions) - -The workflow helps you create and deploy Lambda functions: - -- Setting up function triggers (API, scheduled, event-driven) -- Configuring environment variables and secrets -- Implementing function layers for shared code -- Setting up proper IAM permissions - -**Example prompt:** -``` -Guide me through creating a Lambda function that sends a welcome email when a new user signs up. -``` - -
    - -
    AI features (AI) - -The workflow guides AI/ML feature integration: - -- Setting up Amazon Bedrock for generative AI -- Implementing text generation and summarization -- Adding image analysis capabilities -- Configuring conversation flows - -**Example prompt:** -``` -Help me add an AI summarization feature following Amplify best practices for generative AI. -``` - -
    - -## Frontend Integration workflow - -The Frontend Integration workflow helps you add Amplify to your frontend application or enhance an existing Amplify integration. Whether you have an existing React, Vue, Angular, or mobile app that needs Amplify, or you're adding new features to an app that already uses Amplify, this workflow provides framework-specific patterns and best practices. - -### Framework patterns - -The workflow adapts its guidance based on your frontend framework: - -
    React and Next.js - -- Setting up the Amplify client library -- Using React hooks for authentication state -- Implementing data fetching with proper loading states -- Configuring server-side rendering (SSR) for Next.js -- Using Amplify UI components - -**Example prompt:** -``` -Help me connect my React app to Amplify following best practices. I need a sign-in page and protected routes. -``` - -
    - -
    Vue and Angular - -- Configuring Amplify with framework-specific patterns -- Implementing authentication guards and state management -- Setting up data binding with Amplify Data -- Using framework-appropriate UI components - -**Example prompt:** -``` -Guide me through setting up Amplify in my Vue app with a protected dashboard route. -``` - -
    - -
    React Native - -- Configuring Amplify for mobile development -- Implementing secure storage for tokens -- Setting up push notifications -- Handling offline scenarios with DataStore - -**Example prompt:** -``` -Walk me through adding offline support to my React Native app following Amplify best practices. -``` - -
    - -
    Flutter - -- Setting up Amplify Flutter packages -- Implementing authentication flows -- Configuring API and storage access -- Using Amplify UI components for Flutter - -**Example prompt:** -``` -Help me set up authentication in my Flutter app using the Amplify Authenticator widget. -``` - -
    - -
    Swift (iOS) - -- Configuring Amplify Swift packages -- Implementing async/await patterns for Amplify operations -- Setting up Combine publishers for real-time updates -- Using SwiftUI with Amplify - -**Example prompt:** -``` -Guide me through adding real-time data sync to my SwiftUI app following Amplify best practices. -``` - -
    - -
    Android (Kotlin) - -- Setting up Amplify Android libraries -- Implementing coroutines for async operations -- Configuring Jetpack Compose with Amplify -- Handling Android lifecycle with Amplify - -**Example prompt:** -``` -Walk me through connecting my Kotlin Android app to Amplify with user authentication. -``` - -
    - -## Deployment Guide workflow - -The Deployment Guide workflow helps you deploy your Amplify application through different stages, from local development to production. This works for new projects as well as existing applications that need deployment configuration or CI/CD setup. - -### Sandbox environments - -The workflow guides you through setting up personal cloud sandbox environments for development: - -- Creating isolated sandbox environments -- Configuring hot-reload for backend changes -- Managing sandbox lifecycle -- Sharing sandbox configurations with team members - -**Example prompt:** -``` -Guide me through setting up an Amplify sandbox environment for testing backend changes. -``` - -### Production deployment - -The workflow helps you deploy to production: - -- Configuring production-ready settings -- Setting up custom domains -- Implementing environment variables for different stages -- Configuring monitoring and logging - -**Example prompt:** -``` -Walk me through deploying my Amplify app to production with a custom domain. -``` - -### CI/CD integration - -The workflow guides continuous integration and deployment setup: - -- Configuring Amplify Hosting for automatic deployments -- Setting up branch-based deployments (preview, staging, production) -- Implementing pull request previews -- Configuring build settings and environment variables - -**Example prompt:** -``` -Help me set up CI/CD following Amplify best practices so my app deploys automatically when I push to main. -``` - -## Practical example: Building a fullstack app - -Here's an example of how you might use AWS MCP Server to build a complete Amplify application. This demonstrates how the pre-built workflows guide your AI assistant through each step. - -### Step 1: Set up the backend - -Start by describing your application to your AI assistant: - -``` -Help me build a task management app following Amplify best practices. Users should be able to: -- Sign up and sign in with email -- Create, edit, and delete their own tasks -- Mark tasks as complete -- See their tasks in real-time across devices -``` - -Your AI assistant uses the **Backend Implementation workflow** to: -1. Configure authentication with email sign-in -2. Create a Task data model with proper authorization rules -3. Set up real-time subscriptions for task updates - -### Step 2: Connect the frontend - -Next, ask your assistant to connect your frontend: - -``` -Guide me through connecting my React app to the Amplify backend. I need a sign-in page -and a dashboard that shows the user's tasks with real-time updates. -``` - -Your AI assistant uses the **Frontend Integration workflow** to: -1. Install and configure Amplify libraries -2. Set up the Authenticator component -3. Create data fetching hooks for tasks -4. Implement real-time updates in the UI - -### Step 3: Deploy the application - -Finally, deploy your application: - -``` -Walk me through setting up Amplify deployment so the app deploys automatically when I push -to GitHub. I also want preview deployments for pull requests. -``` - -Your AI assistant uses the **Deployment Guide workflow** to: -1. Connect your GitHub repository to Amplify Hosting -2. Configure branch-based deployments -3. Set up pull request previews -4. Configure production environment settings - -## Tips for effective prompts - -To get the best results from AWS MCP Server, keep these tips in mind: - -> **Info:** **Be specific about your requirements.** Instead of "add authentication," try "add authentication with email sign-in, Google social login, and MFA." - -- **Describe the user experience** you want, not just the technical implementation -- **Mention your framework** so the workflow can provide framework-specific guidance -- **Ask follow-up questions** if you need clarification on any step -- **Request explanations** if you want to understand why certain patterns are recommended - -### Optional: Steering files for teams - -For guaranteed consistency across all developers, you can add steering files that instruct the AI to always use the workflows. - -
    Amazon Kiro (IDE/CLI) - -Create a steering file at `.kiro/steering/amplify.md`: - -```markdown title=".kiro/steering/amplify.md" -# Amplify Development Guidelines - -When working on Amplify projects, use the AWS MCP Server guided workflows: - -- For backend features (auth, data, storage, functions, AI), call `retrieve_agent_sop` with `amplify-backend-implementation` -- For frontend integration, call `retrieve_agent_sop` with `amplify-frontend-integration` -- For deployment tasks, call `retrieve_agent_sop` with `amplify-deployment-guide` -``` - -
    - -
    Claude (Code/Desktop) - -Add to your `CLAUDE.md` file in the project root: - -```markdown title="CLAUDE.md" -# Amplify Development Guidelines - -When working on Amplify projects, use the AWS MCP Server guided workflows: - -- For backend features (auth, data, storage, functions, AI), call `retrieve_agent_sop` with `amplify-backend-implementation` -- For frontend integration, call `retrieve_agent_sop` with `amplify-frontend-integration` -- For deployment tasks, call `retrieve_agent_sop` with `amplify-deployment-guide` -``` - -
    - -
    Cursor - -Create a rules file at `.cursor/rules/amplify.mdc`: - -```markdown title=".cursor/rules/amplify.mdc" ---- -description: Amplify development guidelines -globs: - - "amplify/**" - - "src/**" ---- - -# Amplify Development Guidelines - -When working on Amplify projects, use the AWS MCP Server guided workflows: - -- For backend features (auth, data, storage, functions, AI), call `retrieve_agent_sop` with `amplify-backend-implementation` -- For frontend integration, call `retrieve_agent_sop` with `amplify-frontend-integration` -- For deployment tasks, call `retrieve_agent_sop` with `amplify-deployment-guide` -``` - -
    - -## Next steps - -Now that you understand the pre-built workflows, try using them in your next Amplify project: - -1. [Set up AWS MCP Server](/[platform]/start/mcp-server/set-up-mcp/) if you haven't already -2. Start a conversation with your AI assistant about what you want to build -3. Let the pre-built workflows guide you through implementation - -For more information about specific Amplify features, explore the documentation: - -- [Authentication](/[platform]/build-a-backend/auth/) -- [Data](/[platform]/build-a-backend/data/) -- [Storage](/[platform]/build-a-backend/storage/) -- [Functions](/[platform]/build-a-backend/functions/) - ---- - ---- -title: "Build & connect backend" -section: "build-a-backend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-02-21T20:06:17.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/" ---- - - - ---- - ---- -title: "Authentication" -section: "build-a-backend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-02T22:35:36.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/" ---- - -export async function getStaticPaths() { - return getCustomStaticPath(meta.platforms); -} - ---- - ---- -title: "Set up Amplify Auth" -section: "build-a-backend/auth" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-12-03T11:13:25.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/set-up-auth/" ---- - -Amplify Auth is powered by [Amazon Cognito](https://aws.amazon.com/cognito/). Cognito is a robust user directory service that handles user registration, authentication, account recovery, and other operations. [Review the concepts to learn more](/[platform]/build-a-backend/auth/concepts/). - -To get started with defining your authentication resource, open or create the auth resource file: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" - -/** - * Define and configure your auth resource - * @see https://docs.amplify.aws/gen2/build-a-backend/auth - */ -export const auth = defineAuth({ - loginWith: { - email: true, - }, -}) -``` - -By default, your auth resource is scaffolded using `email` as the default login mechanism. You can also configure your auth resource to allow signing in with: - -- Phone numbers -- External providers (Google, Facebook, Amazon, or Sign in with Apple) - -- [Passwordless authentication](/[platform]/build-a-backend/auth/concepts/passwordless/) (Email OTP, SMS OTP, or WebAuthn passkeys) - - -> **Info:** **Note:** At a minimum you will need to pass a `loginWith` value to set up how your users sign in to your app. Signing in with email and password is configured by default if you do not provide any value. - - -## Enable passwordless authentication - -You can enable passwordless authentication methods to provide a more secure and user-friendly experience: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from '@aws-amplify/backend'; - -export const auth = defineAuth({ - loginWith: { - email: { - otpLogin: true // Enable email-based one-time passwords - } - } -}); -``` - -[Learn more about passwordless authentication options](/[platform]/build-a-backend/auth/concepts/passwordless/). - - -## Deploy auth resource - -After you have chosen and defined your authentication resource, run the following command to create your resource in your personal cloud sandbox. - - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox -``` - - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox --outputs-format dart --outputs-out-dir lib -``` - - -> **Warning:** Be sure to add a "raw" folder under `app/src/main/res` directory if it does not exist. - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox --outputs-out-dir -``` - - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox -``` - - -After a successful deployment, this command also generates an outputs file (`amplify_outputs.json`) to enable your frontend app to connect to your backend resources. The values you configure in your backend authentication resource are set in the generated outputs file to automatically configure the frontend [`Authenticator connected component`](https://ui.docs.amplify.aws/react/connected-components/authenticator). - -## Connect your application code to your auth resource - -Creating and correctly implementing the sign-in flow can be challenging and time-consuming. Amplify's Authenticator UI component streamlines this by enabling you to rapidly build the entire authentication flow for your app. The component works seamlessly with configuration in `amplify/auth/resource.ts` to automatically connect with your backend resources. - -Amplify has pre-built UI components for React, Vue, Angular, React Native, Swift, Android, and Flutter. In this guide, we are focusing on those for web applications. - - -First, install the `@aws-amplify/ui-react` library: - -```bash title="Terminal" showLineNumbers={false} -npm add @aws-amplify/ui-react -``` - -Next, open **pages/\_app.tsx** and add the `Authenticator` component. - -```ts title="pages/_app.tsx" -import type { AppProps } from 'next/app'; -import { Authenticator } from '@aws-amplify/ui-react'; -import { Amplify } from 'aws-amplify'; -import outputs from '@/amplify_outputs.json'; -import '@aws-amplify/ui-react/styles.css'; - -Amplify.configure(outputs); - -export default function App({ Component, pageProps }: AppProps) { - return ( - - {({ signOut, user }) => ( -
    -

    Hello {user?.username}

    - - -
    - )} -
    - ); -}; -``` - - - -#### [Vue 3] - -First, install the `@aws-amplify/ui-vue` library: - -```bash title="Terminal" showLineNumbers={false} -npm add @aws-amplify/ui-vue -``` - -Next, open **src/App.vue** and add the `Authenticator` component. - -**Authenticator** - -The `Authenticator` component offers a simple way to add authentication flows into your app. This component encapsulates an authentication workflow in the framework of your choice and is backed by your backend Auth resources. `Authenticator` passes the `user` info and `signOut` function to the inner template. - -```html - - - -``` - -#### [Vue 2] - -First, install the `@aws-amplify/ui-components` library: - -```bash title="Terminal" showLineNumbers={false} -npm add @aws-amplify/ui-components -``` - -Now open **src/main.ts** and add the following below your last import: - -```js title="src/main.ts" -import '@aws-amplify/ui-components'; -import { - applyPolyfills, - defineCustomElements -} from '@aws-amplify/ui-components/loader'; -import Vue from 'vue'; - -Vue.config.ignoredElements = [/amplify-\w*/]; - -applyPolyfills().then(() => { - defineCustomElements(window); -}); -``` - -Next, open **src/App.ts** and add the `amplify-authenticator` component. - -**amplify-authenticator** - -The `amplify-authenticator` component offers a simple way to add authentication flows into your app. This component encapsulates an authentication workflow in the framework of your choice and is backed by your backend Auth resources. The optional `amplify-sign-out` component is available if you would like to render a sign-out button. - -```html title="src/App.ts" - -``` - - - -First, install the `@aws-amplify/ui-angular` library: - -```bash title="Terminal" showLineNumbers={false} -npm add @aws-amplify/ui-angular -``` - -Now open **app.module.ts** and add the Amplify imports and configuration: - -```js title="app.module.ts" -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular'; - -import { AppComponent } from './app.component'; -import outputs from './amplify_outputs.json'; - -Amplify.configure(outputs); - -@NgModule({ - declarations: [AppComponent], - imports: [BrowserModule, AmplifyAuthenticatorModule], - providers: [], - bootstrap: [AppComponent] -}) -export class AppModule {} -``` - -Next, import the default theme inside **styles.css**: - -```css title="styles.css" -@import '~@aws-amplify/ui-angular/theme.css'; -``` - -Next, open **app.component.html** and add the `amplify-authenticator` component. - -**amplify-authenticator** - -The `Authenticator` component offers a simple way to add authentication flows into your app. This component encapsulates an authentication workflow in the framework of your choice and is backed by your backend Auth resources. `Authenticator` passes the `user` info and `signOut` function to the inner template. - -The `amplify-authenticator` component offers a simple way to add authentication flows into your app. This component encapsulates an authentication workflow in the framework of your choice and is backed by your backend Auth resources. `amplify-authenticator` passes the `user` info and `signOut` function to the inner template. - -```html title="app.component.html" - - -

    Welcome {{ user.username }}!

    - -
    -
    -``` - - -First, install the `@aws-amplify/ui-react-native` library: - -```bash title="Terminal" showLineNumbers={false} -npm add \ - @aws-amplify/react-native \ - @aws-amplify/ui-react-native \ - aws-amplify \ - @react-native-community/netinfo \ - @react-native-async-storage/async-storage \ - react-native-safe-area-context@^4.2.5 \ - react-native-get-random-values -``` - -> **Info:** If your project will support Federated Sign In using the `React Native Authenticator` the `@aws-amplify/rtn-web-browser` package is also required: -> -> ```bash title="Terminal" showLineNumbers={false} -npm add @aws-amplify/rtn-web-browser -``` - -Then install the iOS cocoapods by running: - -```bash title="Terminal" showLineNumbers={false} -npx pod-install -``` - -> **Warning:** For calling native libraries and platform dependencies from Expo, you need to run the prebuild command for generating the folders for related platforms. -> -> ```bash title="Terminal" showLineNumbers={false} -npx expo prebuild -``` -Next, update the `App.tsx` file with the following to set up the authentication flow: - -```typescript -import React from "react"; -import { Button, View, StyleSheet } from "react-native"; -import { Amplify } from "aws-amplify"; -import { Authenticator, useAuthenticator } from "@aws-amplify/ui-react-native"; -import outputs from "./amplify_outputs.json"; - -Amplify.configure(outputs); - -const SignOutButton = () => { - const { signOut } = useAuthenticator(); - - return ( - - - - )} - - ); -} -``` - - -The Authenticator component is automatically configured based on the outputs generated from your backend. To learn more about the Authenticator and how to customize its appearance, visit the [Amplify UI documentation](https://ui.docs.amplify.aws/). - - -Conversely, you can bring your own UI and leverage the library from [`aws-amplify`](https://www.npmjs.com/package/aws-amplify) to handle authentication flows manually. - - ---- - ---- -title: "Sign-up" -section: "build-a-backend/auth/connect-your-frontend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-12-09T19:54:14.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-up/" ---- - -Amplify provides a client library that enables you to interact with backend resources such as Amplify Auth. - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/swift/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/flutter/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/android/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - -To get started, you can use the `signUp()` API to create a new user in your backend: - - -```ts -import { signUp } from "aws-amplify/auth" - -const { isSignUpComplete, userId, nextStep } = await signUp({ - username: "hello@mycompany.com", - password: "hunter2", - options: { - userAttributes: { - email: "hello@mycompany.com", - phone_number: "+15555555555" // E.164 number convention - }, - } -}); -``` - - -```dart -/// Signs a user up with a username, password, and email. The required -/// attributes may be different depending on your app's configuration. -Future signUpUser({ - required String username, - required String password, - required String email, - String? phoneNumber, -}) async { - try { - final userAttributes = { - AuthUserAttributeKey.email: email, - if (phoneNumber != null) AuthUserAttributeKey.phoneNumber: phoneNumber, - // additional attributes as needed - }; - final result = await Amplify.Auth.signUp( - username: username, - password: password, - options: SignUpOptions( - userAttributes: userAttributes, - ), - ); - await _handleSignUpResult(result); - } on AuthException catch (e) { - safePrint('Error signing up user: ${e.message}'); - } -} -``` - -```dart -Future _handleSignUpResult(SignUpResult result) async { - switch (result.nextStep.signUpStep) { - case AuthSignUpStep.confirmSignUp: - final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; - _handleCodeDelivery(codeDeliveryDetails); - break; - case AuthSignUpStep.done: - safePrint('Sign up is complete'); - break; - } -} - -void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { - safePrint( - 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' - 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', - ); -} -``` - - - -#### [Java] - -```java -ArrayList attributes = new ArrayList<>(); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); - -Amplify.Auth.signUp( - "username", - "Password123", - AuthSignUpOptions.builder().userAttributes(attributes).build(), - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val attrs = mapOf( - AuthUserAttributeKey.email() to "my@email.com", - AuthUserAttributeKey.phoneNumber() to "+15551234567" -) -val options = AuthSignUpOptions.builder() - .userAttributes(attrs.map { AuthUserAttribute(it.key, it.value) }) - .build() -Amplify.Auth.signUp("username", "Password123", options, - { Log.i("AuthQuickstart", "Sign up result = $it") }, - { Log.e("AuthQuickstart", "Sign up failed", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val attrs = mapOf( - AuthUserAttributeKey.email() to "my@email.com", - AuthUserAttributeKey.phoneNumber() to "+15551234567" -) -val options = AuthSignUpOptions.builder() - .userAttributes(attrs.map { AuthUserAttribute(it.key, it.value) }) - .build() -try { - val result = Amplify.Auth.signUp("username", "Password123", options) - Log.i("AuthQuickstart", "Sign up OK: $result") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign up failed", error) -} -``` - -#### [RxJava] - -```java -ArrayList attributes = new ArrayList<>(); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); - -RxAmplify.Auth.signUp( - "username", - "Password123", - AuthSignUpOptions.builder().userAttributes(attributes).build()) - .subscribe( - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func signUp(username: String, password: String, email: String, phonenumber: String) async { - let userAttributes = [AuthUserAttribute(.email, value: email), AuthUserAttribute(.phoneNumber, value: phonenumber)] - let options = AuthSignUpRequest.Options(userAttributes: userAttributes) - - do { - let signUpResult = try await Amplify.Auth.signUp( - username: username, - password: password, - options: options - ) - - if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { - print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") - } else { - print("SignUp Complete") - } - } catch let error as AuthError { - print("An error occurred while registering a user \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func signUp(username: String, password: String, email: String, phonenumber: String) -> AnyCancellable { - let userAttributes = [ - AuthUserAttribute(.email, value: email), - AuthUserAttribute(.phoneNumber, value: phonenumber) - ] - let options = AuthSignUpRequest.Options(userAttributes: userAttributes) - Amplify.Publisher.create { - try await Amplify.Auth.signUp( - username: username, - password: password, - options: options - ) - }.sink { - if case let .failure(authError) = $0 { - print("An error occurred while registering a user \(authError)") - } - } - receiveValue: { signUpResult in - if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { - print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") - } else { - print("SignUp Complete") - } - } - return sink -} -``` - - - -The `signUp` API response will include a `nextStep` property, which can be used to determine if further action is required. It may return the following next steps: - - -| Next Step | Description | -| --------- | ----------- | -| `CONFIRM_SIGN_UP` | The sign up needs to be confirmed by collecting a code from the user and calling `confirmSignUp`. | -| `DONE` | The sign up process has been fully completed. | -| `COMPLETE_AUTO_SIGN_IN` | The sign up process needs to complete by invoking the `autoSignIn` API. | - - - -| Next Step | Description | -| --------- | ----------- | -| `CONFIRM_SIGN_UP_STEP` | The sign up needs to be confirmed by collecting a code from the user and calling `confirmSignUp`. | -| `DONE` | The sign up process has been fully completed. | - - - -| Next Step | Description | -| --------- | ----------- | -| `confirmSignUp` | The sign up needs to be confirmed by collecting a code from the user and calling `confirmSignUp`. | -| `done` | The sign up process has been fully completed. | - - -## Confirm sign-up - -By default, each user that signs up remains in the unconfirmed status until they verify with a confirmation code that was sent to their email or phone number. The following are the default verification methods used when either `phone` or `email` are used as `loginWith` options. - -| Login option | User account verification channel | -| ------------------- | --------------------------------- | -| `phone` | Phone Number | -| `email` | Email | -| `email` and `phone` | Email | - -You can confirm the sign-up after receiving a confirmation code from the user: - - -```ts -import { confirmSignUp } from 'aws-amplify/auth'; - -const { isSignUpComplete, nextStep } = await confirmSignUp({ - username: "hello@mycompany.com", - confirmationCode: "123456" -}); -``` - - -```dart -Future confirmUser({ - required String username, - required String confirmationCode, -}) async { - try { - final result = await Amplify.Auth.confirmSignUp( - username: username, - confirmationCode: confirmationCode, - ); - // Check if further confirmations are needed or if - // the sign up is complete. - await _handleSignUpResult(result); - } on AuthException catch (e) { - safePrint('Error confirming user: ${e.message}'); - } -} -``` - - - -#### [Java] - -```java -Amplify.Auth.confirmSignUp( - "username", - "the code you received via email", - result -> Log.i("AuthQuickstart", result.isSignUpComplete() ? "Confirm signUp succeeded" : "Confirm sign up not complete"), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.confirmSignUp( - "username", "the code you received via email", - { result -> - if (result.isSignUpComplete) { - Log.i("AuthQuickstart", "Confirm signUp succeeded") - } else { - Log.i("AuthQuickstart","Confirm sign up not complete") - } - }, - { Log.e("AuthQuickstart", "Failed to confirm sign up", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val code = "code you received via email" - val result = Amplify.Auth.confirmSignUp("username", code) - if (result.isSignUpComplete) { - Log.i("AuthQuickstart", "Signup confirmed") - } else { - Log.i("AuthQuickstart", "Signup confirmation not yet complete") - } -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Failed to confirm signup", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.confirmSignUp("username", "the code you received via email") - .subscribe( - result -> Log.i("AuthQuickstart", result.isSignUpComplete() ? "Confirm signUp succeeded" : "Confirm sign up not complete"), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func confirmSignUp(for username: String, with confirmationCode: String) async { - do { - let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( - for: username, - confirmationCode: confirmationCode - ) - print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") - } catch let error as AuthError { - print("An error occurred while confirming sign up \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignUp( - for: username, - confirmationCode: confirmationCode - ) - }.sink { - if case let .failure(authError) = $0 { - print("An error occurred while confirming sign up \(authError)") - } - } - receiveValue: { _ in - print("Confirm signUp succeeded") - } -} -``` - - - - -> **Info:** **Note:** When specifying `email` or `phone` as a way for your users to sign-in, these are attributes that are used in place of the username. Visit the [concepts page to learn more about usernames](/[platform]/build-a-backend/auth/concepts/). - - - -## Practical Example - - -```tsx title="src/App.tsx" -import type { FormEvent } from "react" -import { Amplify } from "aws-amplify" -// highlight-next-line -import { signUp } from "aws-amplify/auth" -import outputs from "../amplify_outputs.json" - -Amplify.configure(outputs) - -interface SignUpFormElements extends HTMLFormControlsCollection { - email: HTMLInputElement - password: HTMLInputElement -} - -interface SignUpForm extends HTMLFormElement { - readonly elements: SignUpFormElements -} - -export default function App() { - async function handleSubmit(event: FormEvent) { - event.preventDefault() - const form = event.currentTarget - // ... validate inputs - await signUp({ - username: form.elements.email.value, - password: form.elements.password.value, - }) - } - - return ( -
    - - - - - -
    - ) -} -``` - - - - -## Sign up with passwordless methods - -Your application's users can also sign up using passwordless methods. To learn more, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/). - -### SMS OTP - - -```typescript -// Sign up using a phone number -const { nextStep: signUpNextStep } = await signUp({ - username: 'hello', - options: { - userAttributes: { - phone_number: '+15555551234', - }, - }, -}); - -if (signUpNextStep.signUpStep === 'DONE') { - console.log(`SignUp Complete`); -} - -if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { - console.log( - `Code Delivery Medium: ${signUpNextStep.codeDeliveryDetails.deliveryMedium}`, - ); - console.log( - `Code Delivery Destination: ${signUpNextStep.codeDeliveryDetails.destination}`, - ); -} - -// Confirm sign up with the OTP received -const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ - username: 'hello', - confirmationCode: '123456', -}); - -if (confirmSignUpNextStep.signUpStep === 'DONE') { - console.log(`SignUp Complete`); -} -``` - - - -#### [Java] - -```java -// Sign up using a phone number -ArrayList attributes = new ArrayList<>(); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); - -Amplify.Auth.signUp( - "hello@example.com", - null, - AuthSignUpOptions.builder().userAttributes(attributes).build(), - result -> { - if (result.isSignUpComplete()) { - Log.i("AuthQuickstart", "Sign up is complete"); - } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { - Log.i("AuthQuickstart", "Code Deliver Medium: " + - result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); - Log.i("AuthQuickstart", "Code Deliver Destination: " + - result.getNextStep().getCodeDeliveryDetails().getDestination()); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -); - -// Confirm sign up with the OTP received -Amplify.Auth.confirmSignUp( - "hello@example.com", - "123456", - result -> { - if (result.isSignUpComplete()) { - Log.i("AuthQuickstart", "Sign up is complete"); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -// Sign up using a phone number -val attributes = listOf( - AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15555551234") -) -val options = - AuthSignUpOptions - .builder() - .userAttributes(attributes) - .build() - -Amplify.Auth.signUp( - "hello@example.com", - null, - options, - { result -> - if (result.isSignUpComplete) { - Log.i("AuthQuickstart", "Sign up is complete") - } else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { - Log.i("AuthQuickstart", "Code Deliver Medium: " + - "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") - Log.i("AuthQuickstart", "Code Deliver Destination: " + - "${result.nextStep.codeDeliveryDetails?.destination}") - } - }, - { Log.e("AuthQuickstart", "Failed to sign up", it) } -) - -// Confirm sign up with the OTP received -Amplify.Auth.confirmSignUp( - "hello@example.com", - "123456", - { result -> - if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { - Log.i("AuthQuickstart", "Sign up is complete") - } - }, - { Log.e("AuthQuickstart", "Failed to sign up", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -// Sign up using a phone number -val attributes = listOf( - AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15555551234") -) -val options = - AuthSignUpOptions - .builder() - .userAttributes(attributes) - .build() -var result = Amplify.Auth.signUp("hello@example.com", null, options) - -if (result.isSignUpComplete) { - Log.i("AuthQuickstart", "Sign up is complete") -} else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { - Log.i("AuthQuickstart", "Code Deliver Medium: " + - "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") - Log.i("AuthQuickstart", "Code Deliver Destination: " + - "${result.nextStep.codeDeliveryDetails?.destination}") -} - -// Confirm sign up with the OTP received -result = Amplify.Auth.confirmSignUp( - "hello@example.com", - "123456" -) - -if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { - Log.i("AuthQuickstart", "Sign up is complete") -} -``` - -#### [RxJava] - -```java -// Sign up using a phone number -ArrayList attributes = new ArrayList<>(); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); - -RxAmplify.Auth.signUp( - "hello@example.com", - null, - AuthSignUpOptions.builder().userAttributes(attributes).build() -) - .subscribe( - result -> { - if (result.isSignUpComplete()) { - Log.i("AuthQuickstart", "Sign up is complete"); - } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { - Log.i("AuthQuickstart", "Code Deliver Medium: " + - result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); - Log.i("AuthQuickstart", "Code Deliver Destination: " + - result.getNextStep().getCodeDeliveryDetails().getDestination()); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); - -// Confirm sign up with the OTP received -RxAmplify.Auth.confirmSignUp( - "hello@example.com", - "123456" -) - .subscribe( - result -> { - if (result.isSignUpComplete()) { - Log.i("AuthQuickstart", "Sign up is complete"); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -// Sign up using an phone number -func signUp(username: String, phonenumber: String) async { - let userAttributes = [ - AuthUserAttribute(.phoneNumber, value: phonenumber) - ] - let options = AuthSignUpRequest.Options(userAttributes: userAttributes) - do { - let signUpResult = try await Amplify.Auth.signUp( - username: username, - options: options - ) - if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { - print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") - } else { - print("SignUp Complete") - } - } catch let error as AuthError { - print("An error occurred while registering a user \(error)") - } catch { - print("Unexpected error: \(error)") - } -} - -// Confirm sign up with the OTP received -func confirmSignUp(for username: String, with confirmationCode: String) async { - do { - let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( - for: username, - confirmationCode: confirmationCode - ) - print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") - } catch let error as AuthError { - print("An error occurred while confirming sign up \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -// Sign up using a phone number -func signUp(username: String, phonenumber: String) -> AnyCancellable { - let userAttributes = [ - AuthUserAttribute(.phoneNumber, value: phonenumber) - ] - let options = AuthSignUpRequest.Options(userAttributes: userAttributes) - let sink = Amplify.Publisher.create { - try await Amplify.Auth.signUp( - username: username, - options: options - ) - }.sink { - if case let .failure(authError) = $0 { - print("An error occurred while registering a user \(authError)") - } - } - receiveValue: { signUpResult in - if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { - print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") - } else { - print("SignUp Complete") - } - } - return sink -} - -// Confirm sign up with the OTP received -func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignUp( - for: username, - confirmationCode: confirmationCode - ) - }.sink { - if case let .failure(authError) = $0 { - print("An error occurred while confirming sign up \(authError)") - } - } - receiveValue: { _ in - print("Confirm signUp succeeded") - } -} -``` - - - -### Email OTP - - -```typescript -// Sign up using an email address -const { nextStep: signUpNextStep } = await signUp({ - username: 'hello', - options: { - userAttributes: { - email: 'hello@example.com', - }, - }, -}); - -if (signUpNextStep.signUpStep === 'DONE') { - console.log(`SignUp Complete`); -} - -if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { - console.log( - `Code Delivery Medium: ${signUpNextStep.codeDeliveryDetails.deliveryMedium}`, - ); - console.log( - `Code Delivery Destination: ${signUpNextStep.codeDeliveryDetails.destination}`, - ); -} - -// Confirm sign up with the OTP received -const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ - username: 'hello', - confirmationCode: '123456', -}); - -if (confirmSignUpNextStep.signUpStep === 'DONE') { - console.log(`SignUp Complete`); -} -``` - - - -#### [Java] - -```java -// Sign up using an email address -ArrayList attributes = new ArrayList<>(); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "hello@example.com")); - -Amplify.Auth.signUp( - "hello@example.com", - null, - AuthSignUpOptions.builder().userAttributes(attributes).build(), - result -> { - if (result.isSignUpComplete()) { - Log.i("AuthQuickstart", "Sign up is complete"); - } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { - Log.i("AuthQuickstart", "Code Deliver Medium: " + - result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); - Log.i("AuthQuickstart", "Code Deliver Destination: " + - result.getNextStep().getCodeDeliveryDetails().getDestination()); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -); - -// Confirm sign up with the OTP received -Amplify.Auth.confirmSignUp( - "hello@example.com", - "123456", - result -> { - if (result.isSignUpComplete()) { - Log.i("AuthQuickstart", "Sign up is complete"); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -// Sign up using an email address -val attributes = listOf( - AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com") -) -val options = - AuthSignUpOptions - .builder() - .userAttributes(attributes) - .build() - -Amplify.Auth.signUp( - "hello@example.com", - null, - options, - { result -> - if (result.isSignUpComplete) { - Log.i("AuthQuickstart", "Sign up is complete") - } else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { - Log.i("AuthQuickstart", "Code Deliver Medium: " + - "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") - Log.i("AuthQuickstart", "Code Deliver Destination: " + - "${result.nextStep.codeDeliveryDetails?.destination}") - } - }, - { Log.e("AuthQuickstart", "Failed to sign up", it) } -) - -// Confirm sign up with the OTP received -Amplify.Auth.confirmSignUp( - "hello@example.com", - "123456", - { result -> - if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { - Log.i("AuthQuickstart", "Sign up is complete") - } - }, - { Log.e("AuthQuickstart", "Failed to sign up", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -// Sign up using an email address -val attributes = listOf( - AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com") -) -val options = - AuthSignUpOptions - .builder() - .userAttributes(attributes) - .build() -var result = Amplify.Auth.signUp("hello@example.com", null, options) - -if (result.isSignUpComplete) { - Log.i("AuthQuickstart", "Sign up is complete") -} else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { - Log.i("AuthQuickstart", "Code Deliver Medium: " + - "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") - Log.i("AuthQuickstart", "Code Deliver Destination: " + - "${result.nextStep.codeDeliveryDetails?.destination}") -} - -// Confirm sign up with the OTP received -result = Amplify.Auth.confirmSignUp( - "hello@example.com", - "123456" -) - -if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { - Log.i("AuthQuickstart", "Sign up is complete") -} -``` - -#### [RxJava] - -```java -// Sign up using an email address -ArrayList attributes = new ArrayList<>(); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); - -RxAmplify.Auth.signUp( - "hello@example.com", - null, - AuthSignUpOptions.builder().userAttributes(attributes).build() -) - .subscribe( - result -> { - if (result.isSignUpComplete()) { - Log.i("AuthQuickstart", "Sign up is complete"); - } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { - Log.i("AuthQuickstart", "Code Deliver Medium: " + - result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); - Log.i("AuthQuickstart", "Code Deliver Destination: " + - result.getNextStep().getCodeDeliveryDetails().getDestination()); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); - -// Confirm sign up with the OTP received -RxAmplify.Auth.confirmSignUp( - "hello@example.com", - "123456" -) - .subscribe( - result -> { - if (result.isSignUpComplete()) { - Log.i("AuthQuickstart", "Sign up is complete"); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -// Sign up using an email -func signUp(username: String, email: String) async { - let userAttributes = [ - AuthUserAttribute(.email, value: email) - ] - let options = AuthSignUpRequest.Options(userAttributes: userAttributes) - do { - let signUpResult = try await Amplify.Auth.signUp( - username: username, - options: options - ) - if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { - print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") - } else { - print("SignUp Complete") - } - } catch let error as AuthError { - print("An error occurred while registering a user \(error)") - } catch { - print("Unexpected error: \(error)") - } -} - -// Confirm sign up with the OTP received -func confirmSignUp(for username: String, with confirmationCode: String) async { - do { - let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( - for: username, - confirmationCode: confirmationCode - ) - print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") - } catch let error as AuthError { - print("An error occurred while confirming sign up \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -// Sign up using an email -func signUp(username: String, email: String) -> AnyCancellable { - let userAttributes = [ - AuthUserAttribute(.email, value: email) - ] - let options = AuthSignUpRequest.Options(userAttributes: userAttributes) - let sink = Amplify.Publisher.create { - try await Amplify.Auth.signUp( - username: username, - options: options - ) - }.sink { - if case let .failure(authError) = $0 { - print("An error occurred while registering a user \(authError)") - } - } - receiveValue: { signUpResult in - if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { - print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") - } else { - print("SignUp Complete") - } - } - return sink -} - -// Confirm sign up with the OTP received -func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignUp( - for: username, - confirmationCode: confirmationCode - ) - }.sink { - if case let .failure(authError) = $0 { - print("An error occurred while confirming sign up \(authError)") - } - } - receiveValue: { _ in - print("Confirm signUp succeeded") - } -} -``` - - - -### Auto Sign In - - -```typescript -// Call `signUp` API with `USER_AUTH` as the authentication flow type for `autoSignIn` -const { nextStep: signUpNextStep } = await signUp({ - username: 'hello', - options: { - userAttributes: { - email: 'hello@example.com', - phone_number: '+15555551234', - }, - autoSignIn: { - authFlowType: 'USER_AUTH', - }, - }, -}); - -if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { - console.log( - `Code Delivery Medium: ${signUpNextStep.codeDeliveryDetails.deliveryMedium}`, - ); - console.log( - `Code Delivery Destination: ${signUpNextStep.codeDeliveryDetails.destination}`, - ); -} - -// Call `confirmSignUp` API with the OTP received -const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ - username: 'hello', - confirmationCode: '123456', -}); - -if (confirmSignUpNextStep.signUpStep === 'COMPLETE_AUTO_SIGN_IN') { - // Call `autoSignIn` API to complete the flow - const { nextStep } = await autoSignIn(); - - if (nextStep.signInStep === 'DONE') { - console.log('Successfully signed in.'); - } -} - -``` - - - -#### [Java] - -```java -private void confirmSignUp(String username, String confirmationCode) { - // Confirm sign up with the OTP received then auto sign in - Amplify.Auth.confirmSignUp( - username, - confirmationCode, - result -> { - if (result.getNextStep().getSignUpStep() == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { - Log.i("AuthQuickstart", "Sign up is complete, auto sign in"); - autoSignIn(); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); -} - -private void autoSignIn() { - Amplify.Auth.autoSignIn( - result -> Log.i("AuthQuickstart", "Sign in is complete"), - error -> Log.e("AuthQuickstart", error.toString()) - ); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -fun confirmSignUp(username: String, confirmationCode: String) { - // Confirm sign up with the OTP received - Amplify.Auth.confirmSignUp( - username, - confirmationCode, - { signUpResult -> - if (signUpResult.nextStep.signUpStep == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { - Log.i("AuthQuickstart", "Sign up is complete, auto sign in") - autoSignIn() - } - }, - { Log.e("AuthQuickstart", "Failed to sign up", it) } - ) -} -fun autoSignIn() { - Amplify.Auth.autoSignIn( - { signInResult -> - Log.i("AuthQuickstart", "Sign in is complete") - }, - { Log.e("AuthQuickstart", "Failed to sign in", it) } - ) -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -suspend fun confirmSignUp(username: String, confirmationCode: String) { - // Confirm sign up with the OTP received then auto sign in - val result = Amplify.Auth.confirmSignUp( - "hello@example.com", - "123456" - ) - - if (result.nextStep.signUpStep == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { - Log.i("AuthQuickstart", "Sign up is complete, auto sign in") - autoSignIn() - } -} - -suspend fun autoSignIn() { - val result = Amplify.Auth.autoSignIn() - if (result.isSignedIn) { - Log.i("AuthQuickstart", "Sign in is complete") - } else { - Log.e("AuthQuickstart", "Sign in did not complete $result") - } -} -``` - -#### [RxJava] - -```java -private void confirmSignUp(String username, String confirmationCode) { - // Confirm sign up with the OTP received then auto sign in - RxAmplify.Auth.confirmSignUp( - username, - confirmationCode - ) - .subscribe( - result -> { - if (result.getNextStep().getSignUpStep() == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { - Log.i("AuthQuickstart", "Sign up is complete, auto sign in"); - autoSignIn(); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); -} - -private void autoSignIn() { - RxAmplify.Auth.autoSignIn() - .subscribe( - result -> Log.i("AuthQuickstart", "Sign in is complete" + result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) - ); -} -``` - - - - -#### [Async/Await] - -```swift -// Confirm sign up with the OTP received and auto sign in -func confirmSignUp(for username: String, with confirmationCode: String) async { - do { - let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( - for: username, - confirmationCode: confirmationCode - ) - if case .completeAutoSignIn(let session) = confirmSignUpResult.nextStep { - let autoSignInResult = try await Amplify.Auth.autoSignIn() - print("Auto sign in result: \(autoSignInResult.isSignedIn)") - } else { - print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") - } - } catch let error as AuthError { - print("An error occurred while confirming sign up \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -// Confirm sign up with the OTP received and auto sign in -func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignUp( - for: username, - confirmationCode: confirmationCode - ) - }.sink { - if case let .failure(authError) = $0 { - print("An error occurred while confirming sign up \(authError)") - } - } - receiveValue: { confirmSignUpResult in - if case let .completeAutoSignIn(session) = confirmSignUpResult.nextStep { - print("Confirm Sign Up succeeded. Next step is auto sign in") - // call `autoSignIn()` API to complete sign in - } else { - print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") - } - } -} - -func autoSignIn() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.autoSignIn() - }.sink { - if case let .failure(authError) = $0 { - print("Auto Sign in failed \(authError)") - } - } - receiveValue: { autoSignInResult in - if autoSignInResult.isSignedIn { - print("Auto Sign in succeeded") - } - } -} -``` - - - - ---- - ---- -title: "Sign-in" -section: "build-a-backend/auth/connect-your-frontend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-06-24T13:29:28.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-in/" ---- - -Amplify provides a client library that enables you to interact with backend resources such as Amplify Auth. - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/swift/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/flutter/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/android/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - -## Using the signIn API - - -```ts -import { signIn } from 'aws-amplify/auth' - -await signIn({ - username: "hello@mycompany.com", - password: "hunter2", -}) -``` - - -```dart -Future signInUser(String username, String password) async { - try { - final result = await Amplify.Auth.signIn( - username: username, - password: password, - ); - await _handleSignInResult(result); - } on AuthException catch (e) { - safePrint('Error signing in: ${e.message}'); - } -} -``` - -Depending on your configuration and how the user signed up, one or more confirmations will be necessary. Use the `SignInResult` returned from `Amplify.Auth.signIn` to check the next step for signing in. When the value is `done`, the user has successfully signed in. - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - case AuthSignInStep.confirmSignInWithSmsMfaCode: - final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; - _handleCodeDelivery(codeDeliveryDetails); - break; - case AuthSignInStep.confirmSignInWithNewPassword: - safePrint('Enter a new password to continue signing in'); - break; - case AuthSignInStep.confirmSignInWithCustomChallenge: - final parameters = result.nextStep.additionalInfo; - final prompt = parameters['prompt']!; - safePrint(prompt); - break; - case AuthSignInStep.resetPassword: - final resetResult = await Amplify.Auth.resetPassword( - username: username, - ); - await _handleResetPasswordResult(resetResult); - break; - case AuthSignInStep.confirmSignUp: - // Resend the sign up code to the registered device. - final resendResult = await Amplify.Auth.resendSignUpCode( - username: username, - ); - _handleCodeDelivery(resendResult.codeDeliveryDetails); - break; - case AuthSignInStep.done: - safePrint('Sign in is complete'); - break; - } -} - -void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { - safePrint( - 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' - 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', - ); -} -``` - - - -#### [Java] - -```java -Amplify.Auth.signIn( - "username", - "password", - result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.signIn("username", "password", - { result -> - if (result.isSignedIn) { - Log.i("AuthQuickstart", "Sign in succeeded") - } else { - Log.i("AuthQuickstart", "Sign in not complete") - } - }, - { Log.e("AuthQuickstart", "Failed to sign in", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.signIn("username", "password") - if (result.isSignedIn) { - Log.i("AuthQuickstart", "Sign in succeeded") - } else { - Log.e("AuthQuickstart", "Sign in not complete") - } -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign in failed", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.signIn("username", "password") - .subscribe( - result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func signIn(username: String, password: String) async { - do { - let signInResult = try await Amplify.Auth.signIn( - username: username, - password: password - ) - if signInResult.isSignedIn { - print("Sign in succeeded") - } - } catch let error as AuthError { - print("Sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func signIn(username: String, password: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.signIn( - username: username, - password: password - ) - }.sink { - if case let .failure(authError) = $0 { - print("Sign in failed \(authError)") - } - } - receiveValue: { signInResult in - if signInResult.isSignedIn { - print("Sign in succeeded") - } - } -} -``` - - - -The `signIn` API response will include a `nextStep` property, which can be used to determine if further action is required. It may return the following next steps: - - -| Next Step | Description | -| --------- | ----------- | -| `CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED` | The user was created with a temporary password and must set a new one. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE` | The sign-in must be confirmed with a custom challenge response. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_SMS_CODE` | The sign-in must be confirmed with an SMS code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` | The sign-in must be confirmed with an EMAIL code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `"EMAIL"` or `"TOTP"` to `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_EMAIL_SETUP` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | -| `RESET_PASSWORD` | The user must reset their password via `resetPassword`. | -| `CONFIRM_SIGN_UP` | The user hasn't completed the sign-up flow fully and must be confirmed via `confirmSignUp`. | -| `DONE` | The sign in process has been completed. | - - - -| Next Step | Description | -| --------- | ----------- | -| `CONFIRM_SIGN_IN_WITH_NEW_PASSWORD` | The user was created with a temporary password and must set a new one. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE` | The sign-in must be confirmed with a custom challenge response. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_OTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.EMAIL.challengeResponse` or `MFAType.TOTP.challengeResponse` to `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | -| `RESET_PASSWORD` | The user must reset their password via `resetPassword`. | -| `CONFIRM_SIGN_UP` | The user hasn't completed the sign-up flow fully and must be confirmed via `confirmSignUp`. | -| `DONE` | The sign in process has been completed. | - - - -| Next Step | Description | -| --------- | ----------- | -| `confirmSignInWithNewPassword` | The user was created with a temporary password and must set a new one. Complete the process with `confirmSignIn`. | -| `confirmSignInWithCustomChallenge` | The sign-in must be confirmed with a custom challenge response. Complete the process with `confirmSignIn`. | -| `confirmSignInWithTOTPCode` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | -| `confirmSignInWithSMSMFACode` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | -| `confirmSignInWithOTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | -| `confirmSignInWithPassword` | The user must set a new password. Complete the process with `confirmSignIn`. | -| `continueSignInWithFirstFactorSelection` | The user must select their preferred mode of First Factor authentication. Complete the process with `confirmSignIn`. | -| `continueSignInWithMFASelection` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | -| `continueSignInWithMFASetupSelection` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.email.challengeResponse` or `MFAType.totp.challengeResponse ` to `confirmSignIn`. | -| `continueSignInWithTOTPSetup` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | -| `continueSignInWithEmailMFASetup` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | -| `resetPassword` | The user must reset their password via `resetPassword`. | -| `confirmSignUp` | The user hasn't completed the sign-up flow fully and must be confirmed via `confirmSignUp`. | -| `done` | The sign in process has been completed. | - - - -| Next Step | Description | -| --------- | ----------- | -| `confirmSignInWithNewPassword` | The user was created with a temporary password and must set a new one. Complete the process with `confirmSignIn`. | -| `confirmSignInWithCustomChallenge` | The sign-in must be confirmed with a custom challenge response. Complete the process with `confirmSignIn`. | -| `confirmSignInWithTotpMfaCode` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | -| `confirmSignInWithSmsMfaCode` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | -| `confirmSignInWithOtpCode` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | -| `continueSignInWithMfaSelection` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | -| `continueSignInWithMfaSetupSelection` | The user must select their mode of MFA verification to setup. Complete the process by passing either `"EMAIL"` or `"TOTP"` to `confirmSignIn`. | -| `continueSignInWithTotpSetup` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | -| `continueSignInWithEmailMfaSetup` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | -| `resetPassword` | The user must reset their password via `resetPassword`. | -| `confirmSignUp` | The user hasn't completed the sign-up flow fully and must be confirmed via `confirmSignUp`. | -| `done` | The sign in process has been completed. | - - -For more information on handling the MFA steps that may be returned, see [multi-factor authentication](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/). - - - - - - -#### [Async/Await] - -```swift -func confirmSignIn() async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: "") - print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") - } catch let error as AuthError { - print("Confirm sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmSignIn() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn(challengeResponse: "") - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") - } -} -``` - - - - - - - -### Practical Example - - -```tsx title="src/App.tsx" -import type { FormEvent } from "react" -import { Amplify } from "aws-amplify" -// highlight-next-line -import { signIn } from "aws-amplify/auth" -import outputs from "../amplify_outputs.json" - -Amplify.configure(outputs) - -interface SignInFormElements extends HTMLFormControlsCollection { - email: HTMLInputElement - password: HTMLInputElement -} - -interface SignInForm extends HTMLFormElement { - readonly elements: SignInFormElements -} - -export default function App() { - async function handleSubmit(event: FormEvent) { - event.preventDefault() - const form = event.currentTarget - // ... validate inputs - await signIn({ - username: form.elements.email.value, - password: form.elements.password.value, - }) - } - - return ( -
    - - - - - -
    - ) -} -``` - - - -## With multi-factor auth enabled - -When you have Email or SMS MFA enabled, Cognito will send messages to your users on your behalf. Email and SMS messages require that your users have email address and phone number attributes respectively. It is recommended to set these attributes as required in your user pool if you wish to use either Email MFA or SMS MFA. When these attributes are required, a user must provide these details before they can complete the sign up process. - -If you have set MFA to be required and you have activated more than one authentication factor, Cognito will prompt new users to select an MFA factor they want to use. Users must have a phone number to select SMS and an email address to select email MFA. - -If a user doesn't have the necessary attributes defined for any available message based MFA, Cognito will prompt them to set up TOTP. - -Visit the [multi-factor authentication documentation](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/) to learn more about enabling MFA on your backend auth resource. - - - -#### [Java] - -```java -ArrayList attributes = new ArrayList<>(); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); - -Amplify.Auth.signUp( - "username", - "Password123", - AuthSignUpOptions.builder().userAttributes(attributes).build(), - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val attrs = mapOf( - AuthUserAttributeKey.email() to "my@email.com", - AuthUserAttributeKey.phoneNumber() to "+15551234567" -) -val options = AuthSignUpOptions.builder() - .userAttributes(attrs.map { AuthUserAttribute(it.key, it.value) }) - .build() -Amplify.Auth.signUp("username", "Password123", options, - { Log.i("AuthQuickstart", "Sign up result = $it") }, - { Log.e("AuthQuickstart", "Sign up failed", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val attrs = mapOf( - AuthUserAttributeKey.email() to "my@email.com", - AuthUserAttributeKey.phoneNumber() to "+15551234567" -) -val options = AuthSignUpOptions.builder() - .userAttributes(attrs.map { AuthUserAttribute(it.key, it.value) }) - .build() -try { - val result = Amplify.Auth.signUp("username", "Password123", options) - Log.i("AuthQuickstart", "Sign up OK: $result") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign up failed", error) -} -``` - -#### [RxJava] - -```java -ArrayList attributes = new ArrayList<>(); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); -attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); - -RxAmplify.Auth.signUp( - "username", - "Password123", - AuthSignUpOptions.builder().userAttributes(attributes).build()) - .subscribe( - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func signUp(username: String, password: String, email: String, phonenumber: String) async { - let userAttributes = [AuthUserAttribute(.email, value: email), AuthUserAttribute(.phoneNumber, value: phonenumber)] - let options = AuthSignUpRequest.Options(userAttributes: userAttributes) - - do { - let signUpResult = try await Amplify.Auth.signUp( - username: username, - password: password, - options: options - ) - - if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { - print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") - } else { - print("SignUp Complete") - } - } catch let error as AuthError { - print("An error occurred while registering a user \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func signUp(username: String, password: String, email: String, phonenumber: String) -> AnyCancellable { - let userAttributes = [ - AuthUserAttribute(.email, value: email), - AuthUserAttribute(.phoneNumber, value: phonenumber) - ] - let options = AuthSignUpRequest.Options(userAttributes: userAttributes) - Amplify.Publisher.create { - try await Amplify.Auth.signUp( - username: username, - password: password, - options: options - ) - }.sink { - if case let .failure(authError) = $0 { - print("An error occurred while registering a user \(authError)") - } - } - receiveValue: { signUpResult in - if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { - print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") - } else { - print("SignUp Complete") - } - } - return sink -} -``` - - - -### Confirm sign-in - - -Following sign in, you will receive a `nextStep` in the sign-in result of one of the following types. Collect the user response and then pass to the `confirmSignIn` API to complete the sign in flow. -| Next Step | Description | -| --------- | ----------- | -| `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_SMS_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` | The sign-in must be confirmed with a EMAIL code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `"EMAIL"` or `"TOTP"` to `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_EMAIL_SETUP` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | - - - -Following sign in, you will receive a `nextStep` in the sign-in result of one of the following types. Collect the user response and then pass to the `confirmSignIn` API to complete the sign in flow. -| Next Step | Description | -| --------- | ----------- | -| `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_OTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.EMAIL.challengeResponse` or `MFAType.TOTP.challengeResponse` to `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | - - - -Following sign in, you will receive a `nextStep` in the sign-in result of one of the following types. Collect the user response and then pass to the `confirmSignIn` API to complete the sign in flow. -| Next Step | Description | -| --------- | ----------- | -| `confirmSignInWithTOTPCode` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | -| `confirmSignInWithSMSMFACode` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | -| `confirmSignInWithOTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | -| `confirmSignInWithPassword` | The user must set a new password. Complete the process with `confirmSignIn`. | -| `continueSignInWithFirstFactorSelection` | The user must select their preferred mode of First Factor authentication. Complete the process with `confirmSignIn`. | -| `continueSignInWithMFASelection` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | -| `continueSignInWithMFASetupSelection` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.email.challengeResponse` or `MFAType.totp.challengeResponse ` to `confirmSignIn`. | -| `continueSignInWithTOTPSetup` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | -| `continueSignInWithEmailMFASetup` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | - - - -Following sign in, you will receive a `nextStep` in the sign-in result of one of the following types. Collect the user response and then pass to the `confirmSignIn` API to complete the sign in flow. -| Next Step | Description | -| --------- | ----------- | -| `confirmSignInWithTotpMfaCode` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | -| `confirmSignInWithSmsMfaCode` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | -| `confirmSignInWithOtpCode` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | -| `continueSignInWithMfaSelection` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | -| `continueSignInWithMfaSetupSelection` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MfaType.email.confirmationValue` or `MfaType.totp.confirmationValue` to `confirmSignIn`. | -| `continueSignInWithTotpSetup` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | -| `continueSignInWithEmailMfaSetup` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `confirmSignIn`. | - - - -> **Info:** **Note:** you must call `confirmSignIn` in the same app session as you call `signIn`. If you close the app, you will need to call `signIn` again. As a result, for testing purposes, you'll at least need an input field where you can enter the code sent via SMS and pass it to `confirmSignIn`. - - - -```ts title="src/main.ts" -import { confirmSignIn, signIn } from "aws-amplify/auth"; - -const { nextStep } = await signIn({ - username: "hello@mycompany.com", - password: "hunter2", -}); - -if ( - nextStep.signInStep === "CONFIRM_SIGN_IN_WITH_SMS_CODE" || - nextStep.signInStep === "CONFIRM_SIGN_IN_WITH_EMAIL_CODE" || - nextStep.signInStep === "CONFIRM_SIGN_IN_WITH_TOTP_CODE" -) { - // collect OTP from user - await confirmSignIn({ - challengeResponse: "123456", - }); -} - -if (nextStep.signInStep === "CONTINUE_SIGN_IN_WITH_MFA_SELECTION") { - // present nextStep.allowedMFATypes to user - // collect user selection - await confirmSignIn({ - challengeResponse: "EMAIL", // 'EMAIL', 'SMS', or 'TOTP' - }); -} - -if (nextStep.signInStep === "CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION") { - // present nextStep.allowedMFATypes to user - // collect user selection - await confirmSignIn({ - challengeResponse: "EMAIL", // 'EMAIL' or 'TOTP' - }); -} - -if (nextStep.signInStep === "CONTINUE_SIGN_IN_WITH_EMAIL_SETUP") { - // collect email address from user - await confirmSignIn({ - challengeResponse: "hello@mycompany.com", - }); -} - -if (nextStep.signInStep === "CONTINUE_SIGN_IN_WITH_TOTP_SETUP") { - // present nextStep.totpSetupDetails.getSetupUri() to user - // collect OTP from user - await confirmSignIn({ - challengeResponse: "123456", - }); -} - -``` -> **Info:** **Note:** The Amplify authentication flow will persist relevant session data throughout the lifespan of a page session. This enables the `confirmSignIn` API to be leveraged even after a full page refresh in a multi-page application, such as when redirecting from a login page to a sign in confirmation page. - - - - -#### [Java] - -```java -Amplify.Auth.confirmSignIn( - "confirmation code received via SMS", - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.confirmSignIn("code received via SMS", - { Log.i("AuthQuickstart", "Confirmed signin: $it") }, - { Log.e("AuthQuickstart", "Failed to confirm signin", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.confirmSignIn("code received via SMS") - Log.i("AuthQuickstart", "Confirmed signin: $result") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Failed to confirm signin", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.confirmSignIn("confirmation code received via SMS") - .subscribe( - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func confirmSignIn() async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: "") - print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") - } catch let error as AuthError { - print("Confirm sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmSignIn() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn(challengeResponse: "") - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") - } -} -``` - - - -## Sign in with an external identity provider - - -To sign in using an external identity provider such as Google, use the `signInWithRedirect` function. - -> **Info:** For guidance on configuring an external Identity Provider with Amplify see [External Identity Providers](/[platform]/build-a-backend/auth/concepts/external-identity-providers/) - -```ts -import { signInWithRedirect } from "aws-amplify/auth" - -signInWithRedirect({ provider: "Google" }) -``` - -> **Info:** **Note:** if you do not pass an argument to `signInWithRedirect` it will redirect your users to the [Cognito Hosted UI](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-integration.html), which has limited support for customization. - -Alternatively if you have configured OIDC or SAML-based identity providers in your auth resource, you can specify a "custom" provider in `signInWithRedirect`: - -```ts -import { signInWithRedirect } from "aws-amplify/auth" - -signInWithRedirect({ provider: { - custom: "MyOidcProvider" -}}) -``` - -## Auto sign-in - -The `autoSignIn` API will automatically sign-in a user when it was previously enabled by the `signUp` API and after any of the following cases has completed: - -- User confirmed their account with a verification code sent to their phone or email (default option). -- User confirmed their account with a verification link sent to their phone or email. In order to enable this option you need to go to the [Amazon Cognito console](https://aws.amazon.com/pm/cognito), look for your userpool, then go to the `Messaging` tab and enable `link` mode inside the `Verification message` option. Finally you need to define the `signUpVerificationMethod` to `link` inside the `Cognito` option of your `Auth` config. - -```ts title="src/main.ts" -import { autoSignIn } from 'aws-amplify/auth'; - -await autoSignIn(); -``` - -**Note**: When MFA is enabled, your users may be presented with multiple consecutive steps that require them to enter an OTP to proceed with the sign up and subsequent sign in flow. This requirement is not present when using the `USER_AUTH` flow. - - - - -### Install native module - -`signInWithRedirect` displays the sign-in UI inside a platform-dependent webview. On iOS devices, an [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) will be launched and, on Android, a [Custom Tab](https://developer.chrome.com/docs/android/custom-tabs/). After the sign-in process is complete, the sign-in UI will redirect back to your app. - -To enable this capability, an additional dependency must be installed. - -```bash title="Terminal" showLineNumbers={false} -npm add @aws-amplify/rtn-web-browser -``` - -### Platform Setup - -On iOS, there are no additional setup steps. - -#### Android - -After a successful sign-in, the sign-in UI will attempt to redirect back to your application. To register the redirect URI scheme you configured above with the device, an `intent-filter` must be added to your application's `AndroidManifest.xml` file which should be located in your React Native app's `android/app/src/main` directory. - -Add the `intent-filter` to your application's main activity, replacing `myapp` with your redirect URI scheme as necessary. - -```xml title="android/app/src/main/AndroidManifest.xml" - - - ... - - - - - - - ... - - -``` - - - -To sign in using an external identity provider such as Google, use the `signInWithWebUI` function. - -### How It Works - -Sign-in with web UI will display the sign-in UI inside a webview. After the sign-in process is complete, the sign-in UI will redirect back to your app. - -### Platform Setup - -#### Web - -To use Hosted UI in your Flutter web application locally, you must run the app with the `--web-port=3000` argument (with the value being whichever port you assigned to localhost host when configuring your redirect URIs). - -#### Android - -Add the following `queries` element to the `AndroidManifest.xml` file in your app's `android/app/src/main` directory, as well as the following `intent-filter` to the `MainActivity` in the same file. - -Replace `myapp` with your redirect URI scheme as necessary: - -```xml - - - - - - - ... - - - - - - - - - ... - -``` - -#### macOS - -Open XCode and enable the App Sandbox capability and then select "Incoming Connections (Server)" under "Network". - -![Incoming Connections setting selected in the App Sandbox section of the runner signing and capabilities tab.](/images/project-setup/flutter/mac/xcode-entitlements.png) - -#### iOS, Windows and Linux - -No specific platform configuration is required. - -### Launch Social Web UI Sign In - -You're now ready to launch sign in with your external provider's web UI. - -```dart -Future socialSignIn() async { - try { - final result = await Amplify.Auth.signInWithWebUI( - provider: AuthProvider.google, - ); - safePrint('Sign in result: $result'); - } on AuthException catch (e) { - safePrint('Error signing in: ${e.message}'); - } -} -``` - - -To sign in using an external identity provider such as Google, use the `signInWithSocialWebUI` function. - -### Update AndroidManifest.xml - -Add the following activity and queries tag to your app's `AndroidManifest.xml` file, replacing `myapp` with -your redirect URI prefix if necessary: - -```xml - - ... - - - - - - - - - ... - -``` - -### Launch Social Web UI Sign In - -Sweet! You're now ready to launch sign in with your social provider's web UI. - -For now, just add this method to the `onCreate` method of MainActivity with whatever provider you're using (shown with Facebook below): - -#### [Java] - -```java -// Replace facebook with your chosen auth provider such as google, amazon, or apple -Amplify.Auth.signInWithSocialWebUI( - AuthProvider.facebook(), - this, - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -// Replace facebook with your chosen auth provider such as google, amazon, or apple -Amplify.Auth.signInWithSocialWebUI( - AuthProvider.facebook(), - this, - { Log.i("AuthQuickstart", "Sign in OK: $it") }, - { Log.e("AuthQuickstart", "Sign in failed", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - // Replace facebook with your chosen auth provider such as google, amazon, or apple - val result = Amplify.Auth.signInWithSocialWebUI(AuthProvider.facebook(), this) - Log.i("AuthQuickstart", "Sign in OK: $result") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign in failed", error) -} -``` - -#### [RxJava] - -```java -// Replace facebook with your chosen auth provider such as google, amazon, or apple -RxAmplify.Auth.signInWithSocialWebUI(AuthProvider.facebook(), this) - .subscribe( - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -To sign in using an external identity provider such as Google, use the `signInWithWebUI` function. - -### Update Info.plist - -Sign-in with web UI requires the Amplify plugin to show up the sign-in UI inside a webview. After the sign-in process is complete it will redirect back to your app. -You have to enable this in your app's `Info.plist`. Right click Info.plist and then choose Open As > Source Code. Add the following entry in the URL scheme: - -```xml - - - - - - - - - CFBundleURLTypes - - - CFBundleURLSchemes - - myapp - - - - - - -``` - -When creating a new SwiftUI app using Xcode 13 no longer require configuration files such as the Info.plist. If you are missing this file, click on the project target, under Info, Url Types, and click '+' to add a new URL Type. Add `myapp` to the URL Schemes. You should see the Info.plist file now with the entry for CFBundleURLSchemes. - -### Launch Social Web UI Sign In - -Invoke the following API with the provider you're using (shown with Facebook below): - -#### [Async/Await] - -```swift -func socialSignInWithWebUI() async { - do { - let signInResult = try await Amplify.Auth.signInWithWebUI(for: .facebook, presentationAnchor: self.view.window!) - if signInResult.isSignedIn { - print("Sign in succeeded") - } - } catch let error as AuthError { - print("Sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func socialSignInWithWebUI() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.signInWithWebUI(for: .facebook, presentationAnchor: self.view.window!) - }.sink { - if case let .failure(authError) = $0 { - print("Sign in failed \(authError)") - } - } - receiveValue: { signInResult in - if signInResult.isSignedIn { - print("Sign in succeeded") - } - } -} -``` - - - - -## Sign in with passwordless methods - -Your application's users can also sign in using passwordless methods. To learn more, including how to setup the various passwordless authentication flows, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/). - -### SMS OTP - - -Pass `SMS_OTP` as the `preferredChallenge` when calling the `signIn` API in order to initiate a passwordless authentication flow with SMS OTP. - -```ts -const { nextStep: signInNextStep } = await signIn({ - username: '+15551234567', - options: { - authFlowType: 'USER_AUTH', - preferredChallenge: 'SMS_OTP', - }, -}); - -if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_CODE') { - // prompt user for otp code delivered via SMS - const { nextStep: confirmSignInNextStep } = await confirmSignIn({ - challengeResponse: '123456', - }); - - if (confirmSignInNextStep.signInStep === 'DONE') { - console.log('Sign in successful!'); - } -} -``` - - -Pass `SMS_OTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with SMS OTP. - -#### [Java] - -```java -// Use options to specify the preferred first factor -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP - .build(); - -// Sign in the user -Amplify.Auth.signIn( - username, - null, // no password - options, - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - // Show UI to collect OTP - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -); - -// Then pass that OTP into the confirmSignIn API -Amplify.Auth.confirmSignIn( - "123456", - result -> { - // result.getNextStep().getSignInStep() should be "DONE" now - }, - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -// Use options to specify the preferred first factor -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP - .build() - -// Sign in the user -Amplify.Auth.signIn( - username, - null, // no password - options, - { result: AuthSignInResult -> - if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - // Show UI to collect OTP - } - }, - { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } -) - -// Then pass that OTP into the confirmSignIn API -Amplify.Auth.confirmSignIn( - "123456", - { result: AuthSignInResult? -> }, - { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -// Use options to specify the preferred first factor -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP - .build() - -// Sign in the user -val result = Amplify.Auth.signIn( - username = username, - password = null, - options = options -) -if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - // Show UI to collect OTP -} - -// Then pass that OTP into the confirmSignIn API -val confirmResult = Amplify.Auth.confirmSignIn( - challengeResponse = "123456" -) -// confirmResult.nextStep.signInStep should be "DONE" -``` - -#### [RxJava] - -```java -// Use options to specify the preferred first factor -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP - .build(); - -// Sign in the user -RxAmplify.Auth.signIn( - username, - null, // no password - options -).subscribe( - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - // Show UI to collect OTP - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -) - -// Then pass that OTP into the confirmSignIn API -RxAmplify.Auth.confirmSignIn("123456") - .subscribe( - result -> { - // result.getNextStep().getSignInStep() should be "DONE" now - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - -Pass `smsOTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with SMS OTP. - -#### [Async/Await] - -```swift -// sign in with `smsOTP` as preferred factor -func signIn(username: String) async { - do { - let pluginOptions = AWSAuthSignInOptions( - authFlowType: .userAuth(preferredFirstFactor: .smsOTP)) - let signInResult = try await Amplify.Auth.signIn( - username: username, - options: .init(pluginOptions: pluginOptions)) - print("Sign in succeeded. Next step: \(signInResult.nextStep)") - } catch let error as AuthError { - print("Sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} - -// confirm sign in with the code received -func confirmSignIn() async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: "") - print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") - } catch let error as AuthError { - print("Confirm sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} - -``` - -#### [Combine] - -```swift -// sign in with `smsOTP` as preferred factor -func signIn(username: String) -> AnyCancellable { - Amplify.Publisher.create { - let pluginOptions = AWSAuthSignInOptions( - authFlowType: .userAuth(preferredFirstFactor: .smsOTP)) - try await Amplify.Auth.signIn( - username: username, - options: .init(pluginOptions: pluginOptions)) - }.sink { - if case let .failure(authError) = $0 { - print("Sign in failed \(authError)") - } - } - receiveValue: { signInResult in - print("Sign in succeeded. Next step: \(signInResult.nextStep)") - } -} - -// confirm sign in with the code received -func confirmSignIn() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn(challengeResponse: "") - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") - } -} -``` - - - -### Email OTP - - -Pass `EMAIL_OTP` as the `preferredChallenge` when calling the `signIn` API in order to initiate a passwordless authentication flow using email OTP. - -```ts -const { nextStep: signInNextStep } = await signIn({ - username: 'hello@example.com', - options: { - authFlowType: 'USER_AUTH', - preferredChallenge: 'EMAIL_OTP', - }, -}); - -if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE') { - // prompt user for otp code delivered via email - const { nextStep: confirmSignInNextStep } = await confirmSignIn({ - challengeResponse: '123456', - }); - - if (confirmSignInNextStep.signInStep === 'DONE') { - console.log('Sign in successful!'); - } -} -``` - - -Pass `EMAIL_OTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with Email OTP. - -#### [Java] - -```java -// Use options to specify the preferred first factor -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP - .build(); - -// Sign in the user -Amplify.Auth.signIn( - username, - null, // no password - options, - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - // Show UI to collect OTP - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -); - -// Then pass that OTP into the confirmSignIn API -Amplify.Auth.confirmSignIn( - "123456", - result -> { - // result.getNextStep().getSignInStep() should be "DONE" now - }, - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -// Use options to specify the preferred first factor -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP - .build() - -// Sign in the user -Amplify.Auth.signIn( - username, - null, // no password - options, - { result: AuthSignInResult -> - if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - // Show UI to collect OTP - } - }, - { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } -) - -// Then pass that OTP into the confirmSignIn API -Amplify.Auth.confirmSignIn( - "123456", - { result: AuthSignInResult? -> }, - { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -// Use options to specify the preferred first factor -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP - .build() - -// Sign in the user -val result = Amplify.Auth.signIn( - username = username, - password = null, - options = options -) -if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - // Show UI to collect OTP -} - -// Then pass that OTP into the confirmSignIn API -val confirmResult = Amplify.Auth.confirmSignIn( - challengeResponse = "123456" -) -// confirmResult.nextStep.signInStep should be "DONE" -``` - -#### [RxJava] - -```java -// Use options to specify the preferred first factor -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP - .build(); - -// Sign in the user -RxAmplify.Auth.signIn( - username, - null, // no password - options -).subscribe( - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - // Show UI to collect OTP - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -) - -// Then pass that OTP into the confirmSignIn API -RxAmplify.Auth.confirmSignIn("123456") - .subscribe( - result -> { - // result.getNextStep().getSignInStep() should be "DONE" now - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - -Pass `emailOTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with Email OTP. - -#### [Async/Await] - -```swift -// sign in with `emailOTP` as preferred factor -func signIn(username: String) async { - do { - let pluginOptions = AWSAuthSignInOptions( - authFlowType: .userAuth(preferredFirstFactor: .emailOTP)) - let signInResult = try await Amplify.Auth.signIn( - username: username, - options: .init(pluginOptions: pluginOptions)) - print("Sign in succeeded. Next step: \(signInResult.nextStep)") - } catch let error as AuthError { - print("Sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} - -// confirm sign in with the code received -func confirmSignIn() async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: "") - print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") - } catch let error as AuthError { - print("Confirm sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} - -``` - -#### [Combine] - -```swift -// sign in with `emailOTP` as preferred factor -func signIn(username: String) -> AnyCancellable { - Amplify.Publisher.create { - let pluginOptions = AWSAuthSignInOptions( - authFlowType: .userAuth(preferredFirstFactor: .emailOTP)) - try await Amplify.Auth.signIn( - username: username, - options: .init(pluginOptions: pluginOptions)) - }.sink { - if case let .failure(authError) = $0 { - print("Sign in failed \(authError)") - } - } - receiveValue: { signInResult in - print("Sign in succeeded. Next step: \(signInResult.nextStep)") - } -} - -// confirm sign in with the code received -func confirmSignIn() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn(challengeResponse: "") - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") - } -} -``` - - - -### WebAuthn Passkeys - - -Pass `WEB_AUTHN` as the `preferredChallenge` in order to initiate the passwordless authentication flow using a WebAuthn credential. - -```ts -const { nextStep: signInNextStep } = await signIn({ - username: 'hello@example.com', - options: { - authFlowType: 'USER_AUTH', - preferredChallenge: 'WEB_AUTHN', - }, -}); - -if (signInNextStep.signInStep === 'DONE') { - console.log('Sign in successful!'); -} -``` - - -Pass `WEB_AUTHN` as the `preferredFirstFactor` in order to initiate the passwordless authentication flow using a WebAuthn credential. This flow -completes without any additional interaction from your application, so there is only one `Amplify.Auth` call needed for WebAuthn. - - -The user must have previously associated a credential to use this auth factor. To learn more, visit the [manage WebAuthn credentials page](/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/). - - - -Amplify requires an `Activity` reference to attach the PassKey UI to your Application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack) when using WebAuthn - if an `Activity` is not supplied then the UI will appear in a separate Task. For this reason, we strongly recommend always passing the `callingActivity` option to both the `signIn` and `confirmSignIn` APIs if your application allows users to sign in with passkeys. - - -#### [Java] - -```java -// Use options to specify the preferred first factor -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .callingActivity(callingActivity) - .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn - .build(); - -// Sign in the user -Amplify.Auth.signIn( - username, - null, // no password - options, - result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -// Use options to specify the preferred first factor -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .callingActivity(callingActivity) - .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn - .build() - -// Sign in the user -Amplify.Auth.signIn( - username, - null, // no password - options, - { result: AuthSignInResult -> Log.i("AuthQuickStart", "Next sign in step: ${result.nextStep}") }, - { error: AuthException -> Log.e("AuthQuickStart", error.toString()) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -// Use options to specify the preferred first factor -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .callingActivity(callingActivity) - .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn - .build() - -// Sign in the user -val result = Amplify.Auth.signIn( - username = username, - password = null, - options = options -) - -// result.nextStep.signInStep should be "DONE" if use granted access to the passkey -// NOTE: `signIn` will throw a UserCancelledException if user dismissed the passkey UI -``` - -#### [RxJava] - -```java -// Use options to specify the preferred first factor -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .callingActivity(callingActivity) - .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn - .build(); - -// Sign in the user -RxAmplify.Auth.signIn( - username, - null, // no password - options -).subscribe( - result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), - error -> Log.e("AuthQuickstart", error.toString()) -) -``` - -Using WebAuthn sign in may result in a number of possible exception types. - -- `UserCancelledException` - If the user declines to authorize access to the passkey in the system UI. You can retry the WebAuthn flow by invoking `confirmSignIn` again, or restart the `signIn` process to select a different `AuthFactorType`. -- `WebAuthnNotEnabledException` - This indicates WebAuthn is not enabled in your user pool. -- `WebAuthnNotSupportedException` - This indicates WebAuthn is not supported on the user's device. -- `WebAuthnRpMismatchException` - This indicates there is a problem with the `assetlinks.json` file deployed to your relying party. -- `WebAuthnFailedException` - This exception is used for other errors that may occur with WebAuthn. Inspect the `cause` to determine the best course of action. - - - - -Pass `webAuthn` as the `preferredFirstFactor` in order to initiate the passwordless authentication flow using a WebAuthn credential. - -#### [Async/Await] - -```swift -// sign in with `webAuthn` as preferred factor -func signIn(username: String) async { - do { - let authFactorType : AuthFactorType - if #available(iOS 17.4, *) { - authFactorType = .webAuthn - } else { - // Fallback on earlier versions - authFactorType = .passwordSRP - } - - let signInResult = try await Amplify.Auth.signIn( - username: username, - options: .init(pluginOptions: AWSAuthSignInOptions( - authFlowType: .userAuth( - preferredFirstFactor: authFactorType)))) - print("Sign in succeeded. Next step: \(signInResult.nextStep)") - } catch let error as AuthError { - print("Sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} - -``` - -#### [Combine] - -```swift -// sign in with `webAuthn` as preferred factor -func signIn(username: String) async { - Amplify.Publisher.create { - let authFactorType : AuthFactorType - if #available(iOS 17.4, *) { - authFactorType = .webAuthn - } else { - // Fallback on earlier versions - authFactorType = .passwordSRP - } - - try await Amplify.Auth.signIn( - username: username, - options: .init(pluginOptions: AWSAuthSignInOptions( - authFlowType: .userAuth( - preferredFirstFactor: authFactorType)))) - }.sink { - if case let .failure(authError) = $0 { - print("Sign in failed \(authError)") - } - } - receiveValue: { signInResult in - print("Sign in succeeded. Next step: \(signInResult.nextStep)") - } -} -``` - - - -### Password - - -Pass either `PASSWORD` or `PASSWORD_SRP` as the `preferredChallenge` in order to initiate a traditional password based authentication flow. - -```ts -const { nextStep: signInNextStep } = await signIn({ - username: 'hello@example.com', - password: 'example-password', - options: { - authFlowType: 'USER_AUTH', - preferredChallenge: 'PASSWORD_SRP', // or 'PASSWORD' - }, -}); - -if (confirmSignInNextStep.signInStep === 'DONE') { - console.log('Sign in successful!'); -} -``` - - - -Pass either `PASSWORD` or `PASSWORD_SRP` as the `preferredFirstFactor` in order to initiate a traditional password based authentication flow. - -#### [Java] - -```java -// Use options to specify the preferred first factor -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.PASSWORD) // Sign in using Password - .build(); - -// Sign in the user -Amplify.Auth.signIn( - username, - password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP - options, - result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -// Use options to specify the preferred first factor -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.PASSWORD) // Sign in using Password - .build() - -// Sign in the user -Amplify.Auth.signIn( - username, - password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP - options, - { result: AuthSignInResult -> Log.i("AuthQuickStart", "Next sign in step: ${result.nextStep}") }, - { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -// Use options to specify the preferred first factor -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.PASSWORD) // Sign in using Password - .build() - -// Sign in the user -val result = Amplify.Auth.signIn( - username = username, - password = password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP - options = options -) - -// result.nextStep.signInStep should be "DONE" -``` - -#### [RxJava] - -```java -// Use options to specify the preferred first factor -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.Password) // Sign in using Password - .build(); - -// Sign in the user -RxAmplify.Auth.signIn( - username, - password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP - options -).subscribe( - result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), - error -> Log.e("AuthQuickstart", error.toString()) -) -``` - - - -Pass either `password` or `passwordSRP` as the `preferredFirstFactor` in order to initiate a traditional password based authentication flow. - -#### [Async/Await] - -```swift -// sign in with `password` as preferred factor -func signIn(username: String) async { - do { - let pluginOptions = AWSAuthSignInOptions( - authFlowType: .userAuth(preferredFirstFactor: .password)) - let signInResult = try await Amplify.Auth.signIn( - username: username, - options: .init(pluginOptions: pluginOptions)) - print("Sign in succeeded. Next step: \(signInResult.nextStep)") - } catch let error as AuthError { - print("Sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} - -``` - -#### [Combine] - -```swift -// sign in with `password` as preferred factor -func signIn(username: String) async { - Amplify.Publisher.create { - let pluginOptions = AWSAuthSignInOptions( - authFlowType: .userAuth(preferredFirstFactor: .password)) - try await Amplify.Auth.signIn( - username: username, - options: .init(pluginOptions: pluginOptions)) - }.sink { - if case let .failure(authError) = $0 { - print("Sign in failed \(authError)") - } - } - receiveValue: { signInResult in - print("Sign in succeeded. Next step: \(signInResult.nextStep)") - } -} -``` - - - -### First Factor Selection - - -Omit the `preferredChallenge` parameter to discover which first factors are available for a given user. This is useful to allow -users to choose how they would like to sign in. - -The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. - -```ts -const { nextStep: signInNextStep } = await signIn({ - username: '+15551234567', - options: { - authFlowType: 'USER_AUTH', - }, -}); - -if ( - signInNextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION' -) { - // present user with list of available challenges - console.log(`Available Challenges: ${signInNextStep.availableChallenges}`); - - // respond with user selection using `confirmSignIn` API - const { nextStep: nextConfirmSignInStep } = await confirmSignIn({ - challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' - }); -} - -``` - - - -Omit the `preferredFirstFactor` option to discover which first factors are available for a given user. This is useful to allow -users to choose how they would like to sign in. - -The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. - -#### [Java] - -```java -// Omit preferredFirstFactor. If the user has more than one factor available then -// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. -AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .build(); - -// Step 1: Sign in the user -Amplify.Auth.signIn( - "hello@example.com", - null, - options, - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { - Log.i( - "AuthQuickstart", - "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors() - ); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -); - -// Step 2: Select SMS OTP for sign in -Amplify.Auth.confirmSignIn( - AuthFactorType.SMS_OTP.getChallengeResponse(), - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - Log.i( - "AuthQuickStart", - "OTP code sent to " + result.getNextStep().getCodeDeliveryDetails() - ) - // Show UI to collect OTP - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -); - -// Step 3: Then pass that OTP into the confirmSignIn API -Amplify.Auth.confirmSignIn( - "123456", - result -> { - // result.getNextStep().getSignInStep() should be "DONE" now - }, - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -// Omit preferredFirstFactor. If the user has more than one factor available then -// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. -val options: AuthSignInOptions = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .build() - -// Step 1: Sign in the user -Amplify.Auth.signIn( - "hello@example.com", - null, - options, - { result: AuthSignInResult -> - if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { - Log.i( - "AuthQuickstart", - "Available authentication factors for this user: ${result.nextStep.availableFactors}" - ) - } - }, - { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } -) - -// Step 2: Select SMS OTP for sign in -Amplify.Auth.confirmSignIn( - AuthFactorType.SMS_OTP.getChallengeResponse(), - { result: AuthSignInResult -> - if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - Log.i( - "AuthQuickStart", - "OTP code sent to ${result.nextStep.codeDeliveryDetails}" - ) - // Show UI to collect OTP - } - }, - { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } -) - -// Step 3: Then pass that OTP into the confirmSignIn API -Amplify.Auth.confirmSignIn( - "123456", - { result: AuthSignInResult? -> - // result.nextStep.signInStep should be "DONE" now - }, - { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -// Omit preferredFirstFactor. If the user has more than one factor available then -// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. -val options: AuthSignInOptions = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .build() - -// Step 1: Sign in the user -val result = Amplify.Auth.signIn( - username = "hello@example.com", - password = null, - options = options -) - -if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { - Log.i( - "AuthQuickStart", - "Available authentication factors for this user: ${result.nextStep.availableFactors}" - ) -} - -// Step 2: Select SMS OTP for sign in -val selectFactorResult = Amplify.Auth.confirmSignIn(challengeResponse = AuthFactorType.SMS_OTP.challengeResponse) - -if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - Log.i( - "AuthQuickStart", - "OTP code sent to ${result.nextStep.codeDeliveryDetails}" - ) - // Show UI to collect OTP -} - -// Step 3: Then pass that OTP into the confirmSignIn API -val confirmResult = Amplify.Auth.confirmSignIn(challengeResponse = "123456") - -// confirmResult.nextStep.signInStep should be "DONE" now -``` - -#### [RxJava] - -```java -// Omit preferredFirstFactor. If the user has more than one factor available then -// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .build(); - -// Step 1: Sign in the user -RxAmplify.Auth.signIn( - username, - null, // no password - options -).subscribe( - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { - Log.i( - "AuthQuickstart", - "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors() - ); - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -) - -// Step 2: Select SMS OTP for sign in -RxAmplify.Auth.confirmSignIn(AuthFactorType.SMS_OTP.getChallengeResponse()) - .subscribe( - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - Log.i( - "AuthQuickStart", - "OTP code sent to " + result.getNextStep().getCodeDeliveryDetails() - ) - // Show UI to collect OTP - } - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); - -// Step 3: Then pass that OTP into the confirmSignIn API -RxAmplify.Auth.confirmSignIn("123456") - .subscribe( - result -> { - // result.getNextStep().getSignInStep() should be "DONE" now - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - -Omit the `preferredFirstFactor` parameter to discover which first factors are available for a given user. This is useful to allow -users to choose how they would like to sign in. - -The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. - -#### [Async/Await] - -```swift -// Step 1: Initiate UserAuth Sign-In -let pluginOptions = AWSAuthSignInOptions(authFlowType: .userAuth) -let signInResult = try await Amplify.Auth.signIn( - username: "user@example.com", - options: .init(pluginOptions: pluginOptions) -) - -switch signInResult.nextStep { -case .continueSignInWithFirstFactorSelection(let availableFactors): - print("Available factors to select: \(availableFactors)") - // Prompt the user to select a first factor -default: - break -} - -// Step 2: Select Authentication Factor -let confirmSignInResult = try await Amplify.Auth.confirmSignIn( - challengeResponse: AuthFactorType.emailOTP.challengeResponse -) - -switch confirmSignInResult.nextStep { -case .confirmSignInWithOTP(let deliveryDetails): - print("Delivery details: \(deliveryDetails)") - // Prompt the user to enter email OTP code received -default: - break -} - -// Step 3: Complete Sign-In with OTP Code -let finalSignInResult = try await Amplify.Auth.confirmSignIn( - challengeResponse: "" -) - -if case .done = finalSignInResult.nextStep { - print("Login successful") -} -``` - -#### [Combine] - -```swift -func signInWithUserAuth(username: String, getOTP: @escaping () async -> String) -> AnyCancellable { - Amplify.Publisher.create { - // Step 1: Initiate UserAuth Sign-In - let pluginOptions = AWSAuthSignInOptions(authFlowType: .userAuth) - let signInResult = try await Amplify.Auth.signIn( - username: username, - options: .init(pluginOptions: pluginOptions) - ) - - // Step 2: Handle factor selection - if case .continueSignInWithFirstFactorSelection(let availableFactors) = signInResult.nextStep { - print("Available factors to select: \(availableFactors)") - - // For this example, we select emailOTP. You could prompt the user here. - let confirmSignInResult = try await Amplify.Auth.confirmSignIn( - challengeResponse: AuthFactorType.emailOTP.challengeResponse - ) - - // Step 3: Handle OTP delivery - if case .confirmSignInWithOTP(let deliveryDetails) = confirmSignInResult.nextStep { - print("Delivery details: \(deliveryDetails)") - - // Prompt user for OTP code (async closure) - let code = await getOTP() - - // Step 4: Complete sign-in with OTP code - let finalSignInResult = try await Amplify.Auth.confirmSignIn( - challengeResponse: code - ) - - return finalSignInResult - } else { - // Handle other next steps if needed - return confirmSignInResult - } - } else { - // Handle other next steps or immediate sign-in - return signInResult - } - } - .sink( - receiveCompletion: { completion in - if case let .failure(authError) = completion { - print("Sign in failed: \(authError)") - } - }, - receiveValue: { result in - if result.isSignedIn || (result.nextStep == .done) { - print("Sign in succeeded") - } else { - print("Next step: \(result.nextStep)") - } - } - ) -} -``` - - - ---- - ---- -title: "Switching authentication flows" -section: "build-a-backend/auth/connect-your-frontend" -platforms: ["angular", "javascript", "nextjs", "react", "react-native", "swift", "vue", "android"] -gen: 2 -last-updated: "2024-12-09T19:54:14.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/" ---- - - -`AWSCognitoAuthPlugin` allows you to switch between different auth flows while initiating signIn. You can configure the flow in the `amplify_outputs.json` file or pass the `authFlowType` as a runtime parameter to the `signIn` api call. - -For client side authentication there are four different flows that can be configured during runtime: - -1. `userSRP`: The `userSRP` flow uses the [SRP protocol (Secure Remote Password)](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) where the password never leaves the client and is unknown to the server. This is the recommended flow and is used by default. - -2. `userPassword`: The `userPassword` flow will send user credentials unencrypted to the back-end. If you want to migrate users to Cognito using the "Migration" trigger and avoid forcing users to reset their passwords, you will need to use this authentication type because the Lambda function invoked by the trigger needs to verify the supplied credentials. - -3. `customWithSRP`: The `customWithSRP` flow is used to start with SRP authentication and then switch to custom authentication. This is useful if you want to use SRP for the initial authentication and then use custom authentication for subsequent authentication attempts. - -4. `customWithoutSRP`: The `customWithoutSRP` flow is used to start authentication flow **WITHOUT** SRP and then use a series of challenge and response cycles that can be customized to meet different requirements. - -5. `userAuth`: The `userAuth` flow is a choice-based authentication flow that allows the user to choose from the list of available authentication methods. This flow is useful when you want to provide the user with the option to choose the authentication method. The choices that may be available to the user are `emailOTP`, `smsOTP`, `webAuthn`, `password` or `passwordSRP`. - -`Auth` can be configured to use the different flows at runtime by calling `signIn` with `AuthSignInOptions`'s `authFlowType` as `AuthFlowType.userPassword`, `AuthFlowType.customAuthWithoutSrp` or `AuthFlowType.customAuthWithSrp`. If you do not specify the `AuthFlowType` in `AuthSignInOptions`, the default flow (`AuthFlowType.userSRP`) will be used. - - - -Runtime configuration will take precedence and will override any auth flow type configuration present in amplify_outputs.json - - - -> For more information about authentication flows, please visit [Amazon Cognito developer documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-custom-authentication-flow) - -## USER_AUTH (Choice-based authentication) flow - -A use case for the `USER_AUTH` authentication flow is to provide the user with the option to choose the authentication method. The choices that may be available to the user are `emailOTP`, `smsOTP`, `webAuthn`, `password` or `passwordSRP`. - -```swift -let pluginOptions = AWSAuthSignInOptions( - authFlowType: .userAuth) -let signInResult = try await Amplify.Auth.signIn( - username: username, - password: password, - options: .init(pluginOptions: pluginOptions)) -guard case .continueSignInWithFirstFactorSelection(let availableFactors) = signInResult.nextStep else { - return -} -print("Available factors: \(availableFactors)") -``` - -The selection of the authentication method is done by the user. The user can choose from the available factors and proceed with the selected factor. You should call the `confirmSignIn` API with the selected factor to continue the sign-in process. Following is an example if you want to proceed with the `emailOTP` factor selection: - -```swift -// Select emailOTP as the factor -var confirmSignInResult = try await Amplify.Auth.confirmSignIn( - challengeResponse: AuthFactorType.emailOTP.challengeResponse) -``` - -## USER_PASSWORD_AUTH flow - -A use case for the `USER_PASSWORD_AUTH` authentication flow is migrating users into Amazon Cognito - -A user migration Lambda trigger helps migrate users from a legacy user management system into your user pool. If you choose the USER_PASSWORD_AUTH authentication flow, users don't have to reset their passwords during user migration. This flow sends your user's password to the service over an encrypted SSL connection during authentication. - -When you have migrated all your users, switch flows to the more secure SRP flow. The SRP flow doesn't send any passwords over the network. - -```swift -func signIn(username: String, password: String) async throws { - - let option = AWSAuthSignInOptions(authFlowType: .userPassword) - do { - let result = try await Amplify.Auth.signIn( - username: username, - password: password, - options: AuthSignInRequest.Options(pluginOptions: option)) - print("Sign in succeeded with result: \(result)") - } catch { - print("Failed to sign in with error: \(error)") - } -} -``` - -### Migrate users with Amazon Cognito - -Amazon Cognito provides a trigger to migrate users from your existing user directory seamlessly into Cognito. You achieve this by configuring your User Pool's "Migration" trigger which invokes a Lambda function whenever a user that does not already exist in the user pool authenticates, or resets their password. - -In short, the Lambda function will validate the user credentials against your existing user directory and return a response object containing the user attributes and status on success. An error message will be returned if an error occurs. There's documentation around [how to set up this migration flow](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html) and more detailed instructions on [how the lambda should handle request and response objects](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html#cognito-user-pools-lambda-trigger-syntax-user-migration). - -## CUSTOM_AUTH flow - -Amazon Cognito User Pools supports customizing the authentication flow to enable custom challenge types, in addition to a password in order to verify the identity of users. The custom authentication flow is a series of challenge and response cycles that can be customized to meet different requirements. These challenge types may include CAPTCHAs or dynamic challenge questions. - -To define your challenges for custom authentication flow, you need to implement three Lambda triggers for Amazon Cognito. - -The flow is initiated by calling `signIn` with `AuthSignInOptions` configured with `AuthFlowType.customAuthWithSrp` OR `AuthFlowType.customAuthWithoutSrp`. - -Follow the instructions in [Custom Auth Sign In](/gen1/[platform]/build-a-backend/auth/sign-in-custom-flow/) to learn about how to integrate custom authentication flow in your application with the Auth APIs. - - - -For client side authentication there are four different flows: - -1. `USER_SRP_AUTH`: The `USER_SRP_AUTH` flow uses the [SRP protocol (Secure Remote Password)](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) where the password never leaves the client and is unknown to the server. This is the recommended flow and is used by default. - -2. `USER_PASSWORD_AUTH`: The `USER_PASSWORD_AUTH` flow will send user credentials to the backend without applying SRP encryption. If you want to migrate users to Cognito using the "Migration" trigger and avoid forcing users to reset their passwords, you will need to use this authentication type because the Lambda function invoked by the trigger needs to verify the supplied credentials. - -3. `CUSTOM_WITH_SRP` & `CUSTOM_WITHOUT_SRP`: Allows for a series of challenge and response cycles that can be customized to meet different requirements. - -4. `USER_AUTH`: The `USER_AUTH` flow is a choice-based authentication flow that allows the user to choose from the list of available authentication methods. This flow is useful when you want to provide the user with the option to choose the authentication method. The choices that may be available to the user are `EMAIL_OTP`, `SMS_OTP`, `WEB_AUTHN`, `PASSWORD` or `PASSWORD_SRP`. - -The Auth flow can be customized when calling `signIn`, for example: - -```ts title="src/main.ts" -await signIn({ - username: "hello@mycompany.com", - password: "hunter2", - options: { - authFlowType: 'USER_AUTH' - } -}) -``` - -> For more information about authentication flows, please visit [AWS Cognito developer documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-custom-authentication-flow) - -## USER_AUTH flow - -The `USER_AUTH` sign in flow supports the following methods as first factors for authentication: `WEB_AUTHN`, `EMAIL_OTP`, `SMS_OTP`, `PASSWORD`, and `PASSWORD_SRP`. - -If the desired first factor is known when authentication is initiated, it can be passed to the `signIn` API as the `preferredChallenge` to initiate the corresponding authentication flow. - -```ts -// PASSWORD_SRP / PASSWORD -// sign in with preferred challenge as password -// note password must be provided in same step -const { nextStep } = await signIn({ - username: "hello@mycompany.com", - password: "hunter2", - options: { - authFlowType: "USER_AUTH", - preferredChallenge: "PASSWORD_SRP" // or "PASSWORD" - }, -}); - -// WEB_AUTHN / EMAIL_OTP / SMS_OTP -// sign in with preferred passwordless challenge -// no additional user input required at this step -const { nextStep } = await signIn({ - username: "hello@example.com", - options: { - authFlowType: "USER_AUTH", - preferredChallenge: "WEB_AUTHN" // or "EMAIL_OTP" or "SMS_OTP" - }, -}); -``` - -If the desired first factor is not known or you would like to provide users with the available options, `preferredChallenge` can be omitted from the initial `signIn` API call. - -This allows you to discover which authentication first factors are available for a user via the `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` step. You can then present the available options to the user and use the `confirmSignIn` API to respond with the user's selection. - -```ts -const { nextStep: signInNextStep } = await signIn({ - username: '+15551234567', - options: { - authFlowType: 'USER_AUTH', - }, -}); - -if ( - signInNextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION' -) { - // present user with list of available challenges - console.log(`Available Challenges: ${signInNextStep.availableChallenges}`); - - // respond with user selection using `confirmSignIn` API - const { nextStep: nextConfirmSignInStep } = await confirmSignIn({ - challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' - }); -} - -``` -Also, note that if the `preferredChallenge` passed to the initial `signIn` API call is unavailable for the user, Amplify will also respond with the `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` next step. - - -For more information about determining a first factor, and signing in with passwordless authentication factors, please visit the [Passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/) concepts page. - - -## USER_PASSWORD_AUTH flow - -A use case for the `USER_PASSWORD_AUTH` authentication flow is migrating users into Amazon Cognito - -### Set up auth backend - -In order to use the authentication flow `USER_PASSWORD_AUTH`, your Cognito app client has to be configured to allow it. In the AWS Console, this is done by ticking the checkbox at General settings > App clients > Show Details (for the affected client) > Enable username-password (non-SRP) flow. If you're using the AWS CLI or CloudFormation, update your app client by adding `USER_PASSWORD_AUTH` to the list of "Explicit Auth Flows". - -### Migrate users with Amazon Cognito - -Amazon Cognito provides a trigger to migrate users from your existing user directory seamlessly into Cognito. You achieve this by configuring your User Pool's "Migration" trigger which invokes a Lambda function whenever a user that does not already exist in the user pool authenticates, or resets their password. - -In short, the Lambda function will validate the user credentials against your existing user directory and return a response object containing the user attributes and status on success. An error message will be returned if an error occurs. Visit [Amazon Cognito user pools import guide](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html) for migration flow and more detailed instruction, and [Amazon Cognito Lambda trigger guide](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html#cognito-user-pools-lambda-trigger-syntax-user-migration) on how to set up lambda to handle request and response objects. - -## `CUSTOM_WITH_SRP` & `CUSTOM_WITHOUT_SRP` flows - -Amazon Cognito user pools supports customizing the authentication flow to enable custom challenge types, -in addition to a password in order to verify the identity of users. These challenge types may include CAPTCHAs -or dynamic challenge questions. The `CUSTOM_WITH_SRP` flow requires a password when calling `signIn`. Both of -these flows map to the `CUSTOM_AUTH` flow in Cognito. - - - -To define your challenges for custom authentication flow, you need to implement three Lambda triggers for Amazon Cognito. Please visit [AWS Amplify Custom Auth Challenge example](/[platform]/build-a-backend/functions/examples/custom-auth-flows/) for set up instructions. - - - - - -For more information about working with Lambda Triggers for custom authentication challenges, please visit [Amazon Cognito Developer Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). - - - -### Custom authentication flow - -To initiate a custom authentication flow in your app, call `signIn` without a password. A custom challenge needs to be answered using the `confirmSignIn` API: - -```ts title="src/main.ts" -import { signIn, confirmSignIn } from 'aws-amplify/auth'; - -const challengeResponse = 'the answer for the challenge'; - -const { nextStep } = await signIn({ - username, - options: { - authFlowType: 'CUSTOM_WITHOUT_SRP', - }, -}); - -if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE') { - // to send the answer of the custom challenge - await confirmSignIn({ challengeResponse }); -} -``` - -### CAPTCHA authentication - -To create a CAPTCHA challenge with a Lambda Trigger, please visit [AWS Amplify Google reCAPTCHA challenge example](/[platform]/build-a-backend/functions/examples/google-recaptcha-challenge/) for detailed examples. - - - -`AWSCognitoAuthPlugin` allows you to switch between different auth flows while initiating signIn. You can configure the flow in the `amplify_outputs.json` file or pass the `authFlowType` as a option to the `signIn` api call. - -For client side authentication there are four different flows that can be configured during runtime: - -1. `USER_SRP_AUTH`: The `USER_SRP_AUTH` flow uses the [SRP protocol (Secure Remote Password)](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) where the password never leaves the client and is unknown to the server. This is the recommended flow and is used by default. - -2. `USER_PASSWORD_AUTH`: The `USER_PASSWORD_AUTH` flow will send user credentials unencrypted to the back-end. If you want to migrate users to Cognito using the "Migration" trigger and avoid forcing users to reset their passwords, you will need to use this authentication type because the Lambda function invoked by the trigger needs to verify the supplied credentials. - -3. `CUSTOM_AUTH_WITH_SRP`: The `CUSTOM_AUTH_WITH_SRP` flow is used to start with SRP authentication and then switch to custom authentication. This is useful if you want to use SRP for the initial authentication and then use custom authentication for subsequent authentication attempts. - -4. `CUSTOM_AUTH_WITHOUT_SRP`: The `CUSTOM_AUTH_WITHOUT_SRP` flow is used to start authentication flow **WITHOUT** SRP and then use a series of challenge and response cycles that can be customized to meet different requirements. - -5. `USER_AUTH`: The `USER_AUTH` flow is a choice-based authentication flow that allows the user to choose from the list of available authentication methods. This flow is useful when you want to provide the user with the option to choose the authentication method. The choices that may be available to the user are `EMAIL_OTP`, `SMS_OTP`, `WEB_AUTHN`, `PASSWORD` or `PASSWORD_SRP`. - -`Auth` can be configured to use the different flows at runtime by calling `signIn` with `AWSCognitoAuthSignInOptions`'s `authFlowType` as `AuthFlowType.USER_PASSWORD_AUTH`, `AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP`, `AuthFlowType.CUSTOM_AUTH_WITH_SRP`, or `AuthFlowType.USER_AUTH`. If you do not specify the `AuthFlowType` in `AWSCognitoAuthSignInOptions`, the default flow specified in `amplify_outputs.json` will be used. - - - -Runtime configuration will take precedence and will override any auth flow type configuration present in `amplify_outputs.json`. - - - -For more information about authentication flows, please visit [Amazon Cognito developer documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-custom-authentication-flow) - -## USER_AUTH (Choice-based authentication) flow - -A use case for the `USER_AUTH` authentication flow is to provide the user with the option to choose the authentication method. The choices that may be available to the user are `EMAIL_OTP`, `SMS_OTP`, `WEB_AUTHN`, `PASSWORD` or `PASSWORD_SRP`. - - -Amplify requires an `Activity` reference to attach the PassKey UI to your Application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack) when using WebAuthn - if an `Activity` is not supplied then the UI will appear in a separate Task. For this reason, we strongly recommend passing the `callingActivity` option to both the `signIn` and `confirmSignIn` APIs if your application uses the `USER_AUTH` flow. - - -If the desired first factor is known before the sign in flow is initiated it can be passed to the initial sign in call. - -#### [Java] - -```java -// PASSWORD_SRP / PASSWORD -// Sign in with preferred challenge as password -// NOTE: Password must be provided in the same step -AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .callingActivity(activity) - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" - .build(); -Amplify.Auth.signIn( - username, - password, - options, - result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), - error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) -); - -// WEB_AUTHN / EMAIL_OTP / SMS_OTP -// Sign in with preferred passwordless challenge -// No user input is required at this step -AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .callingActivity(activity) - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" - .build(); -Amplify.Auth.signIn( - username, - null, - options, - result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), - error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -// PASSWORD_SRP / PASSWORD -// Sign in with preferred challenge as password -// NOTE: Password must be provided in the same step -val options = AWSCognitoAuthSignInOptions.builder() - .callingActivity(activity) - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" - .build() -Amplify.Auth.signIn( - username, - password, - options, - { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, - { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } -) - -// WEB_AUTHN / EMAIL_OTP / SMS_OTP -// Sign in with preferred passwordless challenge -// No user input is required at this step -val options = AWSCognitoAuthSignInOptions.builder() - .callingActivity(activity) - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" - .build() -Amplify.Auth.signIn( - username, - null, - options, - { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, - { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -// PASSWORD_SRP / PASSWORD -// Sign in with preferred challenge as password -// NOTE: Password must be provided in the same step -try { - val options = AWSCognitoAuthSignInOptions.builder() - .callingActivity(activity) - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" - .build() - val result = Amplify.Auth.signIn( - username = "hello@example.com", - password = "password", - options = options - ) - Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign in failed", error) -} - -// WEB_AUTHN / EMAIL_OTP / SMS_OTP -// Sign in with preferred passwordless challenge -// No user input is required at this step -try { - val options = AWSCognitoAuthSignInOptions.builder() - .callingActivity(activity) - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" - .build() - val result = Amplify.Auth.signIn( - username = "hello@example.com", - password = null, - options = options - ) - Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign in failed", error) -} -``` - -#### [RxJava] - -```java -// PASSWORD_SRP / PASSWORD -// Sign in with preferred challenge as password -// NOTE: Password must be provided in the same step -AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .callingActivity(activity) - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" - .build(); -RxAmplify.Auth.signIn(username, password, options) - .subscribe( - result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), - error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) - ); - -// WEB_AUTHN / EMAIL_OTP / SMS_OTP -// Sign in with preferred passwordless challenge -// No user input is required at this step -AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .callingActivity(activity) - .authFlowType(AuthFlowType.USER_AUTH) - .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" - .build(); -RxAmplify.Auth.signIn(username, null, options) - .subscribe( - result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), - error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) - ); -``` - -If the preferred first factor is not supplied or is unavailable, and the user has multiple factors available, the flow will continue to select an available first factor by returning an `AuthNextSignInStep.signInStep` value of `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, and a list of `AuthNextSignInStep.availableFactors`. - -The selection of the authentication method is done by the user. The user can choose from the available factors and proceed with the selected factor. You should call the `confirmSignIn` API with the selected factor to continue the sign-in process. Following is an example if you want to proceed with the `WEB_AUTHN` factor selection: - -#### [Java] - -```java -AuthConfirmSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() - .callingActivity(activity) - .build(); -Amplify.Auth.confirmSignIn( - AuthFactorType.WEB_AUTHN.getChallengeResponse(), - options, - result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), - error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val options = AWSCognitoAuthConfirmSignInOptions.builder() - .callingActivity(activity) - .build() -Amplify.Auth.confirmSignIn( - AuthFactorType.WEB_AUTHN.challengeResponse, - options, - { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, - { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val options = AWSCognitoAuthConfirmSignInOptions.builder() - .callingActivity(activity) - .build() - val result = Amplify.Auth.confirmSignIn( - challengeResponse = AuthFactorType.WEB_AUTHN.challengeResponse, - options = options - ) - Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign in failed", error) -} -``` - -#### [RxJava] - -```java -AuthConfirmSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() - .callingActivity(activity) - .build(); -RxAmplify.Auth.confirmSignIn(AuthFactorType.WEB_AUTHN.getChallengeResponse(), options) - .subscribe( - result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), - error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) - ); -``` - -## USER_PASSWORD_AUTH flow - -A use case for the `USER_PASSWORD_AUTH` authentication flow is migrating users into Amazon Cognito - -A user migration Lambda trigger helps migrate users from a legacy user management system into your user pool. If you choose the USER_PASSWORD_AUTH authentication flow, users don't have to reset their passwords during user migration. This flow sends your user's password to the service over an encrypted SSL connection during authentication. - -When you have migrated all your users, switch flows to the more secure SRP flow. The SRP flow doesn't send any passwords over the network. - -#### [Java] - -```java -AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) - .build(); -Amplify.Auth.signIn( - "hello@example.com", - "password", - options, - result -> Log.i("AuthQuickStart", "Sign in succeeded with result " + result), - error -> Log.e("AuthQuickStart", "Failed to sign in", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) - .build() -Amplify.Auth.signIn( - "hello@example.com", - "password", - options, - { result -> - Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") - }, - { error -> - Log.e("AuthQuickstart", "Failed to sign in", error) - } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) - .build() - val result = Amplify.Auth.signIn( - username = "hello@example.com", - password = "password", - options = options - ) - Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign in failed", error) -} -``` - -#### [RxJava] - -```java -AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) - .build(); -RxAmplify.Auth.signIn("hello@example.com", "password", options) - .subscribe( - result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - -### Set up auth backend - -In order to use the authentication flow `USER_PASSWORD_AUTH`, your Cognito app client has to be configured to allow it. Amplify Gen 2 enables SRP auth by default. To enable USER_PASSWORD_AUTH, you can update the `backend.ts` file with the following changes: - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend' -import { auth } from './auth/resource' -import { data } from './data/resource' - -const backend = defineBackend({ - auth, - data, -}); - -// highlight-start -backend.auth.resources.cfnResources.cfnUserPoolClient.explicitAuthFlows = [ - "ALLOW_USER_PASSWORD_AUTH", - "ALLOW_USER_SRP_AUTH", - "ALLOW_USER_AUTH", - "ALLOW_REFRESH_TOKEN_AUTH" -]; -// highlight-end -``` - -### Migrate users with Amazon Cognito - -Amazon Cognito provides a trigger to migrate users from your existing user directory seamlessly into Cognito. You achieve this by configuring your User Pool's "Migration" trigger which invokes a Lambda function whenever a user that does not already exist in the user pool authenticates, or resets their password. - -In short, the Lambda function will validate the user credentials against your existing user directory and return a response object containing the user attributes and status on success. An error message will be returned if an error occurs. There's documentation around [how to set up this migration flow](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html) and more detailed instructions on [how the lambda should handle request and response objects](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html#cognito-user-pools-lambda-trigger-syntax-user-migration). - -## CUSTOM_AUTH flow - -Amazon Cognito User Pools supports customizing the authentication flow to enable custom challenge types, in addition to a password in order to verify the identity of users. The custom authentication flow is a series of challenge and response cycles that can be customized to meet different requirements. These challenge types may include CAPTCHAs or dynamic challenge questions. - -To define your challenges for custom authentication flow, you need to implement three Lambda triggers for Amazon Cognito. - -The flow is initiated by calling `signIn` with `AWSCognitoAuthSignInOptions` configured with `AuthFlowType.CUSTOM_AUTH_WITH_SRP` OR `AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP`. - -Follow the instructions in [Custom Auth Sign In](/gen1/[platform]/build-a-backend/auth/sign-in-custom-flow/) to learn about how to integrate custom authentication flow in your application with the Auth APIs. - - - - -For more information about working with Lambda Triggers for custom authentication challenges, please visit [Amazon Cognito Developer Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). - - - ---- - ---- -title: "Sign-out" -section: "build-a-backend/auth/connect-your-frontend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-21T15:34:09.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-out/" ---- - -Amplify provides a client library that enables you to interact with backend resources such as Amplify Auth. - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/swift/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/flutter/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - - -> **Info:** The quickest way to get started with Amplify Auth in your frontend application is with the [Authenticator component](https://ui.docs.amplify.aws/android/connected-components/authenticator), which provides a customizable UI and complete authentication flows. - - -To sign a user out of your application use the `signOut` API. - - -```ts -import { signOut } from 'aws-amplify/auth'; - -await signOut(); -``` - - -```dart -Future signOutCurrentUser() async { - final result = await Amplify.Auth.signOut(); - if (result is CognitoCompleteSignOut) { - safePrint('Sign out completed successfully'); - } else if (result is CognitoFailedSignOut) { - safePrint('Error signing user out: ${result.exception.message}'); - } -} -``` - - - -#### [Java] - -```java -Amplify.Auth.signOut( signOutResult -> { - if (signOutResult instanceof AWSCognitoAuthSignOutResult.CompleteSignOut) { - // Sign Out completed fully and without errors. - Log.i("AuthQuickStart", "Signed out successfully"); - } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.PartialSignOut) { - // Sign Out completed with some errors. User is signed out of the device. - AWSCognitoAuthSignOutResult.PartialSignOut partialSignOutResult = - (AWSCognitoAuthSignOutResult.PartialSignOut) signOutResult; - - HostedUIError hostedUIError = partialSignOutResult.getHostedUIError(); - if (hostedUIError != null) { - Log.e("AuthQuickStart", "HostedUI Error", hostedUIError.getException()); - // Optional: Re-launch hostedUIError.getUrl() in a Custom tab to clear Cognito web session. - } - - GlobalSignOutError globalSignOutError = partialSignOutResult.getGlobalSignOutError(); - if (globalSignOutError != null) { - Log.e("AuthQuickStart", "GlobalSignOut Error", globalSignOutError.getException()); - // Optional: Use escape hatch to retry revocation of globalSignOutError.getAccessToken(). - } - - RevokeTokenError revokeTokenError = partialSignOutResult.getRevokeTokenError(); - if (revokeTokenError != null) { - Log.e("AuthQuickStart", "RevokeToken Error", revokeTokenError.getException()); - // Optional: Use escape hatch to retry revocation of revokeTokenError.getRefreshToken(). - } - } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.FailedSignOut) { - AWSCognitoAuthSignOutResult.FailedSignOut failedSignOutResult = - (AWSCognitoAuthSignOutResult.FailedSignOut) signOutResult; - // Sign Out failed with an exception, leaving the user signed in. - Log.e("AuthQuickStart", "Sign out Failed", failedSignOutResult.getException()); - } -}); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.signOut { signOutResult -> - when(signOutResult) { - is AWSCognitoAuthSignOutResult.CompleteSignOut -> { - // Sign Out completed fully and without errors. - Log.i("AuthQuickStart", "Signed out successfully") - } - is AWSCognitoAuthSignOutResult.PartialSignOut -> { - // Sign Out completed with some errors. User is signed out of the device. - signOutResult.hostedUIError?.let { - Log.e("AuthQuickStart", "HostedUI Error", it.exception) - // Optional: Re-launch it.url in a Custom tab to clear Cognito web session. - - } - signOutResult.globalSignOutError?.let { - Log.e("AuthQuickStart", "GlobalSignOut Error", it.exception) - // Optional: Use escape hatch to retry revocation of it.accessToken. - } - signOutResult.revokeTokenError?.let { - Log.e("AuthQuickStart", "RevokeToken Error", it.exception) - // Optional: Use escape hatch to retry revocation of it.refreshToken. - } - } - is AWSCognitoAuthSignOutResult.FailedSignOut -> { - // Sign Out failed with an exception, leaving the user signed in. - Log.e("AuthQuickStart", "Sign out Failed", signOutResult.exception) - } - } -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -val signOutResult = Amplify.Auth.signOut() -when(signOutResult) { - is AWSCognitoAuthSignOutResult.CompleteSignOut -> { - // Sign Out completed fully and without errors. - Log.i("AuthQuickStart", "Signed out successfully") - } - is AWSCognitoAuthSignOutResult.PartialSignOut -> { - // Sign Out completed with some errors. User is signed out of the device. - signOutResult.hostedUIError?.let { - Log.e("AuthQuickStart", "HostedUI Error", it.exception) - // Optional: Re-launch it.url in a Custom tab to clear Cognito web session. - - } - signOutResult.globalSignOutError?.let { - Log.e("AuthQuickStart", "GlobalSignOut Error", it.exception) - // Optional: Use escape hatch to retry revocation of it.accessToken. - } - signOutResult.revokeTokenError?.let { - Log.e("AuthQuickStart", "RevokeToken Error", it.exception) - // Optional: Use escape hatch to retry revocation of it.refreshToken. - } - } - is AWSCognitoAuthSignOutResult.FailedSignOut -> { - // Sign Out failed with an exception, leaving the user signed in. - Log.e("AuthQuickStart", "Sign out Failed", signOutResult.exception) - } -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.signOut() - .subscribe(signOutResult -> { - if (signOutResult instanceof AWSCognitoAuthSignOutResult.CompleteSignOut) { - // Sign Out completed fully and without errors. - Log.i("AuthQuickStart", "Signed out successfully"); - } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.PartialSignOut) { - // Sign Out completed with some errors. User is signed out of the device. - AWSCognitoAuthSignOutResult.PartialSignOut partialSignOutResult = - (AWSCognitoAuthSignOutResult.PartialSignOut) signOutResult; - - HostedUIError hostedUIError = partialSignOutResult.getHostedUIError(); - if (hostedUIError != null) { - Log.e("AuthQuickStart", "HostedUI Error", hostedUIError.getException()); - // Optional: Re-launch hostedUIError.getUrl() in a Custom tab to clear Cognito web session. - } - - GlobalSignOutError globalSignOutError = partialSignOutResult.getGlobalSignOutError(); - if (globalSignOutError != null) { - Log.e("AuthQuickStart", "GlobalSignOut Error", globalSignOutError.getException()); - // Optional: Use escape hatch to retry revocation of globalSignOutError.getAccessToken(). - } - - RevokeTokenError revokeTokenError = partialSignOutResult.getRevokeTokenError(); - if (revokeTokenError != null) { - Log.e("AuthQuickStart", "RevokeToken Error", revokeTokenError.getException()); - // Optional: Use escape hatch to retry revocation of revokeTokenError.getRefreshToken(). - } - } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.FailedSignOut) { - AWSCognitoAuthSignOutResult.FailedSignOut failedSignOutResult = - (AWSCognitoAuthSignOutResult.FailedSignOut) signOutResult; - // Sign Out failed with an exception, leaving the user signed in. - Log.e("AuthQuickStart", "Sign out Failed", failedSignOutResult.getException()); - } - }); -``` - - - - -#### [Async/Await] - -```swift -func signOutLocally() async { - let result = await Amplify.Auth.signOut() - guard let signOutResult = result as? AWSCognitoSignOutResult - else { - print("Signout failed") - return - } - - print("Local signout successful: \(signOutResult.signedOutLocally)") - switch signOutResult { - case .complete: - // Sign Out completed fully and without errors. - print("Signed out successfully") - - case let .partial(revokeTokenError, globalSignOutError, hostedUIError): - // Sign Out completed with some errors. User is signed out of the device. - - if let hostedUIError = hostedUIError { - print("HostedUI error \(String(describing: hostedUIError))") - } - - if let globalSignOutError = globalSignOutError { - // Optional: Use escape hatch to retry revocation of globalSignOutError.accessToken. - print("GlobalSignOut error \(String(describing: globalSignOutError))") - } - - if let revokeTokenError = revokeTokenError { - // Optional: Use escape hatch to retry revocation of revokeTokenError.accessToken. - print("Revoke token error \(String(describing: revokeTokenError))") - } - - case .failed(let error): - // Sign Out failed with an exception, leaving the user signed in. - print("SignOut failed with \(error)") - } -} -``` - -#### [Combine] - -```swift -func signOutLocally() -> AnyCancellable { - Amplify.Publisher.create { - await Amplify.Auth.signOut() - }.sink(receiveValue: { result in - guard let signOutResult = result as? AWSCognitoSignOutResult - else { - print("Signout failed") - return - } - print("Local signout successful: \(signOutResult.signedOutLocally)") - switch signOutResult { - case .complete: - // Sign Out completed fully and without errors. - print("Signed out successfully") - - case let .partial(revokeTokenError, globalSignOutError, hostedUIError): - // Sign Out completed with some errors. User is signed out of the device. - if let hostedUIError = hostedUIError { - print("HostedUI error \(String(describing: hostedUIError))") - } - - if let globalSignOutError = globalSignOutError { - // Optional: Use escape hatch to retry revocation of globalSignOutError.accessToken. - print("GlobalSignOut error \(String(describing: globalSignOutError))") - } - - if let revokeTokenError = revokeTokenError { - // Optional: Use escape hatch to retry revocation of revokeTokenError.accessToken. - print("Revoke token error \(String(describing: revokeTokenError))") - } - - case .failed(let error): - // Sign Out failed with an exception, leaving the user signed in. - print("SignOut failed with \(error)") - } - }) -} -``` - - - -You can also sign out users from all devices by performing a global sign-out. This will also invalidate all refresh tokens issued to a user. The user's current access and ID tokens will remain valid on other devices until the refresh token expires (access and ID tokens expire one hour after they are issued). - - -```ts -import { signOut } from 'aws-amplify/auth'; - -await signOut({ global: true }); -``` - - -```dart -Future signOutGlobally() async { - final result = await Amplify.Auth.signOut( - options: const SignOutOptions(globalSignOut: true), - ); - if (result is CognitoCompleteSignOut) { - safePrint('Sign out completed successfully'); - } else if (result is CognitoPartialSignOut) { - final globalSignOutException = result.globalSignOutException!; - final accessToken = globalSignOutException.accessToken; - // Retry the global sign out using the access token, if desired - // ... - safePrint('Error signing user out: ${globalSignOutException.message}'); - } else if (result is CognitoFailedSignOut) { - safePrint('Error signing user out: ${result.exception.message}'); - } -} -``` - - - -#### [Java] - -```java -AuthSignOutOptions options = AuthSignOutOptions.builder() - .globalSignOut(true) - .build(); - -Amplify.Auth.signOut(options, signOutResult -> { - if (signOutResult instanceof AWSCognitoAuthSignOutResult.CompleteSignOut) { - // handle successful sign out - } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.PartialSignOut) { - // handle partial sign out - } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.FailedSignOut) { - // handle failed sign out - } -}); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val options = AuthSignOutOptions.builder() - .globalSignOut(true) - .build() - -Amplify.Auth.signOut(options) { signOutResult -> - when(signOutResult) { - is AWSCognitoAuthSignOutResult.CompleteSignOut -> { - // handle successful sign out - } - is AWSCognitoAuthSignOutResult.PartialSignOut -> { - // handle partial sign out - } - is AWSCognitoAuthSignOutResult.FailedSignOut -> { - // handle failed sign out - } - } -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -val options = AuthSignOutOptions.builder() - .globalSignOut(true) - .build() - -val signOutResult = Amplify.Auth.signOut(options) - -when(signOutResult) { - is AWSCognitoAuthSignOutResult.CompleteSignOut -> { - // handle successful sign out - } - is AWSCognitoAuthSignOutResult.PartialSignOut -> { - // handle partial sign out - } - is AWSCognitoAuthSignOutResult.FailedSignOut -> { - // handle failed sign out - } -} -``` - -#### [RxJava] - -```java -AuthSignOutOptions options = AuthSignOutOptions.builder() - .globalSignOut(true) - .build(); - -RxAmplify.Auth.signOut(options) - .subscribe(signOutResult -> { - if (signOutResult instanceof AWSCognitoAuthSignOutResult.CompleteSignOut) { - // handle successful sign out - } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.PartialSignOut) { - // handle partial sign out - } else if (signOutResult instanceof AWSCognitoAuthSignOutResult.FailedSignOut) { - // handle failed sign out - } - }); -``` - - - - -#### [Async/Await] - -```swift -import AWSCognitoAuthPlugin - -func signOutGlobally() async { - let result = await Amplify.Auth.signOut(options: .init(globalSignOut: true)) - guard let signOutResult = result as? AWSCognitoSignOutResult - else { - print("Signout failed") - return - } - - print("Local signout successful: \(signOutResult.signedOutLocally)") - switch signOutResult { - case .complete: - // handle successful sign out - case .failed(let error): - // handle failed sign out - case let .partial(revokeTokenError, globalSignOutError, hostedUIError): - // handle partial sign out - } -} -``` - -#### [Combine] - -```swift -func signOutGlobally() -> AnyCancellable { - Amplify.Publisher.create { - await Amplify.Auth.signOut(options: .init(globalSignOut: true)) - }.sink(receiveValue: { result in - guard let signOutResult = result as? AWSCognitoSignOutResult - else { - print("Signout failed") - return - } - print("Local signout successful: \(signOutResult.signedOutLocally)") - switch signOutResult { - case .complete: - // handle successful sign out - case .failed(let error): - // handle failed sign out - case let .partial(revokeTokenError, globalSignOutError, hostedUIError): - // handle partial sign out - } - }) -} -``` - - - - -## Practical Example - - -```tsx title="src/App.tsx" -import { Amplify } from "aws-amplify" -// highlight-next-line -import { signOut } from "aws-amplify/auth" -import outputs from "../amplify_outputs.json" - -Amplify.configure(outputs) - -export default function App() { - async function handleSignOut() { - // highlight-next-line - await signOut() - } - - return ( - - ) -} -``` - - - ---- - ---- -title: "Manage user sessions" -section: "build-a-backend/auth/connect-your-frontend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-10-25T16:39:44.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/manage-user-sessions/" ---- - -Amplify Auth provides access to current user sessions and tokens to help you retrieve your user's information to determine if they are signed in with a valid session and control their access to your app. - - -## Retrieve your current authenticated user - -You can use the `getCurrentUser` API to get information about the currently authenticated user including the `username`, `userId` and `signInDetails`. - -```ts -import { getCurrentUser } from 'aws-amplify/auth'; - -const { username, userId, signInDetails } = await getCurrentUser(); - -console.log("username", username); -console.log("user id", userId); -console.log("sign-in details", signInDetails); -``` - -This method can be used to check if a user is signed in. It throws an error if the user is not authenticated. - -> **Info:** The user's `signInDetails` are not supported when using the `Hosted UI` or the `signInWithRedirect` API. - -## Retrieve a user session - -Your user's session is their signed-in state, which grants them access to your app. When your users sign in, their credentials are exchanged for temporary access tokens. You can get session details to access these tokens and use this information to validate user access or perform actions unique to that user. - -If you only need the session details, you can use the `fetchAuthSession` API which returns a `tokens` object containing the JSON Web Tokens (JWT). - -```ts -import { fetchAuthSession } from 'aws-amplify/auth'; - -const session = await fetchAuthSession(); - -console.log("id token", session.tokens.idToken) -console.log("access token", session.tokens.accessToken) -``` - -### Refreshing sessions - -The `fetchAuthSession` API automatically refreshes the user's session when the authentication tokens have expired and a valid `refreshToken` is present. Additionally, you can also refresh the session explicitly by calling the `fetchAuthSession` API with the `forceRefresh` flag enabled. - -```ts -import { fetchAuthSession } from 'aws-amplify/auth'; - -await fetchAuthSession({ forceRefresh: true }); -``` - -> **Warning:** **Warning:** by default, sessions from external identity providers cannot be refreshed. - - -An intentional decision with Amplify Auth was to avoid any public methods exposing credentials or manipulating them. - -With Auth, you simply sign in and it handles everything else needed to keep the credentials up to date and vend them to the other categories. - -However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation details by calling `fetchAuthSession` on the Cognito Auth Plugin. This will return a `CognitoAuthSession`, which has additional attributes compared to `AuthSession`, which is typically returned by `fetchAuthSession`. See the example below: - -```dart -Future fetchAuthSession() async { - try { - final result = await Amplify.Auth.fetchAuthSession(); - safePrint('User is signed in: ${result.isSignedIn}'); - } on AuthException catch (e) { - safePrint('Error retrieving auth session: ${e.message}'); - } -} -``` - -## Retrieving AWS credentials - -Sometimes it can be helpful to retrieve the instance of the underlying plugin which has more specific typing. In case of Cognito, calling `fetchAuthSession` on the Cognito plugin returns AWS-specific values such as the identity ID, AWS credentials, and Cognito User Pool tokens. - -```dart -Future fetchCognitoAuthSession() async { - try { - final cognitoPlugin = Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); - final result = await cognitoPlugin.fetchAuthSession(); - final identityId = result.identityIdResult.value; - safePrint("Current user's identity ID: $identityId"); - } on AuthException catch (e) { - safePrint('Error retrieving auth session: ${e.message}'); - } -} -``` - - -An intentional decision with Amplify Auth was to avoid any public methods exposing credentials or manipulating them. - -With Auth, you simply sign in and it handles everything else needed to keep the credentials up to date and vend them to the other categories. - -However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation details by casting the result of `fetchAuthSession` as follows: - -#### [Java] - -```java -Amplify.Auth.fetchAuthSession( - result -> { - AWSCognitoAuthSession cognitoAuthSession = (AWSCognitoAuthSession) result; - switch(cognitoAuthSession.getIdentityIdResult().getType()) { - case SUCCESS: - Log.i("AuthQuickStart", "IdentityId: " + cognitoAuthSession.getIdentityIdResult().getValue()); - break; - case FAILURE: - Log.i("AuthQuickStart", "IdentityId not present because: " + cognitoAuthSession.getIdentityIdResult().getError().toString()); - } - }, - error -> Log.e("AuthQuickStart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.fetchAuthSession( - { - val session = it as AWSCognitoAuthSession - when (session.identityIdResult.type) { - AuthSessionResult.Type.SUCCESS -> - Log.i("AuthQuickStart", "IdentityId = ${session.identityIdResult.value}") - AuthSessionResult.Type.FAILURE -> - Log.w("AuthQuickStart", "IdentityId not found", session.identityIdResult.error) - } - }, - { Log.e("AuthQuickStart", "Failed to fetch session", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val session = Amplify.Auth.fetchAuthSession() as AWSCognitoAuthSession - val id = session.identityIdResult - if (id.type == AuthSessionResult.Type.SUCCESS) { - Log.i("AuthQuickStart", "IdentityId: ${id.value}") - } else if (id.type == AuthSessionResult.Type.FAILURE) { - Log.i("AuthQuickStart", "IdentityId not present: ${id.error}") - } -} catch (error: AuthException) { - Log.e("AuthQuickStart", "Failed to fetch session", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.fetchAuthSession() - .subscribe( - result -> { - AWSCognitoAuthSession cognitoAuthSession = (AWSCognitoAuthSession) result; - - switch (cognitoAuthSession.getIdentityIdResult().getType()) { - case SUCCESS: - Log.i("AuthQuickStart", "IdentityId: " + cognitoAuthSession.getIdentityIdResult().getValue()); - break; - case FAILURE: - Log.i("AuthQuickStart", "IdentityId not present because: " + cognitoAuthSession.getIdentityIdResult().getError().toString()); - } - }, - error -> Log.e("AuthQuickStart", error.toString()) - ); -``` - -## Force refreshing session - -Through the plugin, you can force refresh the internal session by setting the `forceRefresh` option when calling the fetchAuthSession API. - -#### [Java] - -```java -AuthFetchSessionOptions options = AuthFetchSessionOptions.builder().forceRefresh(true).build(); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val option = AuthFetchSessionOptions.builder().forceRefresh(true).build() -``` - -#### [Kotlin - Coroutines] - -```kotlin -val option = AuthFetchSessionOptions.builder().forceRefresh(true).build() -``` - -#### [RxJava] - -```java -AuthFetchSessionOptions options = AuthFetchSessionOptions.builder().forceRefresh(true).build(); -``` - - - -An intentional decision with Amplify Auth was to avoid any public methods exposing credentials or manipulating them. - -With Auth, you simply sign in and it handles everything else needed to keep the credentials up to date and vend them to the other categories. - -However, if you need to access them in relation to working with an API outside Amplify or want access to AWS specific identifying information (e.g. IdentityId), you can access these implementation details by casting the result of `fetchAuthSession` as follows: - -```swift -import AWSPluginsCore - -do { - let session = try await Amplify.Auth.fetchAuthSession() - - // Get user sub or identity id - if let identityProvider = session as? AuthCognitoIdentityProvider { - let usersub = try identityProvider.getUserSub().get() - let identityId = try identityProvider.getIdentityId().get() - print("User sub - \(usersub) and identity id \(identityId)") - } - - // Get AWS credentials - if let awsCredentialsProvider = session as? AuthAWSCredentialsProvider { - let credentials = try awsCredentialsProvider.getAWSCredentials().get() - // Do something with the credentials - } - - // Get cognito user pool token - if let cognitoTokenProvider = session as? AuthCognitoTokensProvider { - let tokens = try cognitoTokenProvider.getCognitoTokens().get() - // Do something with the JWT tokens - } -} catch let error as AuthError { - print("Fetch auth session failed with error - \(error)") -} catch { -} -``` -If you have enabled guest user in Cognito Identity Pool and no user is signed in, you will be able to access only identityId and AWS credentials. All other session details will give you an error. - -```swift -import AWSPluginsCore - -do { - let session = try await Amplify.Auth.fetchAuthSession() - - // Get identity id - if let identityProvider = session as? AuthCognitoIdentityProvider { - let identityId = try identityProvider.getIdentityId().get() - print("Identity id \(identityId)") - } - - // Get AWS credentials - if let awsCredentialsProvider = session as? AuthAWSCredentialsProvider { - let credentials = try awsCredentialsProvider.getAWSCredentials().get() - // Do something with the credentials - } -} catch let error as AuthError { - print("Fetch auth session failed with error - \(error)") -} catch { - print("Unexpected error: \(error)") -} -``` - -## Force refreshing session - -Through the plugin, you can force refresh the internal session by passing an api options `forceRefresh` while calling the fetchAuthSession api. - -```swift -Amplify.Auth.fetchAuthSession(options: .forceRefresh()) - -``` - - ---- - ---- -title: "Manage user attributes" -section: "build-a-backend/auth/connect-your-frontend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-11-14T19:12:03.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/manage-user-attributes/" ---- - -User attributes such as email address, phone number help you identify individual users. Defining the user attributes you include for your user profiles makes user data easy to manage at scale. This information will help you personalize user journeys, tailor content, provide intuitive account control, and more. You can capture information upfront during sign-up or enable customers to update their profile after sign-up. In this section we take a closer look at working with user attributes, how to set them up and manage them. - - -## Pass user attributes during sign-up - -You can create user attributes during sign-up or when the user is authenticated. To do this as part of sign-up you can pass them in the `userAttributes` object of the `signUp` API: - -```ts -import { signUp } from "aws-amplify/auth"; - -await signUp({ - username: "jdoe", - password: "mysecurerandompassword#123", - options: { - userAttributes: { - email: "me@domain.com", - phone_number: "+12128601234", // E.164 number convention - given_name: "Jane", - family_name: "Doe", - nickname: "Jane", - }, - }, -}); -``` - - - -## Configure custom user attributes during sign-up - -Custom attributes can be passed in with the `userAttributes` option of the `signUp` API: - - -```ts -import { signUp } from "aws-amplify/auth"; - -await signUp({ - username: 'john.doe@example.com', - password: 'hunter2', - options: { - userAttributes: { - 'custom:display_name': 'john_doe123', - } - } -}); -``` - - -```dart -Future _signUp({ - required String username, - required String password, - required String email, - required String customValue, -}) async { - final userAttributes = { - AuthUserAttributeKey.email: email, - // Create and pass a custom attribute - const CognitoUserAttributeKey.custom('my-custom-attribute'): customValue - }; - await Amplify.Auth.signUp( - username: username, - password: password, - options: SignUpOptions( - userAttributes: userAttributes, - ), - ); -} -``` - - -```swift -func signUp(username: String, password: String, email: String) async { - do { - let signUpResult = try await Amplify.Auth.signUp( - username: username, - password: password, - options: .init(userAttributes: [ - AuthUserAttribute(.email, value: email), - AuthUserAttribute(.custom("my-custom-attribute"), value: ) - ]) - ) - if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { - print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") - } else { - print("SignUp Complete") - } - } catch let error as AuthError { - print("An error occurred while registering a user \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - - - -## Retrieve user attributes - -You can retrieve user attributes for your users to read in their profile using the `fetchUserAttributes` API. This helps you personalize their frontend experience as well as control what they will see. - - -```ts -import { fetchUserAttributes } from 'aws-amplify/auth'; - -await fetchUserAttributes(); -``` - - - -#### [Async/Await] - -```swift -func fetchAttributes() async { - do { - let attributes = try await Amplify.Auth.fetchUserAttributes() - print("User attributes - \(attributes)") - } catch let error as AuthError{ - print("Fetching user attributes failed with error \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func fetchAttributes() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.fetchUserAttributes() - }.sink { - if case let .failure(authError) = $0 { - print("Fetch user attributes failed with error \(authError)") - } - } - receiveValue: { attributes in - print("User attributes - \(attributes)") - } -} -``` - - - - -#### [Java] - -```java -Amplify.Auth.fetchUserAttributes( - attributes -> Log.i("AuthDemo", "User attributes = " + attributes.toString()), - error -> Log.e("AuthDemo", "Failed to fetch user attributes.", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.fetchUserAttributes( - { Log.i("AuthDemo", "User attributes = $it") }, - { Log.e("AuthDemo", "Failed to fetch user attributes", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val attributes = Amplify.Auth.fetchUserAttributes() - Log.i("AuthDemo", "User attributes = $attributes") -} catch (error: AuthException) { - Log.e("AuthDemo", "Failed to fetch user attributes", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.fetchUserAttributes() - .doOnSubscribe(() -> Log.i("AuthDemo", "Attributes:")) - .flatMapObservable(Observable::fromIterable) - .subscribe( - eachAttribute -> Log.i("AuthDemo", eachAttribute.toString()), - error -> Log.e("AuthDemo", "Failed to fetch attributes.", error) - ); -``` - - - -```dart -Future fetchCurrentUserAttributes() async { - try { - final result = await Amplify.Auth.fetchUserAttributes(); - for (final element in result) { - safePrint('key: ${element.userAttributeKey}; value: ${element.value}'); - } - } on AuthException catch (e) { - safePrint('Error fetching user attributes: ${e.message}'); - } -} -``` - - -## Update user attribute - -You can use the `updateUserAttribute` API to create or update existing user attributes. - - - -#### [TypeScript] - -```typescript -import { - updateUserAttribute, - type UpdateUserAttributeOutput -} from 'aws-amplify/auth'; - -async function handleUpdateUserAttribute(attributeKey: string, value: string) { - try { - const output = await updateUserAttribute({ - userAttribute: { - attributeKey, - value - } - }); - handleUpdateUserAttributeNextSteps(output); - } catch (error) { - console.log(error); - } -} - -function handleUpdateUserAttributeNextSteps(output: UpdateUserAttributeOutput) { - const { nextStep } = output; - - switch (nextStep.updateAttributeStep) { - case 'CONFIRM_ATTRIBUTE_WITH_CODE': - const codeDeliveryDetails = nextStep.codeDeliveryDetails; - console.log( - `Confirmation code was sent to ${codeDeliveryDetails?.deliveryMedium}.` - ); - // Collect the confirmation code from the user and pass to confirmUserAttribute. - break; - case 'DONE': - console.log(`attribute was successfully updated.`); - break; - } -} -``` - -#### [JavaScript] - -```javascript -import { updateUserAttribute } from 'aws-amplify/auth'; - -async function handleUpdateUserAttribute(attributeKey, value) { - try { - const output = await updateUserAttribute({ - userAttribute: { - attributeKey, - value - } - }); - handleUpdateUserAttributeNextSteps(output); - } catch (error) { - console.log(error); - } -} - -function handleUpdateUserAttributeNextSteps(output) { - const { nextStep } = output; - - switch (nextStep.updateAttributeStep) { - case 'CONFIRM_ATTRIBUTE_WITH_CODE': - const codeDeliveryDetails = nextStep.codeDeliveryDetails; - console.log( - `Confirmation code was sent to ${codeDeliveryDetails?.deliveryMedium}.` - ); - // Collect the confirmation code from the user and pass to confirmUserAttribute. - break; - case 'DONE': - console.log(`attribute was successfully updated.`); - break; - } -} -``` - - - Note: If you change an attribute that requires confirmation (i.e. email or phone_number), the user will receive a confirmation code either to their email or cellphone. This code can be used with the confirmUserAttribute API to confirm the change. - - - - - -#### [Async/Await] - -```swift -func updateAttribute() async { - do { - let updateResult = try await Amplify.Auth.update( - userAttribute: AuthUserAttribute(.phoneNumber, value: "+2223334444") - ) - - switch updateResult.nextStep { - case .confirmAttributeWithCode(let deliveryDetails, let info): - print("Confirm the attribute with details send to - \(deliveryDetails) \(String(describing: info))") - case .done: - print("Update completed") - } - } catch let error as AuthError { - print("Update attribute failed with error \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func updateAttribute() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.update( - userAttribute: AuthUserAttribute(.phoneNumber, value: "+2223334444") - ) - }.sink { - if case let .failure(authError) = $0 { - print("Update attribute failed with error \(authError)") - } - } - receiveValue: { updateResult in - switch updateResult.nextStep { - case .confirmAttributeWithCode(let deliveryDetails, let info): - print("Confirm the attribute with details send to - \(deliveryDetails) \(info)") - case .done: - print("Update completed") - } - } -} -``` - - - - - -#### [Java] - -```java -AuthUserAttribute userEmail = - new AuthUserAttribute(AuthUserAttributeKey.email(), "email@email.com"); -Amplify.Auth.updateUserAttribute(userEmail, - result -> Log.i("AuthDemo", "Updated user attribute = " + result.toString()), - error -> Log.e("AuthDemo", "Failed to update user attribute.", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.updateUserAttribute( - AuthUserAttribute(AuthUserAttributeKey.email(), "email@email.com"), - { Log.i("AuthDemo", "Updated user attribute = $it") }, - { Log.e("AuthDemo", "Failed to update user attribute.", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val attribute = - AuthUserAttribute(AuthUserAttributeKey.email(), "email@email.com") -try { - val result = Amplify.Auth.updateUserAttribute(attribute) - Log.i("AuthDemo", "Updated user attribute = $result") -} catch (error: AuthException) { - Log.e("AuthDemo", "Failed to update user attribute.", error) -} -``` - -#### [RxJava] - -```java -AuthUserAttribute userEmail = - new AuthUserAttribute(AuthUserAttributeKey.email(), "email@email.com"); -RxAmplify.Auth.updateUserAttribute(userEmail) - .subscribe( - result -> Log.i("AuthDemo", "Updated user attribute = " + result.toString()), - error -> Log.e("AuthDemo", "Failed to update user attribute.", error) - ); -``` - - - - -```dart -Future updateUserEmail({ - required String newEmail, -}) async { - try { - final result = await Amplify.Auth.updateUserAttribute( - userAttributeKey: AuthUserAttributeKey.email, - value: newEmail, - ); - _handleUpdateUserAttributeResult(result); - } on AuthException catch (e) { - safePrint('Error updating user attribute: ${e.message}'); - } -} -``` - -User attribute updates may require additional verification before they're complete. Check the -`UpdateUserAttributeResult` returned from `Amplify.Auth.updateUserAttribute` to see which next -step, if any, is required. When the update is complete, the next step will be `done`. - -```dart -void _handleUpdateUserAttributeResult( - UpdateUserAttributeResult result, -) { - switch (result.nextStep.updateAttributeStep) { - case AuthUpdateAttributeStep.confirmAttributeWithCode: - final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; - _handleCodeDelivery(codeDeliveryDetails); - break; - case AuthUpdateAttributeStep.done: - safePrint('Successfully updated attribute'); - break; - } -} - -void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { - safePrint( - 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' - 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', - ); -} -``` - -To update multiple user attributes at a time, call `updateUserAttributes`: - -```dart -Future updateUserAttributes() async { - const attributes = [ - AuthUserAttribute( - userAttributeKey: AuthUserAttributeKey.email, - value: 'email@email.com', - ), - AuthUserAttribute( - userAttributeKey: AuthUserAttributeKey.familyName, - value: 'MyFamilyName', - ), - ]; - try { - final result = await Amplify.Auth.updateUserAttributes( - attributes: attributes, - ); - result.forEach((key, value) { - switch (value.nextStep.updateAttributeStep) { - case AuthUpdateAttributeStep.confirmAttributeWithCode: - final destination = value.nextStep.codeDeliveryDetails?.destination; - safePrint('Confirmation code sent to $destination for $key'); - break; - case AuthUpdateAttributeStep.done: - safePrint('Update completed for $key'); - break; - } - }); - } on AuthException catch (e) { - safePrint('Error updating user attributes: ${e.message}'); - } -} -``` - - - -## Update user attributes - -You can use the `updateUserAttributes` API to create or update multiple existing user attributes. - - -```typescript -import { updateUserAttributes, type UpdateUserAttributesOutput } from "aws-amplify/auth"; - -await updateUserAttributes({ - userAttributes: { - email: "me@domain.com", - name: "Jon Doe", - }, -}); -``` - - - - -#### [Java] - -```java -Amplify.Auth.updateUserAttributes( - attributes, // attributes is a list of AuthUserAttribute - result -> Log.i("AuthDemo", "Updated user attributes = " + result.toString()), - error -> Log.e("AuthDemo", "Failed to update user attributes.", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.updateUserAttributes( - attributes, // attributes is a list of AuthUserAttribute - { Log.i("AuthDemo", "Updated user attributes = $it") }, - { Log.e("AuthDemo", "Failed to update user attributes", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.updateUserAttributes(attributes) - Log.i("AuthDemo", "Updated user attributes = $result") -} catch (error: AuthException) { - Log.e("AuthDemo", "Failed to update user attributes", error) -} -``` - -#### [RxJava] - -```java -// attributes is a list of AuthUserAttribute -RxAmplify.Auth.updateUserAttributes(attributes) - .subscribe( - result -> Log.i("AuthDemo", "Updated user attributes = " + result.toString()), - error -> Log.e("AuthDemo", "Failed to update user attributes.", error) - ); -``` - - - - -## Verify user attribute - - -Some attributes require confirmation for the attribute update to complete. If the attribute needs to be confirmed, part of the result of the `updateUserAttribute` or `updateUserAttributes` APIs will be `CONFIRM_ATTRIBUTE_WITH_CODE`. A confirmation code will be sent to the delivery medium mentioned in the delivery details. When the user gets the confirmation code, you can present a UI to the user to enter the code and invoke the `confirmUserAttribute` API with their input: - - - -Some attributes require confirmation for the attribute update to complete. If the attribute needs to be confirmed, part of the result of the `updateUserAttribute` or `updateUserAttributes` APIs will be `confirmAttributeWithCode`. A confirmation code will be sent to the delivery medium mentioned in the delivery details. When the user gets the confirmation code, you can present a UI to the user to enter the code and invoke the `confirmUserAttribute` API with their input: - - - -```typescript -import { - confirmUserAttribute, - type ConfirmUserAttributeInput -} from 'aws-amplify/auth'; - -async function handleConfirmUserAttribute({ - userAttributeKey, - confirmationCode -}: ConfirmUserAttributeInput) { - try { - await confirmUserAttribute({ userAttributeKey, confirmationCode }); - } catch (error) { - console.log(error); - } -} -``` - - - - -#### [Async/Await] - -```swift -func confirmAttribute() async { - do { - try await Amplify.Auth.confirm(userAttribute: .email, confirmationCode: "390739") - print("Attribute verified") - } catch let error as AuthError { - print("Update attribute failed with error \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmAttribute() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirm(userAttribute: .email, confirmationCode: "390739") - }.sink { - if case let .failure(authError) = $0 { - print("Update attribute failed with error \(authError)") - } - } - receiveValue: { _ in - print("Attribute verified") - } -} -``` - - - - - -#### [Java] - -```java -Amplify.Auth.confirmUserAttribute(AuthUserAttributeKey.email(), "344299", - () -> Log.i("AuthDemo", "Confirmed user attribute with correct code."), - error -> Log.e("AuthDemo", "Failed to confirm user attribute. Bad code?", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.confirmUserAttribute(AuthUserAttributeKey.email(), "344299", - { Log.i("AuthDemo", "Confirmed user attribute with correct code.") }, - { Log.e("AuthDemo", "Failed to confirm user attribute. Bad code?", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - Amplify.Auth.confirmUserAttribute(AuthUserAttributeKey.email(), "344299") - Log.i("AuthDemo", "Confirmed user attribute with correct code.") -} catch (error: AuthException) { - Log.e("AuthDemo", "Failed to confirm user attribute. Bade code?", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.confirmUserAttribute(AuthUserAttributeKey.email(), "344299") - .subscribe( - () -> Log.i("AuthDemo", "Confirmed user attribute using correct code."), - error -> Log.e("AuthDemo", "Failed to confirm user attribute. Bad code?", error) - ); -``` - - - - -```dart -Future verifyAttributeUpdate() async { - try { - await Amplify.Auth.confirmUserAttribute( - userAttributeKey: AuthUserAttributeKey.email, - confirmationCode: '390739', - ); - } on AuthException catch (e) { - safePrint('Error confirming attribute update: ${e.message}'); - } -} -``` - - -## Send user attribute verification code - -If an attribute needs to be verified while the user is authenticated, invoke the `sendUserAttributeVerificationCode` API as shown below: - - -```ts -import { - sendUserAttributeVerificationCode, - type VerifiableUserAttributeKey -} from 'aws-amplify/auth'; - -async function handleSendUserAttributeVerificationCode( - key: VerifiableUserAttributeKey -) { - try { - await sendUserAttributeVerificationCode({ - userAttributeKey: key - }); - } catch (error) { - console.log(error); - } -} -``` - - - - -#### [Async/Await] - -```swift -func sendVerificationCode() async { - do { - let deliveryDetails = try await Amplify.Auth.sendVerificationCode(forUserAttributeKey: .email) - print("Resend code send to - \(deliveryDetails)") - } catch let error as AuthError { - print("Resend code failed with error \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func sendVerificationCode() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.sendVerificationCode(forUserAttributeKey: .email) - }.sink { - if case let .failure(authError) = $0 { - print("Resend code failed with error \(authError)") - } - } - receiveValue: { deliveryDetails in - print("Resend code sent to - \(deliveryDetails)") - } -} -``` - - - - - -#### [Java] - -```java -Amplify.Auth.resendUserAttributeConfirmationCode(AuthUserAttributeKey.email(), - result -> Log.i("AuthDemo", "Code was sent again: " + result.toString()), - error -> Log.e("AuthDemo", "Failed to resend code.", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.resendUserAttributeConfirmationCode( - AuthUserAttributeKey.email(), - { Log.i("AuthDemo", "Code was sent again: $it") }, - { Log.e("AuthDemo", "Failed to resend code", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val attr = AuthUserAttributeKey.email() - val result = Amplify.Auth.resendUserAttributeConfirmationCode(attr) - Log.i("AuthDemo", "Code was sent again: $result."), -} catch (error: AuthException) { - Log.e("AuthDemo", "Failed to resend code.", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.resendUserAttributeConfirmationCode(AuthUserAttributeKey.email()) - .subscribe( - result -> Log.i("AuthDemo", "Code was resent: " + result.toString()), - error -> Log.e("AuthDemo", "Failed to resend code.", error) - ); -``` - - - - -```dart -Future resendVerificationCode() async { - try { - final result = await Amplify.Auth.resendUserAttributeConfirmationCode( - userAttributeKey: AuthUserAttributeKey.email, - ); - _handleCodeDelivery(result.codeDeliveryDetails); - } on AuthException catch (e) { - safePrint('Error resending code: ${e.message}'); - } -} -``` - - - -## Delete user attributes - -The `deleteUserAttributes` API allows to delete one or more user attributes. - -```ts -import { - deleteUserAttributes, - type DeleteUserAttributesInput -} from 'aws-amplify/auth'; - -async function handleDeleteUserAttributes( - keys: DeleteUserAttributesInput['userAttributeKeys'] -) { - try { - await deleteUserAttributes({ - userAttributeKeys: ['custom:my_custom_attribute', ...keys] - }); - } catch (error) { - console.log(error); - } -} -``` - - -## Next Steps - -- [Learn how to set up password change and recovery](/[platform]/build-a-backend/auth/manage-users/manage-passwords/) -- [Learn how to set up custom attributes](/[platform]/build-a-backend/auth/concepts/user-attributes#custom-attributes) - ---- - ---- -title: "Listen to auth events" -section: "build-a-backend/auth/connect-your-frontend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-16T15:59:30.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/listen-to-auth-events/" ---- - -Amplify Auth emits events during authentication flows, which enables you to react to user flows in real time and trigger custom business logic. For example, you may want to capture data, synchronize your app's state, and personalize the user's experience. You can listen to and respond to events across the Auth lifecycle such as sign-in and sign-out. - - -## Expose hub events triggered in response to auth actions - -You can use Amplify Hub with its built in Amplify Auth events to subscribe a listener using a publish-subscribe pattern and capture events between different parts of your application. The Amplify Auth category publishes in the `auth` channel when auth events such as `signedIn` or `signedOut` happen independent from your app code. - -You can review the Amplify Hub guide to [learn more](/gen1/react/build-a-backend/utilities/hub/). - -> **Warning:** Channels are logical group names that help you organize dispatching and listening. However, some channels are protected and cannot be used to publish custom events, and `auth` is one of these channels. Sending unexpected payloads to protected channels can have undesirable side effects such as impacting authentication flows. See the [Amplify Hub](/gen1/react/build-a-backend/utilities/hub/) guide for more protected channels. - -Here is a basic example of setting up a listener that logs an event emitted through the `auth` channel: - -```js -import { Hub } from 'aws-amplify/utils'; - -Hub.listen('auth', (data) => { - console.log(data) -}); -``` - -Once your app is set up to subscribe and listen to specific event types from the `auth` channel, the listeners will be notified asynchronously when an event occurs. This pattern allows for a one-to-many relationship where one auth event can be shared with many different listeners that have been subscribed. This lets your app react based on the event rather than proactively poll for information. - -Additionally, you can set up your listener to extract data from the event payload and execute a callback that you define. For example, you might update UI elements in your app to reflect your user's authenticated state after the `signedIn` or `signedOut` events. - -### Listen to and log auth events - -One of the most common workflows will be to log events. In this example you can see how you can listen and target specific `auth` events using a `switch` to log your own messages. - -```js -import { Hub } from 'aws-amplify/utils'; - -Hub.listen('auth', ({ payload }) => { - switch (payload.event) { - case 'signedIn': - console.log('user have been signedIn successfully.'); - break; - case 'signedOut': - console.log('user have been signedOut successfully.'); - break; - case 'tokenRefresh': - console.log('auth tokens have been refreshed.'); - break; - case 'tokenRefresh_failure': - console.log('failure while refreshing auth tokens.'); - break; - case 'signInWithRedirect': - console.log('signInWithRedirect API has successfully been resolved.'); - break; - case 'signInWithRedirect_failure': - console.log('failure while trying to resolve signInWithRedirect API.'); - break; - case 'customOAuthState': - logger.info('custom state returned from CognitoHosted UI'); - break; - } -}); -``` - -### Stop listening to events - -You can also stop listening for messages by calling the result of the `Hub.listen()` function. This may be useful if you no longer need to receive messages in your application flow. This can also help you avoid any memory leaks on low powered devices when you are sending large amounts of data through Amplify Hub on multiple channels. - -To stop listening to a certain event, you need to wrap the listener function with a variable and call it once you no longer need it: - -```js -/* start listening for messages */ -const hubListenerCancelToken = Hub.listen('auth', (data) => { - console.log('Listening for all auth events: ', data.payload.data); -}); - -/* later */ -hubListenerCancelToken(); // stop listening for messages -``` - -You now have a few use cases and examples for listening to and responding to auth events. - - -AWS Cognito Auth Plugin sends important events through Amplify Hub. You can listen to these events like the following: - -```dart -final subscription = Amplify.Hub.listen(HubChannel.Auth, (AuthHubEvent event) { - switch (event.type) { - case AuthHubEventType.signedIn: - safePrint('User is signed in.'); - break; - case AuthHubEventType.signedOut: - safePrint('User is signed out.'); - break; - case AuthHubEventType.sessionExpired: - safePrint('The session has expired.'); - break; - case AuthHubEventType.userDeleted: - safePrint('The user has been deleted.'); - break; - } -}); -``` - - -AWS Cognito Auth Plugin sends important events through Amplify Hub. You can listen to these events like the following: - -#### [Java] - -```java -Amplify.Hub.subscribe(HubChannel.AUTH, - hubEvent -> { - if (hubEvent.getName().equals(InitializationStatus.SUCCEEDED.name())) { - Log.i("AuthQuickstart", "Auth successfully initialized"); - } else if (hubEvent.getName().equals(InitializationStatus.FAILED.name())){ - Log.i("AuthQuickstart", "Auth failed to succeed"); - } else { - String eventName = hubEvent.getName(); - if (eventName.equals(SIGNED_IN.name())) { - Log.i("AuthQuickstart", "Auth just became signed in."); - } - else if (eventName.equals(SIGNED_OUT.name())) { - Log.i("AuthQuickstart", "Auth just became signed out."); - } - else if (eventName.equals(SESSION_EXPIRED.name())) { - Log.i("AuthQuickstart", "Auth session just expired."); - } - else if (eventName.equals(USER_DELETED.name())) { - Log.i("AuthQuickstart", "User has been deleted."); - } - else { - Log.w("AuthQuickstart", "Unhandled Auth Event: " + eventName); - } - } - } -); - -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Hub.subscribe(HubChannel.AUTH) { event -> - when (event.name) { - InitializationStatus.SUCCEEDED.name -> - Log.i("AuthQuickstart", "Auth successfully initialized") - InitializationStatus.FAILED.name -> - Log.i("AuthQuickstart", "Auth failed to succeed") - else -> when (event.name) { - AuthChannelEventName.SIGNED_IN.name -> - Log.i("AuthQuickstart", "Auth just became signed in") - AuthChannelEventName.SIGNED_OUT.name -> - Log.i("AuthQuickstart", "Auth just became signed out") - AuthChannelEventName.SESSION_EXPIRED.name -> - Log.i("AuthQuickstart", "Auth session just expired") - AuthChannelEventName.USER_DELETED.name -> - Log.i("AuthQuickstart", "User has been deleted") - else -> - Log.w("AuthQuickstart", "Unhandled Auth Event: ${event.name}") - } - } -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -Amplify.Hub.subscribe(HubChannel.AUTH).collect { - when (it.name) { - InitializationStatus.SUCCEEDED.name -> - Log.i("AuthQuickstart", "Auth successfully initialized") - InitializationStatus.FAILED.name -> - Log.i("AuthQuickstart", "Auth failed to succeed") - else -> when (it.name) { - AuthChannelEventName.SIGNED_IN.name -> - Log.i("AuthQuickstart", "Auth just became signed in.") - AuthChannelEventName.SIGNED_OUT.name -> - Log.i("AuthQuickstart", "Auth just became signed out.") - AuthChannelEventName.SESSION_EXPIRED.name -> - Log.i("AuthQuickstart", "Auth session just expired.") - AuthChannelEventName.USER_DELETED.name -> - Log.i("AuthQuickstart", "User has been deleted.") - else -> - Log.w("AuthQuickstart", "Unhandled Auth Event: ${it.name}") - } - } -} -``` - -#### [RxJava] - -```java -RxAmplify.Hub.on(HubChannel.AUTH) - .map(HubEvent::getName) - .subscribe(name -> { - if (name.equals(InitializationStatus.SUCCEEDED.name())) { - Log.i("AuthQuickstart", "Auth successfully initialized"); - return; - } else if (name.equals(InitializationStatus.FAILED.name())) { - Log.i("AuthQuickstart", "Auth failed to succeed"); - return; - } else { - if (name.equals(SIGNED_IN.name())) { - Log.i("AuthQuickstart", "Auth just became signed in."); - } - else if (name.equals(SIGNED_OUT.name())) { - Log.i("AuthQuickstart", "Auth just became signed out."); - } - else if (name.equals(SESSION_EXPIRED.name())) { - Log.i("AuthQuickstart", "Auth session just expired."); - } - else if (name.equals(USER_DELETED.name())) { - Log.i("AuthQuickstart", "User has been deleted."); - } - else { - Log.w("AuthQuickstart", "Unhandled Auth Event: " + hubEvent.getName()); - } - } - }); -``` - - - -AWS Cognito Auth Plugin sends important events through Amplify Hub. You can listen to these events like the following: - -#### [Listener] - -```swift -override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - - // Assumes `unsubscribeToken` is declared as an instance variable in your view - unsubscribeToken = Amplify.Hub.listen(to: .auth) { payload in - switch payload.eventName { - case HubPayload.EventName.Auth.signedIn: - print("User signed in") - // Update UI - - case HubPayload.EventName.Auth.sessionExpired: - print("Session expired") - // Re-authenticate the user - - case HubPayload.EventName.Auth.signedOut: - print("User signed out") - // Update UI - - case HubPayload.EventName.Auth.userDeleted: - print("User deleted") - // Update UI - - default: - break - } - } -} -``` - -#### [Combine] - -```swift -override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - - // Assumes `sink` is declared as an instance variable in your view controller - sink = Amplify.Hub - .publisher(for: .auth) - .sink { payload in - switch payload.eventName { - case HubPayload.EventName.Auth.signedIn: - print("User signed in") - // Update UI - - case HubPayload.EventName.Auth.sessionExpired: - print("Session expired") - // Re-authenticate the user - - case HubPayload.EventName.Auth.signedOut: - print("User signed out") - // Update UI - - case HubPayload.EventName.Auth.userDeleted: - print("User deleted") - // Update UI - - default: - break - } - } -} -``` - - - ---- - ---- -title: "Delete user account" -section: "build-a-backend/auth/connect-your-frontend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-16T15:59:30.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/delete-user-account/" ---- - -export async function getStaticPaths() { - return getCustomStaticPath(meta.platforms); -} - -Empowering users to delete their account can improve trust and transparency. You can programmatically enable self-service account deletion with Amplify Auth. - -If you have not yet created an Amplify Gen 2 app, visit the [quickstart](/[platform]/start/quickstart). - -## Allow users to delete their account - -You can quickly set up account deletion for your users with the Amplify Libraries. Invoking the `deleteUser` API to delete a user from the Auth category will also sign out your user. - -If your application uses a Cognito User Pool, which is the default configuration, this action will only delete the user from the Cognito User Pool. It will have no effect if you are federating with a Cognito Identity Pool alone. - -> **Warning:** Before invoking the `deleteUser` API, you may need to first delete associated user data that is not stored in Cognito. For example, if you are using Amplify Data to persist user data, you could follow [these instructions](https://gist.github.com/aws-amplify-ops/27954c421bd72930874d48c15c284807) to delete associated user data. This allows you to address any guidelines (such as GDPR) that require your app to delete data associated with a user who deletes their account. - -You can enable account deletion using the following method: - - -```ts -import { deleteUser } from 'aws-amplify/auth'; - -async function handleDeleteUser() { - try { - await deleteUser(); - } catch (error) { - console.log(error); - } -} -``` - - -```dart -Future deleteUser() async { - try { - await Amplify.Auth.deleteUser(); - safePrint('Delete user succeeded'); - } on AuthException catch (e) { - safePrint('Delete user failed with error: $e'); - } -} -``` - - - -#### [Java] - -```java -Amplify.Auth.deleteUser( - () -> Log.i("AuthQuickStart", "Delete user succeeded"), - error -> Log.e("AuthQuickStart", "Delete user failed with error " + error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.deleteUser( - { Log.i("AuthQuickStart", "Delete user succeeded") }, - { Log.e("AuthQuickStart", "Delete user failed with error", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - Amplify.Auth.deleteUser() - Log.i("AuthQuickStart", "Delete user succeeded") -} catch (error: AuthException) { - Log.e("AuthQuickStart", "Delete user failed with error", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.deleteUser() - .subscribe( - () -> Log.i("AuthQuickStart", "Delete user succeeded"), - error -> Log.e("AuthQuickStart", "Delete user failed with error " + error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func deleteUser() async { - do { - try await Amplify.Auth.deleteUser() - print("Successfully deleted user") - } catch let error as AuthError { - print("Delete user failed with error \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func deleteUser() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.deleteUser() - }.sink { - if case let .failure(authError) = $0 { - print("Delete user failed with error \(authError)") - } - } - receiveValue: { - print("Successfully deleted user") - } -} -``` - - - -We recommend you update your UI to let your users know that their account is deleted and test the functionality with a test user. Note that your user will be signed out of your application when they delete their account. - ---- - ---- -title: "Multi-step sign-in" -section: "build-a-backend/auth/connect-your-frontend" -platforms: ["android", "swift", "flutter", "react", "nextjs", "javascript", "react-native", "vue", "angular"] -gen: 2 -last-updated: "2024-12-09T19:54:14.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/" ---- - - -After a user has finished signup, they can proceed to sign in. Amplify Auth signin flows can be multi-step processes. The required steps are determined by the configuration provided when you define your auth resources. See the [multi-factor authentication](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/) page for more information. - -Depending on the configuration, you may need to call various APIs to finish authenticating a user's signin attempt. To identify the next step in a signin flow, inspect the `nextStep` parameter of the signin result. - -```typescript -import { - confirmSignIn, - confirmSignUp, - resetPassword, - signIn, -} from 'aws-amplify/auth'; - -const { nextStep } = await signIn({ - username: 'hello@mycompany.com', - password: 'hunter2', -}); - -if ( - nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_CODE' || - nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE' || - nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_TOTP_CODE' -) { - // collect OTP from user - await confirmSignIn({ - challengeResponse: '123456', - }); -} - -if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_MFA_SELECTION') { - // present nextStep.allowedMFATypes to user - // collect user selection - await confirmSignIn({ - challengeResponse: 'EMAIL', // 'EMAIL', 'SMS', or 'TOTP' - }); -} - -if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION') { - // present nextStep.allowedMFATypes to user - // collect user selection - await confirmSignIn({ - challengeResponse: 'EMAIL', // 'EMAIL' or 'TOTP' - }); -} - -if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_EMAIL_SETUP') { - // collect email address from user - await confirmSignIn({ - challengeResponse: 'hello@mycompany.com', - }); -} - -if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_TOTP_SETUP') { - // present nextStep.totpSetupDetails.getSetupUri() to user - // collect OTP from user - await confirmSignIn({ - challengeResponse: '123456', - }); -} - -if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_PASSWORD') { - // collect password from user - await confirmSignIn({ - challengeResponse: 'hunter2', - }); -} - -if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION') { - // present nextStep.availableChallenges to user - // collect user selection - await confirmSignIn({ - challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' - }); -} - -if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE') { - // collect custom challenge answer from user - await confirmSignIn({ - challengeResponse: 'custom-challenge-answer', - }); -} - -if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED') { - // collect new password from user - await confirmSignIn({ - challengeResponse: 'new-password', - }); -} - -if (nextStep.signInStep === 'RESET_PASSWORD') { - // initiate reset password flow - await resetPassword({ - username: 'username', - }); -} - -if (nextStep.signInStep === 'CONFIRM_SIGN_UP') { - // user was not confirmed during sign up process - // if user has confirmation code, invoke `confirmSignUp` api - // otherwise, invoke `resendSignUpCode` to resend the code - await confirmSignUp({ - username: 'username', - confirmationCode: '123456', - }); -} - -if (nextStep.signInStep === 'DONE') { - // signin complete -} -``` - -## Confirm sign-in with SMS MFA - -If the next step is `CONFIRM_SIGN_IN_WITH_SMS_CODE`, Amplify Auth has sent the user a random code over SMS and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. - - - -The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial phone number of the SMS recipient, which can be used to prompt the user on where to look for the code. - - - -```ts -import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONFIRM_SIGN_IN_WITH_SMS_CODE': { - const { codeDeliveryDetails } = result.nextStep; - // OTP has been delivered to user via SMS - // Inspect codeDeliveryDetails for additional delivery information - console.log( - `A confirmation code has been sent to ${codeDeliveryDetails?.destination}`, - ); - console.log( - `Please check your ${codeDeliveryDetails?.deliveryMedium} for the code.`, - ); - break; - } - } -} - -async function confirmMfaCode(mfaCode: string) { - const result = await confirmSignIn({ challengeResponse: mfaCode }); - - return handleSignInResult(result); -} - -``` - -## Confirm sign-in with TOTP MFA - -If the next step is `CONFIRM_SIGN_IN_WITH_TOTP_CODE`, you should prompt the user to enter the TOTP code from their associated authenticator app during set up. The code is a six-digit number that changes every 30 seconds. The user must enter the code before the 30-second window expires. - -After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. - -```ts -import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONFIRM_SIGN_IN_WITH_TOTP_CODE': { - // Prompt user to open their authenticator app to retrieve the code - console.log( - `Enter a one-time code from your registered authenticator app`, - ); - break; - } - } -} -// Then, pass the TOTP code to `confirmSignIn` -async function confirmTotpCode(totpCode: string) { - const result = await confirmSignIn({ challengeResponse: totpCode }); - - return handleSignInResult(result); -} - -``` - -## Confirm sign-in with Email MFA - -If the next step is `CONFIRM_SIGN_IN_WITH_EMAIL_CODE`, Amplify Auth has sent the user a random code to their email address and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. - - - -The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial email address of the recipient, which can be used to prompt the user on where to look for the code. - - - -```ts -import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE': { - const { codeDeliveryDetails } = result.nextStep; - // OTP has been delivered to user via Email - // Inspect codeDeliveryDetails for additional delivery information - console.log( - `A confirmation code has been sent to ${codeDeliveryDetails?.destination}`, - ); - console.log( - `Please check your ${codeDeliveryDetails?.deliveryMedium} for the code.`, - ); - break; - } - } -} - -async function confirmMfaCode(mfaCode: string) { - const result = await confirmSignIn({ challengeResponse: mfaCode }); - - return handleSignInResult(result); -} - -``` - -## Continue sign-in with MFA Selection - -If the next step is `CONTINUE_SIGN_IN_WITH_MFA_SELECTION`, the user must select the MFA method to use. Amplify Auth currently supports SMS, TOTP, and EMAIL as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. - -The MFA types which are currently supported by Amplify Auth are: - -- `SMS` -- `TOTP` -- `EMAIL` - -Once Amplify receives the users selection, you can expect to handle a follow up `nextStep` corresponding with the selected MFA type for setup: -- If `SMS` is selected, `CONFIRM_SIGN_IN_WITH_SMS_CODE` will be the next step. -- If `TOTP` is selected, `CONFIRM_SIGN_IN_WITH_TOTP_CODE` will be the next step. -- If `EMAIL` is selected, `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` will be the next step. - -```ts -import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONTINUE_SIGN_IN_WITH_MFA_SELECTION': { - const { allowedMFATypes } = result.nextStep; - // Present available MFA options to user - // Prompt for selection - console.log(`There are multiple MFA options available for sign in.`); - console.log(`Select an MFA type from the allowedMfaTypes list.`); - break; - } - } -} - -type MfaType = 'SMS' | 'TOTP' | 'EMAIL'; - -async function handleMfaSelection(mfaType: MfaType) { - const result = await confirmSignIn({ challengeResponse: mfaType }); - - return handleSignInResult(result); -} - -``` - -## Continue sign-in with Email Setup - -If the next step is `CONTINUE_SIGN_IN_WITH_EMAIL_SETUP`, then the user must provide an email address to complete the sign in process. Once this value has been collected from the user, call the `confirmSignIn` API to continue. - -```ts -import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONTINUE_SIGN_IN_WITH_EMAIL_SETUP': { - // Prompt the user to enter an email address they would like to use for MFA - break; - } - } -} - -// Then, pass the email address to `confirmSignIn` -async function confirmEmail(email: string) { - const result = await confirmSignIn({ challengeResponse: email }); - - return handleSignInResult(result); -} - -``` - -## Continue sign-in with TOTP Setup - -The `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` step signifies that the user must set up TOTP before they can sign in. The step returns an associated value of type TOTPSetupDetails which must be used to configure an authenticator app like Microsoft Authenticator or Google Authenticator. TOTPSetupDetails provides a helper method called getSetupURI which generates a URI that can be used, for example, in a button to open the user's installed authenticator app. For more advanced use cases, TOTPSetupDetails also contains a sharedSecret which can be used to either generate a QR code or be manually entered into an authenticator app. - -Once the authenticator app is set up, the user can generate a TOTP code and provide it to the library to complete the sign in process. - -```ts -import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONTINUE_SIGN_IN_WITH_TOTP_SETUP': { - const { totpSetupDetails } = result.nextStep; - const appName = 'my_app_name'; - const setupUri = totpSetupDetails.getSetupUri(appName); - // Open setupUri with an authenticator app - // Prompt user to enter OTP code to complete setup - break; - } - } -} - -// Then, pass the collected OTP code to `confirmSignIn` -async function confirmTotpCode(totpCode: string) { - const result = await confirmSignIn({ challengeResponse: totpCode }); - - return handleSignInResult(result); -} - -``` - -## Continue sign-in with MFA Setup Selection - -If the next step is `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION`, then the user must indicate which of the available MFA methods they would like to setup. After the user selects an MFA method to setup, your implementation must pass the selected MFA method to the `confirmSignIn` API. - -The MFA types which are currently supported by Amplify Auth for setup are: - -- `TOTP` -- `EMAIL` - -Once Amplify receives the users selection, you can expect to handle a follow up `nextStep` corresponding with the selected MFA type for setup: -- If `EMAIL` is selected, `CONTINUE_SIGN_IN_WITH_EMAIL_SETUP` will be the next step. -- If `TOTP` is selected, `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` will be the next step. - -```ts -import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION': { - const { allowedMFATypes } = result.nextStep; - // Present available MFA options to user - // Prompt for selection - console.log(`There are multiple MFA options available for setup.`); - console.log(`Select an MFA type from the allowedMFATypes list.`); - break; - } - } -} - -type MfaType = 'SMS' | 'TOTP' | 'EMAIL'; - -async function handleMfaSelection(mfaType: MfaType) { - const result = await confirmSignIn({ challengeResponse: mfaType }); - - return handleSignInResult(result); -} - -``` - -## Confirm sign-in with Password - -If the next step is `CONFIRM_SIGN_IN_WITH_PASSWORD`, the user must provide their password as the first factor authentication method. To handle this step, your implementation should prompt the user to enter their password. After the user enters the password, pass the value to the `confirmSignIn` API. - -```ts -import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONFIRM_SIGN_IN_WITH_PASSWORD': { - // Prompt user to enter their password - console.log(`Please enter your password.`); - break; - } - } -} - -async function confirmWithPassword(password: string) { - const result = await confirmSignIn({ challengeResponse: password }); - - return handleSignInResult(result); -} -``` - -## Continue sign-in with First Factor Selection - -If the next step is `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, the user must select a first factor method for authentication. After the user selects an option, your implementation should pass the selected method to the `confirmSignIn` API. - -The first factor types which are currently supported by Amplify Auth are: -- `SMS_OTP` -- `EMAIL_OTP` -- `WEB_AUTHN` -- `PASSWORD` -- `PASSWORD_SRP` - -Depending on your configuration and what factors the user has previously setup, not all options may be available. Only the available options will be presented in `availableChallenges` for selection. - -Once Amplify receives the user's selection via the `confirmSignIn` API, you can expect to handle a follow up `nextStep` corresponding with the first factor type selected: -- If `SMS_OTP` is selected, `CONFIRM_SIGN_IN_WITH_SMS_CODE` will be the next step. -- If `EMAIL_OTP` is selected, `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` will be the next step. -- If `PASSWORD` or `PASSWORD_SRP` is selected, `CONFIRM_SIGN_IN_WITH_PASSWORD` will be the next step. -- If `WEB_AUTHN` is selected, Amplify Auth will initiate the authentication ceremony on the user's device. If successful, the next step will be `DONE`. - -```ts -import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION': { - const { availableChallenges } = result.nextStep; - // Present available first factor options to user - // Prompt for selection - console.log( - `There are multiple first factor options available for sign in.`, - ); - console.log( - `Select a first factor type from the availableChallenges list.`, - ); - break; - } - } -} - -async function handleFirstFactorSelection(firstFactorType: string) { - const result = await confirmSignIn({ challengeResponse: firstFactorType }); - - return handleSignInResult(result); -} - -``` - -## Confirm sign-in with custom challenge - -If the next step is `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the AWS Lambda trigger you configured as part of a custom sign in flow. - -For example, your custom challenge Lambda may pass a prompt to the frontend which requires the user to enter a secret code. - -```ts -import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE': { - const params = result.nextStep.additionalInfo; - const hint = params.hint!; - // Prompt user to enter custom challenge response - console.log(hint); // `Enter the secret code` - break; - } - } -} - -``` - -To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the `confirmSignIn` API. - -```ts -async function confirmCustomChallenge(answer: string) { - const result = await confirmSignIn({ challengeResponse: answer }); - - return handleSignInResult(result); -} -``` - -> **Warning:** **Special Handling on `confirmSignIn`** -> -> If `failAuthentication=true` is returned by the Lambda, Cognito will invalidate the session of the request. This is represented by a `NotAuthorizedException` and requires restarting the sign-in flow by calling `signIn` again. - -## Confirm sign-in with new password - -If the next step is `CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED`, Amplify Auth requires the user choose a new password they proceeding with the sign in. - -Prompt the user for a new password and pass it to the `confirmSignIn` API. - -See the [sign-in](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/) and [manage-password](/[platform]/build-a-backend/auth/manage-users/manage-passwords/) docs for more information. - -```ts -import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED': { - // Prompt user to enter a new password - console.log(`Please enter a new password.`); - break; - } - } -} - -async function confirmNewPassword(newPassword: string) { - const result = await confirmSignIn({ challengeResponse: newPassword }); - - return handleSignInResult(result); -} - -``` - -## Reset password - -If the next step is `RESET_PASSWORD`, Amplify Auth requires that the user reset their password before proceeding. -Use the `resetPassword` API to guide the user through resetting their password, then call `signIn` to restart the sign-in flow. - -See the [reset password](/[platform]/build-a-backend/auth/manage-users/manage-passwords/) docs for more information. - -```ts -import { - type ResetPasswordOutput, - type SignInOutput, - resetPassword, -} from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'RESET_PASSWORD': { - const resetPasswordResult = await resetPassword({ username }); - // initiate reset password flow - await handleResetPasswordResult(resetPasswordResult); - break; - } - } -} - -async function handleResetPasswordResult( - resetPasswordResult: ResetPasswordOutput, -) { - switch (resetPasswordResult.nextStep.resetPasswordStep) { - case 'CONFIRM_RESET_PASSWORD_WITH_CODE': { - const { codeDeliveryDetails } = resetPasswordResult.nextStep; - console.log( - `A confirmation code has been sent to ${codeDeliveryDetails.destination}.`, - ); - console.log( - `Please check your ${codeDeliveryDetails.destination} for the code.`, - ); - break; - } - case 'DONE': { - console.log(`Successfully reset password.`); - break; - } - } -} - -``` - -## Confirm Signup - -If the next step is `CONFIRM_SIGN_UP`, Amplify Auth requires that the user confirm their email or phone number before proceeding. -Use the `resendSignUpCode` API to send a new sign up code to the registered email or phone number, followed by `confirmSignUp` to complete the sign up. - -See the [sign up](/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/) docs for more information. - - - -The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial phone number of the SMS recipient, which can be used to prompt the user on where to look for the code. - - - -```ts -import { - type SignInOutput, - confirmSignUp, - resendSignUpCode, -} from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'CONFIRM_SIGN_UP': { - // Resend sign up code to the registered user - const { destination, deliveryMedium } = await resendSignUpCode({ - username, - }); - console.log(`A confirmation code has been sent to ${destination}.`); - console.log(`Please check your ${deliveryMedium} for the code.`); - break; - } - } -} - -async function handleConfirmSignUp(username: string, confirmationCode: string) { - await confirmSignUp({ - username, - confirmationCode, - }); -} - -``` - -Once the sign up is confirmed, call `signIn` again to restart the sign-in flow. - -## Done - -The sign-in flow is complete when the next step is `DONE`, which means the user is successfully authenticated. -As a convenience, the `SignInResult` also provides the `isSignedIn` property, which will be true if the next step is `DONE`. - -```ts -import { type SignInOutput } from '@aws-amplify/auth'; - -async function handleSignInResult(result: SignInOutput) { - switch (result.nextStep.signInStep) { - case 'DONE': { - // `result.isSignedIn` is `true` - console.log(`Sign in is complete.`); - break; - } - } -} - -``` - - - -After a user has finished signup, they can proceed to sign in. Amplify Auth signin flows can be multi step processes. The required steps are determined by the configuration you provided when you define your auth resources like described on [Manage MFA Settings](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/) page. - -Depending on the configuration, you may need to call various APIs to finish authenticating a user's signin attempt. To identify the next step in a signin flow, inspect the `nextStep` parameter in the signin result. - -> **Warning:** *New enumeration values* -> -> When Amplify adds a new enumeration value (e.g., a new enum class entry or sealed class subtype in Kotlin, or a new enum value in Swift/Dart/Kotlin), it will publish a new minor version of the Amplify Library. Plugins that switch over enumeration values should include default handlers (an else branch in Kotlin or a default statement in Swift/Dart/Kotlin) to ensure that they are not impacted by new enumeration values. - -The `Amplify.Auth.signIn` API returns a `SignInResult` object which indicates whether the sign-in flow is -complete or whether additional steps are required before the user is signed in. - -To see if additional signin steps are required, inspect the sign in result's `nextStep.signInStep` property. -- If the sign-in step is `done`, the flow is complete and the user is signed in. -- If the sign-in step is not `done`, one or more additional steps are required. These are explained in detail below. - - - -The `signInStep` property is an enum of type `AuthSignInStep`. Depending on its value, your code should take one of the actions mentioned on this page. - - - -```dart -Future signInWithCognito( - String username, - String password, -) async { - final SignInResult result = await Amplify.Auth.signIn( - username: username, - password: password, - ); - return _handleSignInResult(result); -} - -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - case AuthSignInStep.continueSignInWithMfaSelection: - // Handle select from MFA methods case - case AuthSignInStep.continueSignInWithMfaSetupSelection: - // Handle select from MFA methods available to setup - case AuthSignInStep.continueSignInWithEmailMfaSetup: - // Handle email setup case - case AuthSignInStep.confirmSignInWithOtpCode: - // Handle email MFA case - case AuthSignInStep.continueSignInWithTotpSetup: - // Handle TOTP setup case - case AuthSignInStep.confirmSignInWithTotpMfaCode: - // Handle TOTP MFA case - case AuthSignInStep.confirmSignInWithSmsMfaCode: - // Handle SMS MFA case - case AuthSignInStep.confirmSignInWithNewPassword: - // Handle new password case - case AuthSignInStep.confirmSignInWithCustomChallenge: - // Handle custom challenge case - case AuthSignInStep.resetPassword: - // Handle reset password case - case AuthSignInStep.confirmSignUp: - // Handle confirm sign up case - case AuthSignInStep.done: - safePrint('Sign in is complete'); - } -} -``` -## Confirm sign-in with SMS MFA - -If the next step is `confirmSignInWithSmsMfaCode`, Amplify Auth has sent the user a random code over SMS and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. - - - -The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial phone number of -the SMS recipient, which can be used to prompt the user on where to look for the code. - - - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - case AuthSignInStep.confirmSignInWithSmsMfaCode: - final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; - _handleCodeDelivery(codeDeliveryDetails); - // ... - } -} - -void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { - safePrint( - 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' - 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', - ); -} -``` - -```dart -Future confirmMfaUser(String mfaCode) async { - try { - final result = await Amplify.Auth.confirmSignIn( - confirmationValue: mfaCode, - ); - return _handleSignInResult(result); - } on AuthException catch (e) { - safePrint('Error confirming MFA code: ${e.message}'); - } -} -``` - -## Confirm sign-in with TOTP MFA - -If the next step is `confirmSignInWithTOTPCode`, you should prompt the user to enter the TOTP code from their associated authenticator app during set up. The code is a six-digit number that changes every 30 seconds. The user must enter the code before the 30-second window expires. - -After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - // Β·Β·Β· - case AuthSignInStep.confirmSignInWithTotpMfaCode: - safePrint('Enter a one-time code from your registered authenticator app'); - // Β·Β·Β· - } -} - -// Then, pass the TOTP code to `confirmSignIn` - -Future confirmTotpUser(String totpCode) async { - try { - final result = await Amplify.Auth.confirmSignIn( - confirmationValue: totpCode, - ); - return _handleSignInResult(result); - } on AuthException catch (e) { - safePrint('Error confirming TOTP code: ${e.message}'); - } -} -``` - -## Confirm sign-in with Email MFA - -If the next step is `confirmSignInWithOtpCode`, Amplify Auth has sent the user a random code to their email address and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. - - - -The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial email address of -the recipient, which can be used to prompt the user on where to look for the code. - - - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - case AuthSignInStep.confirmSignInWithOtpCode: - final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; - _handleCodeDelivery(codeDeliveryDetails); - // ... - } -} - -void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { - safePrint( - 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' - 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', - ); -} -``` - -```dart -Future confirmMfaUser(String mfaCode) async { - try { - final result = await Amplify.Auth.confirmSignIn( - confirmationValue: mfaCode, - ); - return _handleSignInResult(result); - } on AuthException catch (e) { - safePrint('Error confirming MFA code: ${e.message}'); - } -} -``` - -## Continue sign-in with MFA Selection - -If the next step is `continueSignInWithMFASelection`, the user must select the MFA method to use. Amplify Auth currently supports SMS, TOTP, and email as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. - -The MFA types which are currently supported by Amplify Auth are: - -- `MfaType.sms` -- `MfaType.totp` -- `MfaType.email` - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - // Β·Β·Β· - case AuthSignInStep.continueSignInWithMfaSelection: - final allowedMfaTypes = result.nextStep.allowedMfaTypes!; - final selection = await _promptUserPreference(allowedMfaTypes); - return _handleMfaSelection(selection); - // Β·Β·Β· - } -} - -Future _promptUserPreference(Set allowedTypes) async { - // Β·Β·Β· -} - -Future _handleMfaSelection(MfaType selection) async { - try { - final result = await Amplify.Auth.confirmSignIn( - confirmationValue: selection.confirmationValue, - ); - return _handleSignInResult(result); - } on AuthException catch (e) { - safePrint('Error resending code: ${e.message}'); - } -} -``` - -## Continue sign-in with Email Setup - -If the next step is `continueSignInWithEmailMfaSetup`, then the user must provide an email address to complete the sign in process. Once this value has been collected from the user, call the `confirmSignIn` API to continue. - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - // Β·Β·Β· - case AuthSignInStep.continueSignInWithEmailMfaSetup: - // Prompt user to enter an email address they would like to use for MFA - // Β·Β·Β· - } -} - -// Then, pass the email address to `confirmSignIn` - -Future confirmEmailUser(String emailAddress) async { - try { - final result = await Amplify.Auth.confirmSignIn( - confirmationValue: emailAddress, - ); - return _handleSignInResult(result); - } on AuthException catch (e) { - safePrint('Error confirming email address: ${e.message}'); - } -} -``` - -## Continue sign-in with TOTP Setup - -If the next step is `continueSignInWithTOTPSetup`, then the user must provide a TOTP code to complete the sign in process. The step returns an associated value of type `TOTPSetupDetails` which would be used for generating TOTP. `TOTPSetupDetails` provides a helper method called `getSetupURI` that can be used to generate a URI, which can be used by native password managers for TOTP association. For example. if the URI is used on Apple platforms, it will trigger the platform's native password manager to associate TOTP with the account. For more advanced use cases, `TOTPSetupDetails` also contains the `sharedSecret` that will be used to either generate a QR code or can be manually entered into an authenticator app. - -Once the authenticator app is set up, the user can generate a TOTP code and provide it to the library to complete the sign in process. - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - // Β·Β·Β· - case AuthSignInStep.continueSignInWithTotpSetup: - final totpSetupDetails = result.nextStep.totpSetupDetails!; - final setupUri = totpSetupDetails.getSetupUri(appName: 'MyApp'); - safePrint('Open URI to complete setup: $setupUri'); - // Β·Β·Β· - } -} - -// Then, pass the TOTP code to `confirmSignIn` - -Future confirmTotpUser(String totpCode) async { - try { - final result = await Amplify.Auth.confirmSignIn( - confirmationValue: totpCode, - ); - return _handleSignInResult(result); - } on AuthException catch (e) { - safePrint('Error confirming TOTP code: ${e.message}'); - } -} -``` - -## Continue sign-in with MFA Setup Selection -If the next step is `continueSignInWithMfaSetupSelection`, then the user must indicate which of the available MFA methods they would like to setup. After the user selects an MFA method to setup, your implementation must pass the selected MFA method to the `confirmSignIn` API. - -The MFA types which are currently supported by Amplify Auth are: - -- `MfaType.sms` -- `MfaType.totp` -- `MfaType.email` - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - // Β·Β·Β· - case AuthSignInStep.continueSignInWithMfaSetupSelection: - final allowedMfaTypes = result.nextStep.allowedMfaTypes!; - final selection = await _promptUserPreference(allowedMfaTypes); - return _handleMfaSelection(selection); - // Β·Β·Β· - } -} - -Future _promptUserPreference(Set allowedTypes) async { - // Β·Β·Β· -} - -Future _handleMfaSelection(MfaType selection) async { - try { - final result = await Amplify.Auth.confirmSignIn( - confirmationValue: selection.confirmationValue, - ); - return _handleSignInResult(result); - } on AuthException catch (e) { - safePrint('Error selecting MFA method: ${e.message}'); - } -} -``` - -## Confirm sign-in with custom challenge - -If the next step is `confirmSignInWithCustomChallenge`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the AWS Lambda trigger you configured as part of a [custom sign in flow](/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/#sign-in-a-user). - -For example, your custom challenge Lambda may pass a prompt to the frontend which requires the user to enter a secret code. - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - // ... - case AuthSignInStep.confirmSignInWithCustomChallenge: - final parameters = result.nextStep.additionalInfo; - final hint = parameters['hint']!; - safePrint(hint); // "Enter the secret code" - // ... - } -} -``` - -To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the `confirmSignIn` API. - -```dart -Future confirmCustomChallenge(String answer) async { - try { - final result = await Amplify.Auth.confirmSignIn( - confirmationValue: answer, - ); - return _handleSignInResult(result); - } on AuthException catch (e) { - safePrint('Error confirming custom challenge: ${e.message}'); - } -} -``` - -> **Warning:** **Special Handling on `confirmSignIn`** -> -> If `failAuthentication=true` is returned by the Lambda, Cognito will invalidate the session of the request. This is represented by a `NotAuthorizedException` and requires restarting the sign-in flow by calling `Amplify.Auth.signIn` again. - -## Confirm sign-in with new password -If the next step is `confirmSignInWithNewPassword`, Amplify Auth requires the user choose a new password they proceeding with the sign in. - -Prompt the user for a new password and pass it to the `confirmSignIn` API. - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - // ... - case AuthSignInStep.confirmSignInWithNewPassword: - safePrint('Please enter a new password'); - // ... - } -} -``` - -```dart -Future confirmNewPassword(String newPassword) async { - try { - final result = await Amplify.Auth.confirmSignIn( - confirmationValue: newPassword, - ); - return _handleSignInResult(result); - } on AuthException catch (e) { - safePrint('Error confirming new password: ${e.message}'); - } -} -``` - -## Reset password -If the next step is `resetPassword`, Amplify Auth requires that the user reset their password before proceeding. -Use the `resetPassword` API to guide the user through resetting their password, then call `Amplify.Auth.signIn` -when that's complete to restart the sign-in flow. - -See the [reset password](/[platform]/build-a-backend/auth/manage-users/manage-passwords/) docs for more information. - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - // ... - case AuthSignInStep.resetPassword: - final resetResult = await Amplify.Auth.resetPassword( - username: username, - ); - await _handleResetPasswordResult(resetResult); - // ... - } -} - -Future _handleResetPasswordResult(ResetPasswordResult result) async { - switch (result.nextStep.updateStep) { - case AuthResetPasswordStep.confirmResetPasswordWithCode: - final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; - _handleCodeDelivery(codeDeliveryDetails); - case AuthResetPasswordStep.done: - safePrint('Successfully reset password'); - } -} - -void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { - safePrint( - 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' - 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', - ); -} -``` -## Confirm Signup -If the next step is `resetPassword`, Amplify Auth requires that the user confirm their email or phone number before proceeding. -Use the `resendSignUpCode` API to send a new sign up code to the registered email or phone number, followed by `confirmSignUp` -to complete the sign up. - -See the [confirm sign up](/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/#confirm-sign-up) docs for more information. - - - -The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial phone number of -the SMS recipient, which can be used to prompt the user on where to look for the code. - - - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - // ... - case AuthSignInStep.confirmSignUp: - // Resend the sign up code to the registered device. - final resendResult = await Amplify.Auth.resendSignUpCode( - username: username, - ); - _handleCodeDelivery(resendResult.codeDeliveryDetails); - // ... - } -} - -void _handleCodeDelivery(AuthCodeDeliveryDetails codeDeliveryDetails) { - safePrint( - 'A confirmation code has been sent to ${codeDeliveryDetails.destination}. ' - 'Please check your ${codeDeliveryDetails.deliveryMedium.name} for the code.', - ); -} -``` - -```dart -Future confirmSignUp({ - required String username, - required String confirmationCode, -}) async { - try { - await Amplify.Auth.confirmSignUp( - username: username, - confirmationCode: confirmationCode, - ); - } on AuthException catch (e) { - safePrint('Error confirming sign up: ${e.message}'); - } -} -``` - -Once the sign up is confirmed, call `Amplify.Auth.signIn` again to restart the sign-in flow. - -## Done - -The sign-in flow is complete when the next step is `done`, which means the user is successfully authenticated. -As a convenience, the `SignInResult` also provides the `isSignedIn` property, which will be true if the next step is `done`. - -```dart -Future _handleSignInResult(SignInResult result) async { - switch (result.nextStep.signInStep) { - // ... - case AuthSignInStep.done: - // Could also check that `result.isSignedIn` is `true` - safePrint('Sign in is complete'); - } -} -``` - - - -After a user has finished signup, they can proceed to sign in. Amplify Auth signin flows can be multi step processes. The required steps are determined by the configuration you provided when you define your auth resources like described on [Manage MFA Settings](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/) page. - -Depending on the configuration, you may need to call various APIs to finish authenticating a user's signin attempt. To identify the next step in a signin flow, inspect the `nextStep` parameter in the signin result. - -> **Warning:** *New enumeration values* -> -> When Amplify adds a new enumeration value (e.g., a new enum class entry or sealed class subtype in Kotlin, or a new enum value in Swift/Dart/Kotlin), it will publish a new minor version of the Amplify Library. Plugins that switch over enumeration values should include default handlers (an else branch in Kotlin or a default statement in Swift/Dart/Kotlin) to ensure that they are not impacted by new enumeration values. - -When called successfully, the signin APIs will return an `AuthSignInResult`. Inspect the `nextStep` property in the result to see if additional signin steps are required. -The `nextStep` property is of enum type `AuthSignInStep`. Depending on its value, your code should take one of the following actions: - -#### [Java] - -```java -try { - Amplify.Auth.signIn( - "hello@example.com", - "password", - result -> - { - AuthNextSignInStep nextStep = result.getNextStep(); - switch (nextStep.getSignInStep()) { - case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { - Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); - Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to setup - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); - // Prompt the user to enter the email address they would like to use to receive OTPs - // Then invoke `confirmSignIn` api with the email address - break; - } - case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); - Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to use - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION: { - Log.i("AuthQuickstart", "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors()); - // Prompt the user to select which authentication factor they want to use to sign-in - // Then invoke `confirmSignIn` api with that selection - break; - } - case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { - Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the SMS MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_OTP: { - Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the OTP MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_PASSWORD: { - Log.i("AuthQuickstart", "Received next step as confirm sign in with password"); - // Prompt the user to enter their password - // Then invoke `confirmSignIn` api with that password - break; - } - case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { - Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter custom challenge answer - // Then invoke `confirmSignIn` api with the answer - break; - } - case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { - Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter a new password - // Then invoke `confirmSignIn` api with new password - break; - } - case DONE: { - Log.i("AuthQuickstart", "SignIn complete"); - // User has successfully signed in to the app - break; - } - } - }, - error -> { - if (error instanceof UserNotConfirmedException) { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.i("AuthQuickstart", "Signup confirmation required" + error); - } else if (error instanceof PasswordResetRequiredException) { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.i("AuthQuickstart", "Password reset required" + error); - } else { - Log.e("AuthQuickstart", "SignIn failed: " + error); - } - } - ); -} catch (Exception error) { - Log.e("AuthQuickstart", "Unexpected error occurred: " + error); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -try { - Amplify.Auth.signIn( - "hello@example.com", - "password", - { result -> - val nextStep = result.nextStep - when(nextStep.signInStep){ - AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE -> { - Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code") - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup") - Log.i("AuthQuickstart", "Allowed MFA types for setup ${nextStep.allowedMFATypes}") - // Prompt the user to select the MFA type they want to setup - // Then invoke `confirmSignIn` api with the MFA type - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA") - // Prompt the user to enter the email address they would like to use to receive OTPs - // Then invoke `confirmSignIn` api with the email address - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP") - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails?.sharedSecret}") - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type") - Log.i("AuthQuickstart", "Allowed MFA types ${nextStep.allowedMFATypes}") - // Prompt the user to select the MFA type they want to use - // Then invoke `confirmSignIn` api with the MFA type - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION -> { - Log.i("AuthQuickstart", "Available authentication factors for this user: ${result.nextStep.availableFactors}") - // Prompt the user to select which authentication factor they want to use to sign-in - // Then invoke `confirmSignIn` api with that selection - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE -> { - Log.i("AuthQuickstart", "SMS code sent to ${nextStep.codeDeliveryDetails?.destination}") - Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") - // Prompt the user to enter the SMS MFA code they received - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP -> { - Log.i("AuthQuickstart", "OTP code sent to ${nextStep.codeDeliveryDetails?.destination}") - Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") - // Prompt the user to enter the OTP MFA code they received - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD -> { - Log.i("AuthQuickstart", "Received next step as confirm sign in with password") - // Prompt the user to enter their password - // Then invoke `confirmSignIn` api with that password - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE -> { - Log.i("AuthQuickstart","Custom challenge, additional info: ${nextStep.additionalInfo}") - // Prompt the user to enter custom challenge answer - // Then invoke `confirmSignIn` api with the answer - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD -> { - Log.i("AuthQuickstart", "Sign in with new password, additional info: ${nextStep.additionalInfo}") - // Prompt the user to enter a new password - // Then invoke `confirmSignIn` api with new password - } - AuthSignInStep.DONE -> { - Log.i("AuthQuickstart", "SignIn complete") - // User has successfully signed in to the app - } - } - - } - ) { error -> - when (error) { - is UserNotConfirmedException -> { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.e("AuthQuickstart", "Signup confirmation required", error) - } - is PasswordResetRequiredException -> { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.e("AuthQuickstart", "Password reset required", error) - } - else -> { - Log.e("AuthQuickstart", "Unexpected error occurred: $error") - } - } - } -} catch (error: Exception) { - Log.e("AuthQuickstart", "Unexpected error occurred: $error") -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.signIn( - "hello@example.com", - "password" - ) - val nextStep = result.nextStep - when (nextStep.signInStep) { - AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE -> { - Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code") - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup") - Log.i("AuthQuickstart", "Allowed MFA types for setup ${nextStep.allowedMFATypes}") - // Prompt the user to select the MFA type they want to setup - // Then invoke `confirmSignIn` api with the MFA type - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA") - // Prompt the user to enter the email address they would like to use to receive OTPs - // Then invoke `confirmSignIn` api with the email address - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP") - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails?.sharedSecret}") - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type") - Log.i("AuthQuickstart", "Allowed MFA types ${nextStep.allowedMFATypes}") - // Prompt the user to select the MFA type they want to use - // Then invoke `confirmSignIn` api with the MFA type - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION -> { - Log.i("AuthQuickstart", "Available authentication factors for this user: ${result.nextStep.availableFactors}") - // Prompt the user to select which authentication factor they want to use to sign-in - // Then invoke `confirmSignIn` api with that selection - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE -> { - Log.i("AuthQuickstart", "SMS code sent to ${nextStep.codeDeliveryDetails?.destination}") - Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") - // Prompt the user to enter the SMS MFA code they received - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP -> { - Log.i("AuthQuickstart", "OTP code sent to ${nextStep.codeDeliveryDetails?.destination}") - Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") - // Prompt the user to enter the OTP MFA code they received - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD -> { - Log.i("AuthQuickstart", "Received next step as confirm sign in with password") - // Prompt the user to enter their password - // Then invoke `confirmSignIn` api with that password - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE -> { - Log.i("AuthQuickstart","Custom challenge, additional info: ${nextStep.additionalInfo}") - // Prompt the user to enter custom challenge answer - // Then invoke `confirmSignIn` api with the answer - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD -> { - Log.i("AuthQuickstart", "Sign in with new password, additional info: ${nextStep.additionalInfo}") - // Prompt the user to enter a new password - // Then invoke `confirmSignIn` api with new password - } - AuthSignInStep.DONE -> { - Log.i("AuthQuickstart", "SignIn complete") - // User has successfully signed in to the app - } - } -} catch (error: Exception) { - when (error) { - is UserNotConfirmedException -> { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.e("AuthQuickstart", "Signup confirmation required", error) - } - is PasswordResetRequiredException -> { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.e("AuthQuickstart", "Password reset required", error) - } - else -> { - Log.e("AuthQuickstart", "Unexpected error occurred: $error") - } - } -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.signIn("hello@example.com", "password").subscribe( - result -> - { - AuthNextSignInStep nextStep = result.getNextStep(); - switch (nextStep.getSignInStep()) { - case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { - Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); - Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to setup - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); - // Prompt the user to enter the email address they would like to use to receive OTPs - // Then invoke `confirmSignIn` api with the email address - break; - } - case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); - Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to use - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION: { - Log.i("AuthQuickstart", "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors()); - // Prompt the user to select which authentication factor they want to use to sign-in - // Then invoke `confirmSignIn` api with that selection - break; - } - case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { - Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the SMS MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_OTP: { - Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the OTP MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_PASSWORD: { - Log.i("AuthQuickstart", "Received next step as confirm sign in with password"); - // Prompt the user to enter their password - // Then invoke `confirmSignIn` api with that password - break; - } - case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { - Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter custom challenge answer - // Then invoke `confirmSignIn` api with the answer - break; - } - case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { - Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter a new password - // Then invoke `confirmSignIn` api with new password - break; - } - case DONE: { - Log.i("AuthQuickstart", "SignIn complete"); - // User has successfully signed in to the app - break; - } - } - }, - error -> { - if (error instanceof UserNotConfirmedException) { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.i("AuthQuickstart", "Signup confirmation required" + error); - } else if (error instanceof PasswordResetRequiredException) { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.i("AuthQuickstart", "Password reset required" + error); - } else { - Log.e("AuthQuickstart", "SignIn failed: " + error); - } - } -); -``` - -## Confirm sign-in with SMS MFA - -If the next step is `CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE`, Amplify Auth has sent the user a random code over SMS, and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. - - - -**Note:** The result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial phone number of the SMS recipient. - - - -#### [Java] - -```java -try { - Amplify.Auth.confirmSignIn( - "confirmation code", - result -> { - if (result.isSignedIn()) { - Log.i("AuthQuickstart", "Confirm signIn succeeded"); - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - }, - error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) - ); -} catch (Exception error) { - Log.e("AuthQuickstart", "Unexpected error: " + error); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -try { - Amplify.Auth.confirmSignIn( - "confirmation code", - { result -> - if (result.isSignedIn) { - Log.i("AuthQuickstart","Confirm signIn succeeded") - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } - ) { error -> Log.e("AuthQuickstart", "Confirm sign in failed: $error")} -} catch (error: Exception) { - Log.e("AuthQuickstart", "Unexpected error: $error") -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.confirmSignIn( - "confirmation code" - ) - if (result.isSignedIn) { - Log.i("AuthQuickstart", "Confirm signIn succeeded") - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}" - ) - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } -} catch (error: Exception) { - Log.e("AuthQuickstart", "Unexpected error: $error") -} -``` - -#### [RxJava] - -```java - -RxAmplify.Auth.confirmSignIn( - "confirmation code").subscribe( - result -> { - if (result.isSignedIn()) { - Log.i("AuthQuickstart", "Confirm signIn succeeded"); - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - }, - error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) - ); -``` - -## Confirm sign-in with TOTP MFA - -If the next step is `CONFIRM_SIGN_IN_WITH_TOTP_CODE`, you should prompt the user to enter the TOTP code from their associated authenticator app during set up. The code is a six-digit number that changes every 30 seconds. The user must enter the code before the 30-second window expires. - -After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. - -## Confirm sign-in with Email MFA - -If the next step is `CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE`, Amplify Auth has sent the user a random code to their email address and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. - - - -**Note:** The result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial email address of the recipient. - - - -## Confirm sign-in with OTP - -If the next step is `CONFIRM_SIGN_IN_WITH_OTP`, Amplify Auth has sent the user a random code to the medium of the user's choosing (e.g. SMS or email) and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. - - - -**Note:** The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial email address of the recipient, which can be used to prompt the user on where to look for the code. - - - -## Continue sign-in with MFA Selection - -If the next step is `CONTINUE_SIGN_IN_WITH_MFA_SELECTION`, the user must select the MFA method to use. Amplify Auth currently supports SMS, TOTP, and email as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. - -## Continue sign-in with Email Setup - -If the next step is `CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP`, then the user must provide an email address to complete the sign in process. Once this value has been collected from the user, call the `confirmSignIn` API to continue. - -## Continue sign-in with TOTP Setup - -If the next step is `CONTINUE_SIGN_IN_WITH_TOTP_SETUP`, then the user must provide a TOTP code to complete the sign in process. The step returns an associated value of type `TOTPSetupDetails` which would be used for generating TOTP. `TOTPSetupDetails` provides a helper method called `getSetupURI` that can be used to generate a URI, which can be used by native password managers for TOTP association. For example. if the URI is used on Apple platforms, it will trigger the platform's native password manager to associate TOTP with the account. For more advanced use cases, `TOTPSetupDetails` also contains the `sharedSecret` that will be used to either generate a QR code or can be manually entered into an authenticator app. - -Once the authenticator app is set up, the user can generate a TOTP code and provide it to the library to complete the sign in process. - -## Continue sign-in with MFA Setup Selection - -If the next step is `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION`, the user must select the MFA method to setup. Amplify Auth currently supports SMS, TOTP, and email as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. - -## Continue sign-in with First Factor Selection - -If the next step is `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, the user must select an authentication factor to use either because they did not specify one or because the one they chose is not supported (e.g. selecting SMS when they don't have a phone number registered to their account). Amplify Auth currently supports SMS, email, password, and webauthn as authentication factors. After the user selects an authentication method, your implementation must pass the selected authentication method to Amplify Auth using `confirmSignIn` API. - -Visit the [sign-in documentation](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#sign-in-with-passwordless-methods) to see examples on how to call the `confirmSignIn` API. - -## Confirm sign-in with custom challenge - -If the next step is `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the Lambda trigger you setup when you configured a [custom sign in flow](/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/#sign-in-a-user). To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the `confirmSignIn` API. - -#### [Java] - -```java -try { - Amplify.Auth.confirmSignIn( - "challenge answer", - result -> { - if (result.isSignedIn()) { - Log.i("AuthQuickstart", "Confirm signIn succeeded"); - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - }, - error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) - ); -} catch (Exception error) { - Log.e("AuthQuickstart", "Unexpected error: " + error); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -try { - Amplify.Auth.confirmSignIn( - "challenge answer", - { result -> - if (result.isSignedIn) { - Log.i("AuthQuickstart","Confirm signIn succeeded") - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } - ) { error -> - Log.e("AuthQuickstart", "Confirm sign in failed: $error") - } -} catch (error: Exception) { - Log.e("AuthQuickstart", "Unexpected error: $error") -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.confirmSignIn( - "challenge answer" - ) - if (result.isSignedIn) { - Log.i("AuthQuickstart", "Confirm signIn succeeded") - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } -} catch (error: Exception) { - Log.e("AuthQuickstart", "Unexpected error: $error") -} -``` - -#### [RxJava] - -```java - -RxAmplify.Auth.confirmSignIn( - "challenge answer").subscribe( - result -> { - if (result.isSignedIn()) { - Log.i("AuthQuickstart", "Confirm signIn succeeded"); - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - }, - error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) -); -``` - -> **Warning:** **Special Handling on `confirmSignIn`** -> -> During a confirmSignIn call if `failAuthentication=true` is returned by the Lambda the session of the request gets invalidated by cognito, a NotAuthorizedException is returned and a new signIn call is expected via Amplify.Auth.signIn -> -> ```java -NotAuthorizedException{message=Failed since user is not authorized., cause=NotAuthorizedException(message=Invalid session for the user.), recoverySuggestion=Check whether the given values are correct and the user is authorized to perform the operation.} -``` - -## Confirm sign-in with new password -If you receive a `UserNotConfirmedException` while signing in, Amplify Auth requires a new password for the user before they can proceed. Prompt the user for a new password and pass it to the `confirmSignIn` API. - -#### [Java] - -```java -try { - Amplify.Auth.confirmSignIn( - "confirmation code", - result -> { - if (result.isSignedIn()) { - Log.i("AuthQuickstart", "Confirm signIn succeeded"); - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - }, - error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) - ); -} catch (Exception error) { - Log.e("AuthQuickstart", "Unexpected error: " + error); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin - try { - Amplify.Auth.confirmSignIn( - "confirmation code", - { result -> - if (result.isSignedIn) { - Log.i("AuthQuickstart","Confirm signIn succeeded") - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}") - } - } - ) { error -> - Log.e("AuthQuickstart", "Confirm sign in failed: $error") - } -} catch (error: Exception) { - Log.e("AuthQuickstart", "Unexpected error: $error") -} -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.confirmSignIn( - "confirmation code" - ) - if (result.isSignedIn) { - Log.i("AuthQuickstart", "Confirm signIn succeeded") - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: ${result.nextStep}") - } -} catch (error: Exception) { - Log.e("AuthQuickstart", "Unexpected error: $error") -} -``` - -#### [RxJava] - -```java - -RxAmplify.Auth.confirmSignIn( - "confirmation code").subscribe( - result -> { - if (result.isSignedIn()) { - Log.i("AuthQuickstart", "Confirm signIn succeeded"); - } else { - Log.i("AuthQuickstart", "Confirm sign in not complete. There might be additional steps: " + result.getNextStep()); - } - }, - error -> Log.e("AuthQuickstart", "Confirm sign in failed: " + error) - ); -``` - -## Reset password -If you receive `PasswordResetRequiredException`, authentication flow could not proceed without resetting the password. The next step is to invoke `resetPassword` api and follow the reset password flow. - -#### [Java] - -```java -try { - Amplify.Auth.resetPassword( - "username", - result -> Log.i("AuthQuickstart", "Reset password succeeded"), - error -> Log.e("AuthQuickstart", "Reset password failed : " + error) - ); -} catch (Exception error) { - Log.e("AuthQuickstart", "Unexpected error: " + error); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -try { - Amplify.Auth.resetPassword( - "username", - { - Log.i("AuthQuickstart", "Reset password succeeded") - } - ) { error -> - Log.e("AuthQuickstart", "Reset password failed : $error") - } -} catch (error: Exception) { - Log.e("AuthQuickstart", "Unexpected error: $error") -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - Amplify.Auth.resetPassword("username") - Log.i("AuthQuickstart", "Reset password succeeded") -} catch (error: Exception) { - Log.e("AuthQuickstart", "Unexpected error: $error") -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.resetPassword( - "username").subscribe( - result -> Log.i("AuthQuickstart", "Reset password succeeded"), - error -> Log.e("AuthQuickstart", "Reset password failed : " + error) -); -``` - -## Confirm Signup - -If you receive `CONFIRM_SIGN_UP` as a next step, sign up could not proceed without confirming user information such as email or phone number. The next step is to invoke the `confirmSignUp` API and follow the confirm signup flow. - -#### [Java] - -```java - try { - Amplify.Auth.confirmSignUp( - "username", - "confirmation code", - result -> Log.i("AuthQuickstart", "Confirm signUp result completed: " + result.isSignUpComplete()), - error -> Log.e("AuthQuickstart", "An error occurred while confirming sign up: " + error) - ); -} catch (Exception error) { - Log.e("AuthQuickstart", "unexpected error: " + error); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin - try { - Amplify.Auth.confirmSignUp( - "username", - "confirmation code", - { result -> - Log.i("AuthQuickstart", "Confirm signUp result completed: ${result.isSignUpComplete}") - } - ) { error -> - Log.e("AuthQuickstart", "An error occurred while confirming sign up: $error") - } -} catch (error: Exception) { - Log.e("AuthQuickstart", "unexpected error: $error") -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.confirmSignUp( - "username", - "confirmation code" - ) - Log.i("AuthQuickstart", "Confirm signUp result completed: ${result.isSignUpComplete}") -} catch (error: Exception) { - Log.e("AuthQuickstart", "unexpected error: $error") -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.confirmSignUp( - "username", - "confirmation code").subscribe( - result -> Log.i("AuthQuickstart", "Confirm signUp result completed: " + result.isSignUpComplete()), - error -> Log.e("AuthQuickstart", "An error occurred while confirming sign up: " + error) -); -``` - -## Get Current User - -This call fetches the current logged in user and should be used after a user has been successfully signed in. -If the user is signed in, it will return the current userId and username. - - -**Note:** An empty string will be assigned to userId and/or username, if the values are not present in the accessToken. - - -#### [Java] - -```java - try { - Amplify.Auth.getCurrentUser( - result -> Log.i("AuthQuickstart", "Current user details are:" + result.toString(), - error -> Log.e("AuthQuickstart", "getCurrentUser failed with an exception: " + error) - ); - } catch (Exception error) { - Log.e("AuthQuickstart", "unexpected error: " + error); - } -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.getCurrentUser({ - Log.i("AuthQuickStart", "Current user details are: $it")},{ - Log.e("AuthQuickStart", "getCurrentUser failed with an exception: $it") -}) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.getCurrentUser() - Log.i("AuthQuickstart", "Current user details are: $result") -} catch (error: Exception) { - Log.e("AuthQuickstart", "getCurrentUser failed with an exception: $error") -} -``` - -#### [RxJava] - -```java - RxAmplify.Auth.getCurrentUser().subscribe( - result -> Log.i("AuthQuickStart getCurrentUser: " + result.toString()), - error -> Log.e("AuthQuickStart", error.toString()) - ); -``` - -## Done - -Sign In flow is complete when you get `done`. This means the user is successfully authenticated. As a convenience, the SignInResult also provides the `isSignedIn` property, which will be true if the next step is `done`. - - - -After a user has finished signup, they can proceed to sign in. Amplify Auth signin flows can be multi step processes. The required steps are determined by the configuration you provided when you define your auth resources like described on [Manage MFA Settings](/[platform]/build-a-backend/auth/concepts/multi-factor-authentication/) page. - -Depending on the configuration, you may need to call various APIs to finish authenticating a user's signin attempt. To identify the next step in a signin flow, inspect the `nextStep` parameter in the signin result. - -> **Warning:** *New enumeration values* -> -> When Amplify adds a new enumeration value (e.g., a new enum class entry or sealed class subtype in Kotlin, or a new enum value in Swift/Dart/Kotlin), it will publish a new minor version of the Amplify Library. Plugins that switch over enumeration values should include default handlers (an else branch in Kotlin or a default statement in Swift/Dart/Kotlin) to ensure that they are not impacted by new enumeration values. - -When called successfully, the signin APIs will return an `AuthSignInResult`. Inspect the `nextStep` property in the result to see if additional signin steps are required. - -```swift -func signIn(username: String, password: String) async { - do { - let signInResult = try await Amplify.Auth.signIn(username: username, password: password) - switch signInResult.nextStep { - case .confirmSignInWithSMSMFACode(let deliveryDetails, let info): - print("SMS code sent to \(deliveryDetails.destination)") - print("Additional info \(String(describing: info))") - - // Prompt the user to enter the SMSMFA code they received - // Then invoke `confirmSignIn` api with the code - - case .confirmSignInWithTOTPCode: - print("Received next step as confirm sign in with TOTP code") - - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - - case .confirmSignInWithOTP(let deliveryDetails): - print("Email code sent to \(deliveryDetails.destination)") - - // Prompt the user to enter the Email MFA code they received - // Then invoke `confirmSignIn` api with the code - - case .continueSignInWithFirstFactorSelection(let allowedFactors): - print("Received next step as continue sign in by selecting first factor") - print("Allowed factors \(allowedFactors)") - - // Prompt the user to select the first factor they want to use - // Then invoke `confirmSignIn` api with the factor - - case .confirmSignInWithPassword: - print("Received next step as confirm sign in with password") - - // Prompt the user to enter the password - // Then invoke `confirmSignIn` api with the password - - case .continueSignInWithTOTPSetup(let setUpDetails): - print("Received next step as continue sign in by setting up TOTP") - print("Shared secret that will be used to set up TOTP in the authenticator app \(setUpDetails.sharedSecret)") - - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - - case .continueSignInWithEmailMFASetup: - print("Received next step as continue sign in by setting up email MFA") - - // Prompt the user to enter the email address they wish to use for MFA - // Then invoke `confirmSignIn` api with the email address - - case .continueSignInWithMFASetupSelection(let allowedMFATypes): - print("Received next step as continue sign in by selecting MFA type to setup") - print("Allowed MFA types \(allowedMFATypes)") - - // Prompt the user to select the MFA type they want to setup - // Then invoke `confirmSignIn` api with the MFA type - - case .continueSignInWithMFASelection(let allowedMFATypes): - print("Received next step as continue sign in by selecting MFA type") - print("Allowed MFA types \(allowedMFATypes)") - - // Prompt the user to select the MFA type they want to use - // Then invoke `confirmSignIn` api with the MFA type - - case .confirmSignInWithCustomChallenge(let info): - print("Custom challenge, additional info \(String(describing: info))") - - // Prompt the user to enter custom challenge answer - // Then invoke `confirmSignIn` api with the answer - - case .confirmSignInWithNewPassword(let info): - print("New password additional info \(String(describing: info))") - - // Prompt the user to enter a new password - // Then invoke `confirmSignIn` api with new password - - case .resetPassword(let info): - print("Reset password additional info \(String(describing: info))") - - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signin flow again. - - case .confirmSignUp(let info): - print("Confirm signup additional info \(String(describing: info))") - - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - case .done: - - // Use has successfully signed in to the app - print("Signin complete") - } - } catch let error as AuthError{ - print ("Sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -The `nextStep` property is of enum type `AuthSignInStep`. Depending on its value, your code should take one of the following actions: - -## Confirm sign-in with SMS MFA -If the next step is `confirmSignInWithSMSMFACode`, Amplify Auth has sent the user a random code over SMS, and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. - -Note: the signin result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial phone number of the SMS recipient. - -#### [Async/Await] - -```swift -func confirmSignIn(confirmationCodeFromUser: String) async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: confirmationCodeFromUser) - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } catch let error as AuthError { - print("Confirm sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmSignIn(confirmationCodeFromUser: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn(challengeResponse: confirmationCodeFromUser) - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } -} -``` - -## Confirm sign-in with TOTP MFA - -If the next step is `confirmSignInWithTOTPCode`, you should prompt the user to enter the TOTP code from their associated authenticator app during set up. The code is a six-digit number that changes every 30 seconds. The user must enter the code before the 30-second window expires. - -After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. - -#### [Async/Await] - -```swift -func confirmSignIn(totpCode: String) async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: totpCode) - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } catch { - print("Confirm sign in failed \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmSignIn(totpCode: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn(challengeResponse: totpCode) - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } -} -``` - -## Confirm sign-in with Email MFA -If the next step is `confirmSignInWithOTP`, Amplify Auth has sent a random code to the user's email address, and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. - -> **Info:** **Note:** the sign-in result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial email address of the recipient. - -#### [Async/Await] - -```swift -func confirmSignIn(confirmationCodeFromUser: String) async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: confirmationCodeFromUser) - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } catch let error as AuthError { - print("Confirm sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmSignIn(confirmationCodeFromUser: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn(challengeResponse: confirmationCodeFromUser) - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } -} -``` - -## Continue sign-in with MFA Selection - -If the next step is `continueSignInWithMFASelection`, the user must select the MFA method to use. Amplify Auth currently supports SMS, TOTP, and email as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. - -#### [Async/Await] - -```swift -func confirmSignInWithTOTPAsMFASelection() async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn( - challengeResponse: MFAType.totp.challengeResponse) - - if case .confirmSignInWithTOTPCode = signInResult.nextStep { - print("Received next step as confirm sign in with TOTP") - } - - } catch { - print("Confirm sign in failed \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmSignInWithTOTPAsMFASelection() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn( - challengeResponse: MFAType.totp.challengeResponse) - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - if case .confirmSignInWithTOTPCode = signInResult.nextStep { - print("Received next step as confirm sign in with TOTP") - } - } -} -``` - -## Continue sign-in with Email Setup -If the next step is `continueSignInWithEmailMFASetup`, then the user must provide an email address to complete the sign in process. Once this value has been collected from the user, call the `confirmSignIn` API to continue. - -```swift -// Confirm sign in with Email Setup -case .continueSignInWithEmailMFASetup: - print("Received next step as continue sign in by setting up email MFA") - - // Prompt the user to enter the email address they wish to use for MFA - // Then invoke `confirmSignIn` api with the email address -``` - -## Continue sign-in with TOTP Setup - -If the next step is `continueSignInWithTOTPSetup`, then the user must provide a TOTP code to complete the sign in process. The step returns an associated value of type `TOTPSetupDetails` which would be used for generating TOTP. `TOTPSetupDetails` provides a helper method called `getSetupURI` that can be used to generate a URI, which can be used by native password managers for TOTP association. For example. if the URI is used on Apple platforms, it will trigger the platform's native password manager to associate TOTP with the account. For more advanced use cases, `TOTPSetupDetails` also contains the `sharedSecret` that will be used to either generate a QR code or can be manually entered into an authenticator app. - -Once the authenticator app is set up, the user can generate a TOTP code and provide it to the library to complete the sign in process. - -```swift -// Confirm sign in with TOTP setup -case .continueSignInWithTOTPSetup(let setUpDetails): - - /// appName parameter will help distinguish the account in the Authenticator app - let setupURI = try setUpDetails.getSetupURI(appName: ">") - - print("TOTP Setup URI: \(setupURI)") -``` - -#### [Async/Await] - -```swift -func confirmSignInWithTOTPSetup(totpCodeFromAuthenticatorApp: String) async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn( - challengeResponse: totpCodeFromAuthenticatorApp) - - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } catch { - print("Confirm sign in failed \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmSignInWithTOTPSetup(totpCodeFromAuthenticatorApp: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn( - challengeResponse: totpCodeFromAuthenticatorApp) - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } -} -``` - -## Continue sign-in with MFA Setup Selection - -If the next step is `continueSignInWithMFASetupSelection`, the user must indicate which of the available MFA methods they would like to setup. After the user selects an MFA method to setup, your implementation must pass the selected MFA method to the `confirmSignIn` API. - -#### [Async/Await] - -```swift -func continueSignInWithEmailMFASetupSelection() async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn( - challengeResponse: MFAType.email.challengeResponse) - - if case .confirmSignInWithTOTPCode = signInResult.nextStep { - print("Received next step as confirm sign in with TOTP") - } - - } catch { - print("Confirm sign in failed \(error)") - } -} -``` - -#### [Combine] - -```swift -func continueSignInWithEmailMFASetupSelection() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn( - challengeResponse: MFAType.email.challengeResponse) - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - if case .confirmSignInWithTOTPCode = signInResult.nextStep { - print("Received next step as confirm sign in with TOTP") - } - } -} -``` - -## Confirm sign-in with custom challenge - -If the next step is `confirmSignInWithCustomChallenge`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the Lambda trigger you setup when you configured a [custom sign in flow](/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/#sign-in-a-user). To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the `confirmSignIn` API. - -#### [Async/Await] - -```swift -func confirmSignIn(challengeAnswerFromUser: String) async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: challengeAnswerFromUser) - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } catch let error as AuthError { - print("Confirm sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmSignIn(challengeAnswerFromUser: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn(challengeResponse: challengeAnswerFromUser) - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } -} -``` - -> **Warning:** **Special Handling on `confirmSignIn`** -> -> During a confirmSignIn call if `failAuthentication=true` is returned by the Lambda function the session of the request gets invalidated by cognito, a NotAuthorizedException is returned and a new signIn call is expected via Amplify.Auth.signIn -> -> ```swift -Exception: notAuthorized{message=Failed since user is not authorized., cause=NotAuthorizedException(message=Invalid session for the user.), recoverySuggestion=Check whether the given values are correct and the user is authorized to perform the operation.} -``` - -## Confirm sign-in with new password - -If the next step is `confirmSignInWithNewPassword`, Amplify Auth requires a new password for the user before they can proceed. Prompt the user for a new password and pass it to the `confirmSignIn` API. - -#### [Async/Await] - -```swift -func confirmSignIn(newPasswordFromUser: String) async { - do { - let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: newPasswordFromUser) - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } catch let error as AuthError { - print("Confirm sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmSignIn(newPasswordFromUser: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn(challengeResponse: newPasswordFromUser) - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { signInResult in - if signInResult.isSignedIn { - print("Confirm sign in succeeded. The user is signed in.") - } else { - print("Confirm sign in succeeded.") - print("Next step: \(signInResult.nextStep)") - // Switch on the next step to take appropriate actions. - // If `signInResult.isSignedIn` is true, the next step - // is 'done', and the user is now signed in. - } - } -} -``` - -## Reset password - -If you receive `resetPassword`, authentication flow could not proceed without resetting the password. The next step is to invoke `resetPassword` api and follow the reset password flow. - -#### [Async/Await] - -```swift -func resetPassword(username: String) async { - do { - let resetPasswordResult = try await Amplify.Auth.resetPassword(for: username) - print("Reset password succeeded.") - print("Next step: \(resetPasswordResult.nextStep)") - } catch let error as AuthError { - print("Reset password failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func resetPassword(username: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.resetPassword(for: username) - }.sink { - if case let .failure(authError) = $0 { - print("Reset password failed \(authError)") - } - } - receiveValue: { resetPasswordResult in - print("Reset password succeeded.") - print("Next step: \(resetPasswordResult.nextStep)") - } -} -``` - -## Confirm Signup - -If you receive `confirmSignUp` as a next step, sign up could not proceed without confirming user information such as email or phone number. The next step is to invoke the `confirmSignUp` API and follow the confirm signup flow. - -#### [Async/Await] - -```swift -func confirmSignUp(for username: String, with confirmationCode: String) async { - do { - let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( - for: username, - confirmationCode: confirmationCode - ) - print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") - } catch let error as AuthError { - print("An error occurred while confirming sign up \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignUp(for: username, confirmationCode: confirmationCode) - }.sink { - if case let .failure(authError) = $0 { - print("An error occurred while confirming sign up \(authError)") - } - } - receiveValue: { _ in - print("Confirm signUp succeeded") - } -} -``` - -## Done - -Signin flow is complete when you get `done`. This means the user is successfully authenticated. As a convenience, the SignInResult also provides the `isSignedIn` property, which will be true if the next step is `done`. - - ---- - ---- -title: "Manage users" -section: "build-a-backend/auth" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-01T21:28:13.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/" ---- - - - ---- - ---- -title: "With admin actions" -section: "build-a-backend/auth/manage-users" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-02T01:41:16.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/with-admin-actions/" ---- - -Amplify Auth can be managed with the [AWS SDK's `@aws-sdk/client-cognito-identity-provider` package](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/cognito-identity-provider/). This package is intended to use server-side, and can be used within a Function. This example focuses on the `addUserToGroup` action and will be defined as a [custom mutation](/[platform]/build-a-backend/data/custom-business-logic/#step-1---define-a-custom-query-or-mutation). - -To get started, create an "ADMINS" group that will be used to authorize the mutation: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" - -export const auth = defineAuth({ - loginWith: { - email: true, - }, - // highlight-next-line - groups: ["ADMINS"] -}) -``` - -Next, create the Function resource: - -```ts title="amplify/data/add-user-to-group/resource.ts" -import { defineFunction } from "@aws-amplify/backend" - -export const addUserToGroup = defineFunction({ - name: "add-user-to-group", -}) -``` - -Then, in your auth resources, grant access for the function to perform the `addUserToGroup` action. [Learn more about granting access to auth resources](/[platform]/build-a-backend/auth/grant-access-to-auth-resources). - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" -// highlight-next-line -import { addUserToGroup } from "../data/add-user-to-group/resource" - -export const auth = defineAuth({ - loginWith: { - email: true, - }, - groups: ["ADMINS"], - // highlight-start - access: (allow) => [ - allow.resource(addUserToGroup).to(["addUserToGroup"]) - ], - // highlight-end -}) -``` - -You're now ready to define the custom mutation. Here you will use the newly-created `addUserToGroup` function resource to handle the `addUserToGroup` mutation. This mutation can only be called by a user in the "ADMINS" group. - -```ts title="amplify/data/resource.ts" -import type { ClientSchema } from "@aws-amplify/backend" -import { a, defineData } from "@aws-amplify/backend" -import { addUserToGroup } from "./resource" - -const schema = a.schema({ - addUserToGroup: a - .mutation() - .arguments({ - userId: a.string().required(), - groupName: a.string().required(), - }) - .authorization((allow) => [allow.group("ADMINS")]) - .handler(a.handler.function(addUserToGroup)) - .returns(a.json()) -}) - -export type Schema = ClientSchema - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "iam", - }, -}) -``` - -Lastly, create the function's handler using the exported client schema to type the handler function, and the generated `env` to specify the user pool ID you'd like to interact with: - -```ts title="amplify/data/add-user-to-group/handler.ts" -import type { Schema } from "../resource" -import { env } from "$amplify/env/add-user-to-group" -import { - AdminAddUserToGroupCommand, - CognitoIdentityProviderClient, -} from "@aws-sdk/client-cognito-identity-provider" - -type Handler = Schema["addUserToGroup"]["functionHandler"] -const client = new CognitoIdentityProviderClient() - -export const handler: Handler = async (event) => { - const { userId, groupName } = event.arguments - const command = new AdminAddUserToGroupCommand({ - Username: userId, - GroupName: groupName, - UserPoolId: env.AMPLIFY_AUTH_USERPOOL_ID, - }) - const response = await client.send(command) - return response -} -``` - - -In your frontend, use the generated client to call your mutation using the group name and the user's ID. - - -```ts title="src/client.ts" -import type { Schema } from "../amplify/data/resource" -import { generateClient } from "aws-amplify/data" - -const client = generateClient() - -await client.mutations.addUserToGroup({ - groupName: "ADMINS", - userId: "5468d468-4061-70ed-8870-45c766d26225", -}) -``` - - - - - - - - - - - - ---- - ---- -title: "Manage passwords" -section: "build-a-backend/auth/manage-users" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-10-14T15:02:39.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/manage-passwords/" ---- - -Amplify Auth provides a secure way for your users to change their password or recover a forgotten password. - -## Understand password default settings - -By default, your users can retrieve access to their accounts if they forgot their password by using either their phone or email. The following are the default account recovery methods used when either `phone` or `email` are used as login options. - -| Login option | User account verification channel | -| ------------------- | --------------------------------- | -| `phone` | Phone Number | -| `email` | Email | -| `email` and `phone` | Email | - -## Reset Password - -To reset a user's password, use the `resetPassword` API which will send a reset code to the destination (e.g. email or SMS) based on the user's settings. - - -```ts -import { resetPassword } from 'aws-amplify/auth'; - -const output = await resetPassword({ - username: "hello@mycompany.com" -}); - -const { nextStep } = output; -switch (nextStep.resetPasswordStep) { - case 'CONFIRM_RESET_PASSWORD_WITH_CODE': - const codeDeliveryDetails = nextStep.codeDeliveryDetails; - console.log( - `Confirmation code was sent to ${codeDeliveryDetails.deliveryMedium}` - ); - // Collect the confirmation code from the user and pass to confirmResetPassword. - break; - case 'DONE': - console.log('Successfully reset password.'); - break; -} -``` - - -```dart -Future resetPassword(String username) async { - try { - final result = await Amplify.Auth.resetPassword( - username: username, - ); - await _handleResetPasswordResult(result); - } on AuthException catch (e) { - safePrint('Error resetting password: ${e.message}'); - } -} -``` - -```dart -Future _handleResetPasswordResult(ResetPasswordResult result) async { - switch (result.nextStep.updateStep) { - case AuthResetPasswordStep.confirmResetPasswordWithCode: - final codeDeliveryDetails = result.nextStep.codeDeliveryDetails!; - _handleCodeDelivery(codeDeliveryDetails); - break; - case AuthResetPasswordStep.done: - safePrint('Successfully reset password'); - break; - } -} -``` - - - -#### [Java] - -```java -Amplify.Auth.resetPassword( - "username", - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.resetPassword("username", - { Log.i("AuthQuickstart", "Password reset OK: $it") }, - { Log.e("AuthQuickstart", "Password reset failed", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.resetPassword("username") - Log.i("AuthQuickstart", "Password reset OK: $result") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Password reset failed", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.resetPassword("username") - .subscribe( - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func resetPassword(username: String) async { - do { - let resetResult = try await Amplify.Auth.resetPassword(for: username) - switch resetResult.nextStep { - case .confirmResetPasswordWithCode(let deliveryDetails, let info): - print("Confirm reset password with code send to - \(deliveryDetails) \(String(describing: info))") - case .done: - print("Reset completed") - } - } catch let error as AuthError { - print("Reset password failed with error \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func resetPassword(username: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.resetPassword(for: username) - }.sink { - if case let .failure(authError) = $0 { - print("Reset password failed with error \(authError)") - } - } - receiveValue: { resetResult in - switch resetResult.nextStep { - case .confirmResetPasswordWithCode(let deliveryDetails, let info): - print("Confirm reset password with code send to - \(deliveryDetails) \(String(describing: info))") - case .done: - print("Reset completed") - } - } -} -``` - -Usually, resetting the password require you to verify that it is the actual user that tried to reset the password. The next step above will be `.confirmResetPasswordWithCode`. - -If you would like to display a more specific view or messaging to your users based the error that occurred, you can handle this by downcasting the `underlyingError` to `AWSCognitoAuthError`. - -```swift -if let authError = error as? AuthError, - let cognitoAuthError = authError.underlyingError as? AWSCognitoAuthError { - switch cognitoAuthError { - case .userNotFound: - print("User not found") - case .invalidParameter: - print("Invalid Parameter) - default: - break - } -} -``` - - -To complete the password reset process, invoke the `confirmResetPassword` API with the code your user received and the new password they want to set. - - -```ts -import { confirmResetPassword } from 'aws-amplify/auth'; - -await confirmResetPassword({ - username: "hello@mycompany.com", - confirmationCode: "123456", - newPassword: "hunter3", -}); -``` - - -```dart -Future confirmResetPassword({ - required String username, - required String newPassword, - required String confirmationCode, -}) async { - try { - final result = await Amplify.Auth.confirmResetPassword( - username: username, - newPassword: newPassword, - confirmationCode: confirmationCode, - ); - safePrint('Password reset complete: ${result.isPasswordReset}'); - } on AuthException catch (e) { - safePrint('Error resetting password: ${e.message}'); - } -} -``` - - - -#### [Java] - -```java -Amplify.Auth.confirmResetPassword( - "Username", - "NewPassword123", - "confirmation code you received", - () -> Log.i("AuthQuickstart", "New password confirmed"), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.confirmResetPassword("Username", "NewPassword123", "confirmation code", - { Log.i("AuthQuickstart", "New password confirmed") }, - { Log.e("AuthQuickstart", "Failed to confirm password reset", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - Amplify.Auth.confirmResetPassword("Username", "NewPassword123", "code you received") - Log.i("AuthQuickstart", "New password confirmed") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Failed to confirm password reset", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.confirmResetPassword("Username","NewPassword123", "confirmation code") - .subscribe( - () -> Log.i("AuthQuickstart", "New password confirmed"), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func confirmResetPassword( - username: String, - newPassword: String, - confirmationCode: String -) async { - do { - try await Amplify.Auth.confirmResetPassword( - for: username, - with: newPassword, - confirmationCode: confirmationCode - ) - print("Password reset confirmed") - } catch let error as AuthError { - print("Reset password failed with error \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func confirmResetPassword( - username: String, - newPassword: String, - confirmationCode: String -) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmResetPassword( - for: username, - with: newPassword, - confirmationCode: confirmationCode - ) - }.sink { - if case let .failure(authError) = $0 { - print("Reset password failed with error \(authError)") - } - } - receiveValue: { - print("Password reset confirmed") - } -} -``` - - - -## Update password - -You can update a signed in user's password using the `updatePassword` API. - - -```ts -import { updatePassword } from 'aws-amplify/auth'; - -await updatePassword({ - oldPassword: "hunter2", - newPassword: "hunter3", -}); -``` - - -```dart -Future updatePassword({ - required String oldPassword, - required String newPassword, -}) async { - try { - await Amplify.Auth.updatePassword( - oldPassword: oldPassword, - newPassword: newPassword, - ); - } on AuthException catch (e) { - safePrint('Error updating password: ${e.message}'); - } -} -``` - - - -#### [Java] - -```java -Amplify.Auth.updatePassword( - "existingPassword", - "newPassword", - () -> Log.i("AuthQuickstart", "Updated password successfully"), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.updatePassword("existingPassword", "newPassword", - { Log.i("AuthQuickstart", "Updated password successfully") }, - { Log.e("AuthQuickstart", "Password update failed", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - Amplify.Auth.updatePassword("existingPassword", "newPassword") - Log.i("AuthQuickstart", "Updated password successfully") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Password update failed", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.updatePassword("existingPassword", "newPassword") - .subscribe( - () -> Log.i("AuthQuickstart", "Updated password successfully"), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func changePassword(oldPassword: String, newPassword: String) async { - do { - try await Amplify.Auth.update(oldPassword: oldPassword, to: newPassword) - print("Change password succeeded") - } catch let error as AuthError { - print("Change password failed with error \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func changePassword(oldPassword: String, newPassword: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.update(oldPassword: oldPassword, to: newPassword) - }.sink { - if case let .failure(authError) = $0 { - print("Change password failed with error \(authError)") - } - } - receiveValue: { - print("Change password succeeded") - } -} -``` - - - -### Override default user account verification channel - -You can always change the channel used by your authentication resources by overriding the following setting. - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from '@aws-amplify/backend'; - -export const auth = defineAuth({ - loginWith: { - email: true - }, -// highlight-start - accountRecovery: 'EMAIL_ONLY' -// highlight-end -}); -``` - -## Override default password policy - -By default your password policy is set to the following: - -- `MinLength`: 8 characters -- `requireLowercase`: true -- `requireUppercase`: true -- `requireNumbers`: true -- `requireSymbols`: true -- `tempPasswordValidity`: 3 days - -You can customize the password format acceptable by your auth resource by modifying the underlying `cfnUserPool` resource: - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { auth } from './auth/resource'; - -const backend = defineBackend({ - auth, -}); -// extract L1 CfnUserPool resources -const { cfnUserPool } = backend.auth.resources.cfnResources; -// modify cfnUserPool policies directly -cfnUserPool.policies = { - passwordPolicy: { - minimumLength: 32, - requireLowercase: true, - requireNumbers: true, - requireSymbols: true, - requireUppercase: true, - temporaryPasswordValidityDays: 20, - }, -}; -``` - ---- - ---- -title: "Manage WebAuthn credentials" -section: "build-a-backend/auth/manage-users" -platforms: ["android", "angular", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-06-24T13:29:28.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/manage-webauthn-credentials/" ---- - - -> **Warning:** WebAuthn registration and authentication are not currently supported on React Native, other passwordless features are fully supported. - - -Amplify Auth uses passkeys as the credential mechanism for WebAuthn. The following APIs allow users to register, keep track of, and delete the passkeys associated with their Cognito account. - -[Learn more about using passkeys with Amplify](/[platform]/build-a-backend/auth/concepts/passwordless/#webauthn-passkey). - -## Associate WebAuthn credentials - - -> **Warning:** Registering a passkey is supported on Android 9 (API level 28) and above. - - -Note that users must be authenticated to register a passkey. That also means users cannot create a passkey during sign up; consequently, they must have at least one other first factor authentication mechanism associated with their account to use WebAuthn. - -You can associate a passkey using the following API: - - -```ts -import { associateWebAuthnCredential} from 'aws-amplify/auth'; - -await associateWebAuthnCredential(); - -``` - - - -#### [Java] - -```java -Amplify.Auth.associateWebAuthnCredential( - activity, - () -> Log.i("AuthQuickstart", "Associated credential"), - error -> Log.e("AuthQuickstart", "Failed to associate credential", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.associateWebAuthnCredential( - activity, - { Log.i("AuthQuickstart", "Associated credential") }, - { Log.e("AuthQuickstart", "Failed to associate credential", error) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.associateWebAuthnCredential(activity) - Log.i("AuthQuickstart", "Associated credential") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Failed to associate credential", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.associateWebAuthnCredential(activity) - .subscribe( - result -> Log.i("AuthQuickstart", "Associated credential"), - error -> Log.e("AuthQuickstart", "Failed to associate credential", error) - ); -``` - -You must supply an `Activity` instance so that Amplify can display the PassKey UI in your application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack). - - - -#### [Async/Await] - -```swift -func associateWebAuthNCredentials() async { - do { - try await Amplify.Auth.associateWebAuthnCredential() - print("WebAuthn credential was associated") - } catch { - print("Associate WebAuthn Credential failed: \(error)") - } -} -``` - -#### [Combine] - -```swift -func associateWebAuthNCredentials() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.associateWebAuthnCredential() - }.sink { - print("Associate WebAuthn Credential failed: \($0)") - } - receiveValue: { _ in - print("WebAuthn credential was associated") - } -} -``` - - - -The user will be prompted to register a passkey using their local authenticator. Amplify will then associate that passkey with Cognito. - -## List WebAuthn credentials - -You can list registered passkeys using the following API: - - -```ts -import { listWebAuthnCredentials } from 'aws-amplify/auth'; - -const result = await listWebAuthnCredentials(); - -for (const credential of result.credentials) { - console.log(`Credential ID: ${credential.credentialId}`); - console.log(`Friendly Name: ${credential.friendlyCredentialName}`); - console.log(`Relying Party ID: ${credential.relyingPartyId}`); - console.log(`Created At: ${credential.createdAt}`); -} - -``` - - - -#### [Async/Await] - -```swift -func listWebAuthNCredentials() async { - do { - let result = try await Amplify.Auth.listWebAuthnCredentials( - options: .init(pageSize: 5)) - - for credential in result.credentials { - print("Credential ID: \(credential.credentialId)") - print("Created At: \(credential.createdAt)") - print("Relying Party Id: \(credential.relyingPartyId)") - if let friendlyName = credential.friendlyName { - print("Friendly name: \(friendlyName)") - } - } - - // Fetch the next page - if let nextToken = result.nextToken { - let nextResult = try await Amplify.Auth.listWebAuthnCredentials( - options: .init( - pageSize: 5, - nextToken: nextToken)) - } - } catch { - print("Associate WebAuthn Credential failed: \(error)") - } -} -``` - -#### [Combine] - -```swift -func listWebAuthNCredentials() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.listWebAuthnCredentials( - options: .init(pageSize: 5)) - }.sink { - print("List WebAuthn Credential failed: \($0)") - } - receiveValue: { result in - for credential in result.credentials { - print("Credential ID: \(credential.credentialId)") - print("Created At: \(credential.createdAt)") - print("Relying Party Id: \(credential.relyingPartyId)") - if let friendlyName = credential.friendlyName { - print("Friendly name: \(friendlyName)") - } - } - - if let nextToken = result.nextToken { - // Fetch the next page - } - } -} -``` - - - - -#### [Java] - -```java -Amplify.Auth.listWebAuthnCredentials( - result -> result.getCredentials().forEach(credential -> { - Log.i("AuthQuickstart", "Credential ID: " + credential.getCredentialId()); - Log.i("AuthQuickstart", "Friendly Name: " + credential.getFriendlyName()); - Log.i("AuthQuickstart", "Relying Party ID: " + credential.getRelyingPartyId()); - Log.i("AuthQuickstart", "Created At: " + credential.getCreatedAt()); - }), - error -> Log.e("AuthQuickstart", "Failed to list credentials", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.listWebAuthnCredentials( - { result -> - result.credentials.forEach { credential -> - Log.i("AuthQuickstart", "Credential ID: ${credential.credentialId}") - Log.i("AuthQuickstart", "Friendly Name: ${credential.friendlyName}") - Log.i("AuthQuickstart", "Relying Party ID: ${credential.relyingPartyId}") - Log.i("AuthQuickstart", "Created At: ${credential.createdAt}") - } - }, - { error -> Log.e("AuthQuickstart", "Failed to list credentials", error) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.listWebAuthnCredentials() - result.credentials.forEach { credential -> - Log.i("AuthQuickstart", "Credential ID: ${credential.credentialId}") - Log.i("AuthQuickstart", "Friendly Name: ${credential.friendlyName}") - Log.i("AuthQuickstart", "Relying Party ID: ${credential.relyingPartyId}") - Log.i("AuthQuickstart", "Created At: ${credential.createdAt}") - } -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Failed to list credentials", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.listWebAuthnCredentials() - .subscribe( - result -> result.getCredentials().forEach(credential -> { - Log.i("AuthQuickstart", "Credential ID: " + credential.getCredentialId()); - Log.i("AuthQuickstart", "Friendly Name: " + credential.getFriendlyName()); - Log.i("AuthQuickstart", "Relying Party ID: " + credential.getRelyingPartyId()); - Log.i("AuthQuickstart", "Created At: " + credential.getCreatedAt()); - }), - error -> Log.e("AuthQuickstart", "Failed to list credentials", error) - ); -``` - - - -## Delete WebAuthn credentials - -You can delete a passkey with the following API: - - -```ts -import { deleteWebAuthnCredential } from 'aws-amplify/auth'; - -const id = "credential-id-to-delete"; - -await deleteWebAuthnCredential({ - credentialId: id -}); -``` - - - -#### [Async/Await] - -```swift -func deleteWebAuthNCredentials(credentialId: String) async { - do { - try await Amplify.Auth.deleteWebAuthnCredential(credentialId: credentialId) - print("WebAuthn credential was deleted") - } catch { - print("Delete WebAuthn Credential failed: \(error)") - } -} -``` - -#### [Combine] - -```swift -func deleteWebAuthNCredentials(credentialId: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.deleteWebAuthnCredential(credentialId: credentialId) - }.sink { - print("Delete WebAuthn Credential failed: \($0)") - } - receiveValue: { _ in - print("WebAuthn credential was deleted") - } -} -``` - - - - -#### [Java] - -```java -Amplify.Auth.deleteWebAuthnCredential( - credentialId, - (result) -> Log.i("AuthQuickstart", "Deleted credential"), - error -> Log.e("AuthQuickstart", "Failed to delete credential", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.deleteWebAuthnCredential( - credentialId, - { Log.i("AuthQuickstart", "Deleted credential") }, - { Log.e("AuthQuickstart", "Failed to delete credential", error) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.deleteWebAuthnCredential(credentialId) - Log.i("AuthQuickstart", "Deleted credential") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Failed to delete credential", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.deleteWebAuthnCredential(credentialId) - .subscribe( - result -> Log.i("AuthQuickstart", "Deleted credential"), - error -> Log.e("AuthQuickstart", "Failed to delete credential", error) - ); -``` - -The delete passkey API has only the required `credentialId` as input, and it does not return a value. - - - -## Practical example - -Here is a code example that uses the list and delete APIs together. In this example, the user has 3 passkeys registered. They want to list all passkeys while using a `pageSize` of 2 as well as delete the first passkey in the list. - -```ts -import { - listWebAuthnCredentials, - deleteWebAuthnCredential -} from 'aws-amplify/auth'; - -let passkeys = []; - -const result = await listWebAuthnCredentials({ pageSize: 2 }); - -passkeys.push(...result.credentials); - -const nextPage = await listWebAuthnCredentials({ - pageSize: 2, - nextToken: result.nextToken, -}); - -passkeys.push(...nextPage.credentials); - -const id = passkeys[0].credentialId; - -await deleteWebAuthnCredential({ - credentialId: id -}); -``` - - ---- - ---- -title: "Manage devices" -section: "build-a-backend/auth/manage-users" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-08-20T18:34:28.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/manage-devices/" ---- - -Amplify Auth enables you to track devices your users use for auditing, MFA, and more. Before you begin it is important to understand the terminology for device statuses: - -- **Tracked:** Every time the user signs in with a new device, the client is given the device key at the end of a successful authentication event. We use this device key to generate a salt and password verifier which is used to call the ConfirmDevice API. At this point, the device is considered to be _tracked_. Once the device is in a tracked state, you can use the Amazon Cognito console to see the time it started to be tracked, last authentication time, and other information about that device. -- **Remembered:** Remembered devices are also tracked. During user authentication, the device key and secret pair assigned to a remembered device is used to authenticate the device to verify that it is the same device that the user previously used to sign in. -- **Not Remembered:** A not-remembered device is a tracked device where Cognito has been configured to require users to "Opt-in" to remember a device, but the user has not opt-ed in to having the device remembered. This use case is used for users signing into their application from a device that they don't own. -- **Forgotten:** a forgotten device is one removed from being remembered - -> **Info:** **Note:** [device tracking and remembering](https://aws.amazon.com/blogs/mobile/tracking-and-remembering-devices-using-amazon-cognito-your-user-pools/) features are not available when using federating sign-in with external providers as devices are tracked on the upstream identity provider. These features are also not available when using Cognito's Hosted UI. - -## Remember devices - -You can remember devices using the following: - - -```ts -import { rememberDevice } from 'aws-amplify/auth'; - -await rememberDevice(); -``` - - -```dart -Future rememberCurrentDevice() async { - try { - await Amplify.Auth.rememberDevice(); - safePrint('Remember device succeeded'); - } on AuthException catch (e) { - safePrint('Remember device failed with error: $e'); - } -} -``` - - - -#### [Java] - -```java -Amplify.Auth.rememberDevice( - () -> Log.i("AuthQuickStart", "Remember device succeeded"), - error -> Log.e("AuthQuickStart", "Remember device failed with error " + error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.rememberDevice( - { Log.i("AuthQuickStart", "Remember device succeeded") }, - { Log.e("AuthQuickStart", "Remember device failed with error", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - Amplify.Auth.rememberDevice() - Log.i("AuthQuickStart", "Remember device succeeded") -} catch (error: AuthException) { - Log.e("AuthQuickStart", "Remember device failed with error", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.rememberDevice() - .subscribe( - () -> Log.i("AuthQuickStart", "Remember device succeeded"), - error -> Log.e("AuthQuickStart", "Remember device failed with error " + error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func rememberDevice() async { - do { - try await Amplify.Auth.rememberDevice() - print("Remember device succeeded") - } catch let error as AuthError { - print("Remember device failed with error \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func rememberDevice() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.rememberDevice() - }.sink { - if case let .failure(authError) = $0 { - print("Remember device failed with error \(authError)") - } - } - receiveValue: { - print("Remember device succeeded") - } -} -``` - - - -## Forget devices - -You can also forget devices but note that forgotten devices are neither remembered nor tracked. - - -```ts -import { forgetDevice } from 'aws-amplify/auth'; - -await forgetDevice(); -``` - - - -#### [Current Device] - -```dart -Future forgetCurrentDevice() async { - try { - await Amplify.Auth.forgetDevice(); - safePrint('Forget device succeeded'); - } on AuthException catch (e) { - safePrint('Forget device failed with error: $e'); - } -} -``` - -#### [Specific Device] - -```dart -// A device that was fetched via Amplify.Auth.fetchDevices() -Future forgetSpecificDevice(AuthDevice myDevice) async { - try { - await Amplify.Auth.forgetDevice(myDevice); - safePrint('Forget device succeeded'); - } on AuthException catch (e) { - safePrint('Forget device failed with error: $e'); - } -} -``` - - - - -#### [Java] - -```java -Amplify.Auth.forgetDevice( - () -> Log.i("AuthQuickStart", "Forget device succeeded"), - error -> Log.e("AuthQuickStart", "Forget device failed with error " + error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.forgetDevice( - { Log.i("AuthQuickStart", "Forget device succeeded") }, - { Log.e("AuthQuickStart", "Forget device failed with error", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - Amplify.Auth.forgetDevice() - Log.i("AuthQuickStart", "Forget device succeeded") -} catch (error: AuthException) { - Log.e("AuthQuickStart", "Forget device failed with error", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.forgetDevice() - .subscribe( - () -> Log.i("AuthQuickStart", "Forget device succeeded"), - error -> Log.e("AuthQuickStart", "Forget device failed with error " + error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func forgetDevice() async { - do { - try await Amplify.Auth.forgetDevice() - print("Forget device succeeded") - } catch let error as AuthError { - print("Forget device failed with error \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func forgetDevice() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.forgetDevice() - }.sink { - if case let .failure(authError) = $0 { - print("Forget device failed with error \(authError)") - } - } - receiveValue: { - print("Forget device succeeded") - } -} -``` - - - -## Fetch devices - -You can fetch a list of remembered devices by using the following: - - -```ts -import { fetchDevices } from 'aws-amplify/auth'; - -const output = await fetchDevices(); -``` - - -```dart -Future fetchAllDevices() async { - try { - final devices = await Amplify.Auth.fetchDevices(); - for (final device in devices) { - safePrint('Device: $device'); - } - } on AuthException catch (e) { - safePrint('Fetch devices failed with error: $e'); - } -} -``` - - - - -#### [Java] - -```java -Amplify.Auth.fetchDevices( - devices -> { - for (AuthDevice device : devices) { - Log.i("AuthQuickStart", "Device: " + device); - } - }, - error -> Log.e("AuthQuickStart", "Fetch devices failed with error: " + error.toString())); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.fetchDevices( - { devices -> - devices.forEach { Log.i("AuthQuickStart", "Device: " + it) } - }, - { Log.e("AuthQuickStart", "Fetch devices failed with error", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - Amplify.Auth.fetchDevices().forEach { device -> - Log.i("AuthQuickStart", "Device: $device") - } -} catch (error: AuthException) { - Log.e("AuthQuickStart", "Fetch devices failed with error", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.fetchDevices() - .subscribe( - device -> Log.i("AuthQuickStart", "Device: " + device); - error -> Log.e("AuthQuickStart", "Fetch devices failed with error: " + error.toString()) - ); -``` - - - - -#### [Async/Await] - -```swift -func fetchDevices() async { - do { - let fetchDeviceResult = try await Amplify.Auth.fetchDevices() - for device in fetchDeviceResult { - print(device.id) - } - } catch let error as AuthError { - print("Fetch devices failed with error \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func fetchDevices() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.fetchDevices() - }.sink { - if case let .failure(authError) = $0 { - print("Fetch devices failed with error \(authError)") - } - } - receiveValue: { fetchDeviceResult in - for device in fetchDeviceResult { - print(device.id) - } - } -} -``` - - - - -## Fetch the current device - -You can fetch the current device by using the following: - -```dart -Future fetchCurrentUserDevice() async { - try { - final device = await Amplify.Auth.fetchCurrentDevice(); - safePrint('Device: $device'); - } on AuthException catch (e) { - safePrint('Get current device failed with error: $e'); - } -} -``` - - -You can now set up devices to be remembered, forgotten, and fetched. - ---- - ---- -title: "Manage users with Amplify console" -section: "build-a-backend/auth/manage-users" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-08-06T19:20:15.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/manage-users/with-amplify-console/" ---- - -The **User management** page in the Amplify console provides a user-friendly interface for managing your application's users. You can create and manage users and groups, edit user attributes, and suspend users. - -If you have not yet created an **auth** resource, visit the [Auth setup guide](/[platform]/build-a-backend/auth/set-up-auth/). - -## Access User management - -After you've deployed your auth resource, you can access the manager on Amplify Console. - -1. Log in to the [Amplify console](https://console.aws.amazon.com/amplify/home) and choose your app. -2. Select the branch you would like to access. -3. Select **Authentication** from the left navigation bar. -4. Then, select **User management**. - -### To create a user - -1. On the **User management** page, select **Users** tab. -2. Select **Create user**. -3. In the **Create user** window, for Unique identifier enter a email address, username, or phone number. For Temporary password enter a password. -4. Choose Create user. - - - -A user can be confirmed by using the [pre-built UI components](/[platform]/build-a-backend/auth/connect-your-frontend/using-the-authenticator/) and [Amplify libraries](/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/). - - - -## To create a group - -1. On the **User management** page, choose the **Groups** tab and then choose **Create group**. -2. In the **Create group** window, for **Title** enter a name for the group. -3. Choose **Create group**. - -## To add a users to a group - -1. On the **User management** page, choose the **Groups** tab. -2. Select the name of the group to add users to. -3. Choose **Add users**. -4. In the **Add users to group** window, choose how you want to search for users to add from the **Search** menu. You can choose _Email_, _Phone number_, or _Username_. -5. Add one user or multiple users to add to the group and then choose **Add users**. - -## To delete a group - -1. On the **User management** page, choose the **Groups** tab. -2. In the **Groups** section, select the name of the group to delete. -3. Choose **Delete**. -4. A confirmation window is displayed. Enter _Delete_ and choose, **Confirm deletion**. - ---- - ---- -title: "Customize auth lifecycle" -section: "build-a-backend/auth" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-01T23:11:32.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/customize-auth-lifecycle/" ---- - - - ---- - ---- -title: "Custom auth flows" -section: "build-a-backend/auth/customize-auth-lifecycle" -platforms: ["android", "flutter", "swift"] -gen: 2 -last-updated: "2024-10-31T15:49:20.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/" ---- - - -The Auth category can be configured to perform a [custom authentication flow](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html) defined by you. The following guide shows how to setup a simple passwordless authentication flow. - -## Prerequisites -An application with Amplify libraries integrated and a minimum target of any of the following: -- **iOS 13.0**, using **Xcode 14.1** or later. -- **macOS 10.15**, using **Xcode 14.1** or later. -- **tvOS 13.0**, using **Xcode 14.3** or later. -- **watchOS 9.0**, using **Xcode 14.3** or later. -- **visionOS 1.0**, using **Xcode 15** or later. (Preview support - see below for more details.) - -For a full example, please follow the [project setup walkthrough](/[platform]/start/quickstart/). - - - -visionOS support is currently in **preview** and can be used by using the latest [Amplify Release](https://github.com/aws-amplify/amplify-swift/releases). -As new Xcode and visionOS versions are released, the support will be updated with any necessary fixes on a best effort basis. - - - - - -To use Auth in a macOS project, you'll need to enable the Keychain Sharing capability. In Xcode, navigate to **your application target** > **Signing & Capabilities** > **+ Capability**, then select **Keychain Sharing.** - -This capability is required because Auth uses the Data Protection Keychain on macOS as a platform best practice. See [TN3137: macOS keychain APIs and implementations](https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains) for more information on how Keychain works on macOS and the Keychain Sharing entitlement. - -For more information on adding capabilities to your application, see [Xcode Capabilities](https://developer.apple.com/documentation/xcode/capabilities). - - - -## Configure Auth - -The custom auth flow can be [configured manually](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). - -## Sign in a user - -Implement a UI to get the username from the user. After the user enters the username you can start the sign in flow by calling the following method: - -#### [Async/Await] - -```swift -func signIn(username: String) async { - do { - let options = AWSAuthSignInOptions(authFlowType: .customWithoutSRP) - let signInResult = try await Amplify.Auth.signIn(username: username, - options: .init(pluginOptions: options)) - if case .confirmSignInWithCustomChallenge(_) = signInResult.nextStep { - // Ask the user to enter the custom challenge. - } else { - print("Sign in succeeded") - } - } catch let error as AuthError { - print("Sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func signIn(username: String) -> AnyCancellable { - Amplify.Publisher.create { - let options = AWSAuthSignInOptions(authFlowType: .customWithoutSRP) - try await Amplify.Auth.signIn(username: username, - options: .init(pluginOptions: options)) - }.sink { - if case let .failure(authError) = $0 { - print("Sign in failed \(authError)") - } - } - receiveValue: { result in - if case .confirmSignInWithCustomChallenge(_) = result.nextStep { - // Ask the user to enter the custom challenge. - } else { - print("Sign in succeeded") - } - } -} -``` - -Since this is a custom authentication flow with a challenge, the result of the signin process has a next step `.confirmSignInWithCustomChallenge`. Implement a UI to allow the user to enter the custom challenge. - -## Confirm sign in with custom challenge - -To get a custom challenge from the user, create an appropriate UI for the user to submit the required value, and pass that value into the `confirmSignin()` API. - -#### [Async/Await] - -```swift -func customChallenge(response: String) async { - do { - _ = try await Amplify.Auth.confirmSignIn(challengeResponse: response) - print("Confirm sign in succeeded") - } catch let error as AuthError { - print("Confirm sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func customChallenge(response: String) -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.confirmSignIn(challengeResponse: response) - }.sink { - if case let .failure(authError) = $0 { - print("Confirm sign in failed \(authError)") - } - } - receiveValue: { _ in - print("Confirm sign in succeeded") - } -} -``` - -You will know the sign in flow is complete if you see the following in your console window: - -```console -Confirm sign in succeeded -``` - -### Lambda Trigger Setup - -AWS Amplify now supports creating functions as part of its new backend experience. For more information on the Functions and how to start with them check out [Functions documentation](/[platform]/build-a-backend/functions/). In addition, more information on available triggers can be found in the [Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html). - -### Custom Auth Flow with Secure Remote Password (SRP) - -Cognito User Pool allows to start the custom authentication flow with SRP as the first step. If you would like to use this flow, setup Define Auth Lambda trigger to handle SRP_A as the first challenge as shown below: - -```javascript -exports.handler = (event, context) => { - if (event.request.session.length == 1 && - event.request.session[0].challengeName == 'SRP_A') { - event.response.issueTokens = false; - event.response.failAuthentication = false; - event.response.challengeName = 'PASSWORD_VERIFIER'; - } else if (event.request.session.length == 2 && - event.request.session[1].challengeName == 'PASSWORD_VERIFIER' && - event.request.session[1].challengeResult == true) { - event.response.issueTokens = false; - event.response.failAuthentication = false; - event.response.challengeName = 'CUSTOM_CHALLENGE'; - } else if (event.request.session.length == 3 && - event.request.session[2].challengeName == 'CUSTOM_CHALLENGE' && - event.request.session[2].challengeResult == true) { - event.response.issueTokens = true; - event.response.failAuthentication = false; - } else { - event.response.issueTokens = false; - event.response.failAuthentication = true; - } - context.done(null, event); -}; -``` - -If your lambda is setup to start with `SRP` as the first step, make sure to initiate the signIn process with `customWithSRP` as the authentication flow: - -```swift -let options = AWSAuthSignInOptions( - authFlowType: .customWithSRP) -let signInResult = try await Amplify.Auth.signIn( - username: username, - password: password, - options: .init(pluginOptions: options)) -``` - - -The Auth category can be configured to perform a [custom authentication flow](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html) defined by you. The following guide shows how to setup a simple passwordless authentication flow. - -## Prerequisites - -* An Android application targeting at least Android SDK API level 24 with Amplify libraries integrated - * For a full example of creating Android project, please follow the [project setup walkthrough](/[platform]/start/quickstart/) - -## Configure Auth - -The custom auth flow can be [configured manually](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). - -## Register a user - -The flow as mentioned above requires a username and a valid email id as parameters to register a user. Invoke the following api to initiate a sign up flow. - -#### [Java] - -```java -AuthSignUpOptions options = AuthSignUpOptions.builder() - .userAttribute(AuthUserAttributeKey.email(), "my@email.com") - .build(); -Amplify.Auth.signUp("username", "Password123", options, - result -> Log.i("AuthQuickStart", "Result: " + result.toString()), - error -> Log.e("AuthQuickStart", "Sign up failed", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val options = AuthSignUpOptions.builder() - .userAttribute(AuthUserAttributeKey.email(), "my@email.com") - .build() -Amplify.Auth.signUp("username", "Password123", options, - { Log.i("AuthQuickStart", "Sign up succeeded: $it") }, - { Log.e ("AuthQuickStart", "Sign up failed", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val options = AuthSignUpOptions.builder() - .userAttribute(AuthUserAttributeKey.email(), "my@email.com") - .build() -try { - val result = Amplify.Auth.signUp("username", "Password123", options) - Log.i("AuthQuickStart", "Result: $result") -} catch (error: AuthException) { - Log.e("AuthQuickStart", "Sign up failed", error) -} -``` - -#### [RxJava] - - ```java -RxAmplify.Auth.signUp( - "username", - "Password123", - AuthSignUpOptions.builder().userAttribute(AuthUserAttributeKey.email(), "my@email.com").build()) - .subscribe( - result -> Log.i("AuthQuickStart", "Result: " + result.toString()), - error -> Log.e("AuthQuickStart", "Sign up failed", error) - ); -``` - -The next step in the sign up flow is to confirm the user. A confirmation code will be sent to the email id provided during sign up. Enter the confirmation code received via email in the `confirmSignUp` call. - -#### [Java] - -```java -Amplify.Auth.confirmSignUp( - "username", - "the code you received via email", - result -> Log.i("AuthQuickstart", result.isSignUpComplete() ? "Confirm signUp succeeded" : "Confirm sign up not complete"), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.confirmSignUp( - "username", "the code you received via email", - { result -> - if (result.isSignUpComplete) { - Log.i("AuthQuickstart", "Confirm signUp succeeded") - } else { - Log.i("AuthQuickstart","Confirm sign up not complete") - } - }, - { Log.e("AuthQuickstart", "Failed to confirm sign up", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val code = "code you received via email" - val result = Amplify.Auth.confirmSignUp("username", code) - if (result.isSignUpComplete) { - Log.i("AuthQuickstart", "Signup confirmed") - } else { - Log.i("AuthQuickstart", "Signup confirmation not yet complete") - } -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Failed to confirm signup", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.confirmSignUp("username", "the code you received via email") - .subscribe( - result -> Log.i("AuthQuickstart", result.isSignUpComplete() ? "Confirm signUp succeeded" : "Confirm sign up not complete"), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - -You will know the sign up flow is complete if you see the following in your console window: - -```console -Confirm signUp succeeded -``` - -## Sign in a user - -Implement a UI to get the username from the user. After the user enters the username you can start the sign in flow by calling the following method: - -#### [Java] - -```java -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP) - .build(); -Amplify.Auth.signIn( - "username", - "password", - options, - result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP) - .build() -Amplify.Auth.signIn( - "username", - "password", - options, - { result -> - if (result.isSignedIn) { - Log.i("AuthQuickstart", "Sign in succeeded") - } else { - Log.i("AuthQuickstart", "Sign in not complete") - } - }, - { Log.e("AuthQuickstart", "Failed to sign in", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP) - .build() -try { - val result = Amplify.Auth.signIn("username", "password", options) - if (result.isSignedIn) { - Log.i("AuthQuickstart", "Sign in succeeded") - } else { - Log.e("AuthQuickstart", "Sign in not complete") - } -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign in failed", error) -} -``` - -#### [RxJava] - -```java -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP) - .build(); -RxAmplify.Auth.signIn("username", "password", options) - .subscribe( - result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - -Since this is a custom authentication flow with a challenge, the result of the signin process has a next step `.confirmSignInWithCustomChallenge`. Implement a UI to allow the user to enter the custom challenge. - -## Confirm sign in with custom challenge - -Get the custom challenge (`1234` in this case) from the user and pass it to the `confirmSignin()` api. - -#### [Java] - -```java -Amplify.Auth.confirmSignIn( - "confirmation", - result -> Log.i("AuthQuickstart", "Confirm sign in succeeded: " + result.toString()), - error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.confirmSignIn("confirmation", - { Log.i("AuthQuickstart", "Confirm sign in succeeded: $it") }, - { Log.e("AuthQuickstart", "Failed to confirm sign in", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.confirmSignIn("confirmation") - Log.i("AuthQuickstart", "Confirm sign in succeeded: $result") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Failed to confirm signin", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.confirmSignIn("confirmation") - .subscribe( - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - -You will know the sign in flow is complete if you see the following in your console window: - -```console -Confirm sign in succeeded -``` - -### Lambda Trigger Setup - -AWS Amplify now supports creating functions as part of the AWS Amplify. For more information on the Functions and how to start with them check out [Functions documentation](/[platform]/build-a-backend/functions/). In addition, more information on available triggers can be found in the [Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html). - -### Custom Auth Flow with Secure Remote Password (SRP) - -Cognito User Pool allows to start the custom authentication flow with SRP as the first step. If you would like to use this flow, setup Define Auth Lambda trigger to handle SRP_A as the first challenge as shown below: - -```javascript -exports.handler = (event, context) => { - if (event.request.session.length == 1 && - event.request.session[0].challengeName == 'SRP_A') { - event.response.issueTokens = false; - event.response.failAuthentication = false; - event.response.challengeName = 'PASSWORD_VERIFIER'; - } else if (event.request.session.length == 2 && - event.request.session[1].challengeName == 'PASSWORD_VERIFIER' && - event.request.session[1].challengeResult == true) { - event.response.issueTokens = false; - event.response.failAuthentication = false; - event.response.challengeName = 'CUSTOM_CHALLENGE'; - } else if (event.request.session.length == 3 && - event.request.session[2].challengeName == 'CUSTOM_CHALLENGE' && - event.request.session[2].challengeResult == true) { - event.response.issueTokens = true; - event.response.failAuthentication = false; - } else { - event.response.issueTokens = false; - event.response.failAuthentication = true; - } - context.done(null, event); -}; -``` - -If your lambda is setup to start with `SRP` as the first step, make sure to initiate the signIn process with `customWithSRP` as the authentication flow: - -#### [Java] - -```java -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP) - .build(); -Amplify.Auth.signIn( - "username", - "password", - options, - result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), - error -> Log.e("AuthQuickstart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP) - .build() -Amplify.Auth.signIn( - "username", - "password", - options, - { result -> - if (result.isSignedIn) { - Log.i("AuthQuickstart", "Sign in succeeded") - } else { - Log.i("AuthQuickstart", "Sign in not complete") - } - }, - { Log.e("AuthQuickstart", "Failed to sign in", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP) - .build() -try { - val result = Amplify.Auth.signIn("username", "password", options) - if (result.isSignedIn) { - Log.i("AuthQuickstart", "Sign in succeeded") - } else { - Log.e("AuthQuickstart", "Sign in not complete") - } -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign in failed", error) -} -``` - -#### [RxJava] - -```java -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP) - .build(); -RxAmplify.Auth.signIn("username", "password", options) - .subscribe( - result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - - -The Auth category can be configured to perform a [custom authentication flow](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html) defined by you. The following guide shows how to setup a simple passwordless authentication flow. - -## Prerequisites - -Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Refer to [Flutter's supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) when targeting Web, Windows, or Linux. Additional setup is required for some target platforms. Please see the [platform setup](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#platform-setup) for more details on platform specific setup. - -## Configure Auth - -The custom auth flow can be [configured manually](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). - -## Register a user - -The flow as mentioned above requires a username and a valid email id as parameters to register a user. Invoke the following api to initiate a sign up flow. - -Because authentication flows in Cognito can be switched via your configuration, it is still required that users register with a password. - -```dart -Future signUpCustomFlow() async { - try { - final userAttributes = { - AuthUserAttributeKey.email: 'email@domain.com', - AuthUserAttributeKey.phoneNumber: '+15559101234', - // additional attributes as needed - }; - final result = await Amplify.Auth.signUp( - username: 'myusername', - password: 'mysupersecurepassword', - options: SignUpOptions(userAttributes: userAttributes), - ); - safePrint('Sign up result: $result'); - } on AuthException catch (e) { - safePrint('Error signing up: ${e.message}'); - } -} -``` - -The next step in the sign up flow is to confirm the user. A confirmation code will be sent to the email id provided during sign up. Enter the confirmation code received via email in the `confirmSignUp` call. - -```dart -Future confirmUser({ - required String username, - required String confirmationCode, -}) async { - try { - final result = await Amplify.Auth.confirmSignUp( - username: username, - confirmationCode: confirmationCode, - ); - // Check if further confirmations are needed or if - // the sign up is complete. - await _handleSignUpResult(result); - } on AuthException catch (e) { - safePrint('Error confirming user: ${e.message}'); - } -} -``` - -## Sign in a user - -Implement a UI to get the username from the user. After the user enters the username you can start the sign in flow by calling the following method: - -```dart -// Create state variables for the sign in status -var isSignedIn = false; -String? challengeHint; - -Future signInCustomFlow(String username) async { - try { - final result = await Amplify.Auth.signIn(username: username); - setState(() { - isSignedIn = result.isSignedIn; - // Get the publicChallengeParameters from your Create Auth Challenge Lambda - challengeHint = result.nextStep.additionalInfo['hint']; - }); - } on AuthException catch (e) { - safePrint('Error signing in: ${e.message}'); - } -} -``` - - - -Please note that you will be prevented from successfully calling `signIn` if a -user has already signed in and a valid session is active. You must first call -`signOut` to remove the original session. - - -## Confirm sign in with custom challenge - -To get a custom challenge from the user, create an appropriate UI for the user to submit the required value, and pass that value into the `confirmSignin()` API. - -```dart -Future confirmSignIn(String generatedNumber) async { - try { - final result = await Amplify.Auth.confirmSignIn( - /// Enter the random number generated by your Create Auth Challenge trigger - confirmationValue: generatedNumber, - ); - safePrint('Sign in result: $result'); - } on AuthException catch (e) { - safePrint('Error signing in: ${e.message}'); - } -} -``` - -Once the user provides the correct response, they should be authenticated in your application. - -> **Warning:** Special Handling on ConfirmSignIn -> -> During a `confirmSignIn` call, if `failAuthentication: true` is returned by the Lambda, the session of the request gets invalidated by Cognito, and a `NotAuthorizedException` is thrown. To recover, the user must initiate a new sign in by calling `Amplify.Auth.signIn`. -> -> Exception: `NotAuthorizedException` with a message `Invalid session for the user.` - -## Custom authentication flow with password verification - -The example in this documentation demonstrates the passwordless custom authentication flow. However, it is also possible to require that users supply a valid password as part of the custom authentication flow. - -To require a valid password, you can alter the [DefineAuthChallenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html) code to handle a `PASSWORD_VERIFIER` step: - -```js -exports.handler = async (event) => { - if ( - event.request.session.length === 1 && - event.request.session[0].challengeName === 'SRP_A' - ) { - event.response.issueTokens = false; - event.response.failAuthentication = false; - event.response.challengeName = 'PASSWORD_VERIFIER'; - } else if ( - event.request.session.length === 2 && - event.request.session[1].challengeName === 'PASSWORD_VERIFIER' && - event.request.session[1].challengeResult === true - ) { - event.response.issueTokens = false; - event.response.failAuthentication = false; - event.response.challengeName = 'CUSTOM_CHALLENGE'; - } else if ( - event.request.session.length === 3 && - event.request.session[2].challengeName === 'CUSTOM_CHALLENGE' && - event.request.session[2].challengeResult === true - ) { - event.response.issueTokens = true; - event.response.failAuthentication = false; - } else { - event.response.issueTokens = false; - event.response.failAuthentication = true; - } - - return event; -}; -``` - - ---- - ---- -title: "Email customization" -section: "build-a-backend/auth/customize-auth-lifecycle" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-08-15T17:57:11.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/customize-auth-lifecycle/email-customization/" ---- - -## Customize the Verification Email - -By default, Amplify Auth resources are scaffolded with email as the default method for your users to sign in. When you users sign up they receive a verification email to confirm their ownership of the email they specified during sign-up. Emails such as the verification email can be customized with your app's brand identity. - -To get started, change the `email` attribute of `loginWith` from `true` to an object to begin customizing its default behavior: - -```diff title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" - -export const auth = defineAuth({ - loginWith: { -- email: true, -+ email: { -+ verificationEmailStyle: "CODE", -+ verificationEmailSubject: "Welcome to my app!", -+ verificationEmailBody: (createCode) => `Use this code to confirm your account: ${createCode()}`, -+ }, - }, -}) -``` - -## Customize the Invitation Email - -In some cases, you may [set up a user account on behalf of a user in the Amplify console](/[platform]/build-a-backend/auth/manage-users/with-amplify-console/). In this case, Amplify Auth will send an invitation email to the user welcoming them to your application. This email includes a brief welcome message, along with the email address they can log in with and the temporary password you've set up for them. - -If you'd like to customize that email, you can override the `userInvitation` attribute of the `email` object: - -```diff title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" - -export const auth = defineAuth({ - loginWith: { -- email: true, -+ email: { -+ // can be used in conjunction with a customized welcome email as well -+ verificationEmailStyle: "CODE", -+ verificationEmailSubject: "Welcome to my app!", -+ verificationEmailBody: (createCode) => `Use this code to confirm your account: ${createCode()}`, -+ userInvitation: { -+ emailSubject: "Welcome to my app!", -+ emailBody: (user, code) => -+ `We're happy to have you! You can now login with username ${user()} and temporary password ${code()}`, -+ }, -+ }, - }, -}) -``` - -Note that when using the `user` and `code` arguments of the `emailBody` function, `user` and `code` are **functions** which must be called. Failure to call them will result in an error when your sandbox deploys. - ---- - ---- -title: "Triggers" -section: "build-a-backend/auth/customize-auth-lifecycle" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-03T17:21:51.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/customize-auth-lifecycle/triggers/" ---- - -Amplify Auth's behavior can be customized through the use of triggers. A trigger is defined as a Function, and is a mechanism to slot some logic to execute during the authentication flow. For example, you can use triggers to [validate whether emails include an allowlisted domain](/[platform]/build-a-backend/functions/examples/email-domain-filtering), [add a user to a group upon confirmation](/[platform]/build-a-backend/functions/examples/add-user-to-group), or [create a "UserProfile" model upon account confirmation](/[platform]/build-a-backend/functions/examples/create-user-profile-record). - -Triggers translate to [Cognito user pool Lambda triggers](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html). - -> When you have a Lambda trigger assigned to your user pool, Amazon Cognito interrupts its default flow to request information from your function. Amazon Cognito generates a JSON event and passes it to your function. The event contains information about your user's request to create a user account, sign in, reset a password, or update an attribute. Your function then has an opportunity to take action, or to send the event back unmodified. - -To get started, define a function and specify the `triggers` property on your auth resource: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" - -export const auth = defineAuth({ - loginWith: { - email: true, - }, - // highlight-next-line - triggers: {} -}) -``` - -To learn more about use cases for triggers, visit the [Functions examples](/[platform]/build-a-backend/functions/examples/). - ---- - ---- -title: "Enable sign-in with web UI" -section: "build-a-backend/auth" -platforms: ["flutter", "swift", "android"] -gen: 2 -last-updated: "2025-12-16T19:28:39.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/sign-in-with-web-ui/" ---- - - -## Prerequisites -* An app set up according to the [getting started walkthrough](/[platform]/build-a-backend/auth/set-up-auth/) - -> **Warning:** When configuring social sign-in, it's important to exercise caution when designating attributes as "required." Different social identity providers have varied scopes in terms of the information they respond back to Cognito with. User pool attributes that are initially set up as "required" cannot be changed later, and may require you to migrate the users or create a new user pool. - -## Configure Auth Category - - - -This library's Cognito plugin currently supports the [Authorization Code Grant](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html) OAuth Flow. - - - -In your `auth/resource.ts` file, update the -```ts -export const auth = defineAuth({ - loginWith: { - email: true, - externalProviders: { - callbackUrls: ["myapp://callback/"], - logoutUrls: ["myapp://signout/"], - }, - }, -}); -``` - -## Update AndroidManifest.xml - -Add the following activity and queries tag to your app's `AndroidManifest.xml` file, replacing `myapp` with -your redirect URI prefix if necessary: - -```xml - - ... - - - - - - - - - ... - -``` - -## Launch Web UI Sign In - -Sweet! You're now ready to launch sign in with web UI. For now, just add this method to the `onCreate` method of MainActivity: - -#### [Java] - -```java -Amplify.Auth.signInWithWebUI( - this, - result -> Log.i("AuthQuickStart", result.toString()), - error -> Log.e("AuthQuickStart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.signInWithWebUI( - this, - { Log.i("AuthQuickStart", "Signin OK = $it") }, - { Log.e("AuthQuickStart", "Signin failed", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.signInWithWebUI(this) - Log.i("AuthQuickStart", "Signin OK: $result") -} catch (error: AuthException) { - Log.e("AuthQuickStart", "Signin failed", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.signInWithWebUI(this) - .subscribe( - result -> Log.i("AuthQuickStart", result.toString()), - error -> Log.e("AuthQuickStart", error.toString()) - ); -``` - -### Additional options during signIn - -You may pass additional parameters to the `Amplify.Auth.signInWithWebUI` which are added as query parameters in the requests to [Cognito authorization endpoint](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html). - -```kotlin -val options = AWSCognitoAuthWebUISignInOptions.builder() - .nonce("randomUUID") - .language("en") - .loginHint("username") - .prompt(AuthWebUIPrompt.LOGIN, AuthWebUIPrompt.CONSENT) - .resource("https://localhost") - .build() - -Amplify.Auth.signInWithWebUI( - this, - options, - result -> Log.i("AuthQuickStart", result.toString()), - error -> Log.e("AuthQuickStart", error.toString()) -); -``` - - -## Prerequisites - -> **Warning:** **Note:** Social sign-in (OAuth) functionality is only available in **iOS**, **macOS** and **visionOS**. -> -> When configuring social sign-in, it's important to exercise caution when designating attributes as "required." Different social identity providers have varied scopes in terms of the information they respond back to Cognito with. User pool attributes that are initially set up as "required" cannot be changed later, and may require you to migrate the users or create a new user pool. - -For a full example, please follow the [project setup walkthrough](/[platform]/start/quickstart/). - - - -To use Auth in a macOS project, you'll need to enable the Keychain Sharing capability. In Xcode, navigate to **your application target** > **Signing & Capabilities** > **+ Capability**, then select **Keychain Sharing.** - -This capability is required because Auth uses the Data Protection Keychain on macOS as a platform best practice. -See [TN3137: macOS keychain APIs and implementations](https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains) for more information on how Keychain works on macOS and the Keychain Sharing entitlement. - -For more information on adding capabilities to your application, see [Xcode Capabilities](https://developer.apple.com/documentation/xcode/capabilities). - - - -## Configure Auth Category - - - -This library's Cognito plugin currently supports the [Authorization Code Grant](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html) OAuth Flow. - - - -Update the `auth/resource.ts` file like the following to enable the sign-in and sign-out capabilities with a web ui. - -```ts -export const auth = defineAuth({ - loginWith: { - email: true, - externalProviders: { - callbackUrls: ["myapp://callback/"], - logoutUrls: ["myapp://signout/"], - }, - }, -}); -``` -## Update Info.plist - -Signin with web UI require the Amplify plugin to show up the signin UI inside a webview. After the signin process is complete it will redirect back to your app. -You have to enable this in your app's `Info.plist`. Right click Info.plist and then choose Open As > Source Code. Add the following entry in the URL scheme: - -```xml - - - - - - - - - CFBundleURLTypes - - - CFBundleURLSchemes - - myapp - - - - - - -``` - -When creating a new SwiftUI app using Xcode 13 no longer require configuration files such as the Info.plist. If you are missing this file, click on the project target, under Info, Url Types, and click '+' to add a new URL Type. Add `myapp` to the URL Schemes. You should see the Info.plist file now with the entry for CFBundleURLSchemes. - -## Launch Web UI Sign In - -You're now ready to launch sign in with web UI. The `signInWithWebUI` api require a presentationAnchor and for an iOS app it will be the main UIWindow of the app. The example code below assume that you are in a UIViewController where you can fetch the UIWindow instance by `self.view.window`. - -#### [Async/Await] - -```swift -func signInWithWebUI() async { - do { - let signInResult = try await Amplify.Auth.signInWithWebUI(presentationAnchor: self.view.window!) - if signInResult.isSignedIn { - print("Sign in succeeded") - } - } catch let error as AuthError { - print("Sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func signInWithWebUI() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.signInWithWebUI(presentationAnchor: self.view.window!) - }.sink { - if case let .failure(authError) = $0 { - print("Sign in failed \(authError)") - } - } - receiveValue: { signInResult in - if signInResult.isSignedIn { - print("Sign in succeeded") - } - } -} -``` - -### Prefer private session during signIn - -Starting Amplify 1.6.0, `Amplify.Auth.signInWithWebUI` automatically uses [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) internally for iOS 13.0+. For older iOS versions, it will fall back to [SFAuthenticationSession](https://developer.apple.com/documentation/safariservices/sfauthenticationsession). -This release also introduces a new `preferPrivateSession` flag to `AWSAuthWebUISignInOptions` during the sign in flow. If `preferPrivateSession` is set to `true` during sign in, the user will not see a web view displayed when they sign out. `preferPrivateSession` will set [ASWebAuthenticationSession.prefersEphemeralWebBrowserSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/3237231-prefersephemeralwebbrowsersessio) internally and the authentication session will be private if the user's preferred browser supports it. - -```swift -try await Amplify.Auth.signInWithWebUI( - presentationAnchor: self.view.window!, - options: .preferPrivateSession() -) { - ... -} -``` - -### Additional options during signIn - -You may pass additional parameters to the `Amplify.Auth.signInWithWebUI` which are added as query parameters in the requests to [Cognito authorization endpoint](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html). - -```swift -let options = AuthWebUISignInRequest.Options( - pluginOptions: AWSAuthWebUISignInOptions.init( - nonce: "randomUUID", - language: "en", - loginHint: "username", - prompt: [.login, .consent], - resource: "http://localhost")) - -let signInResult = try await Amplify.Auth.signInWithWebUI( - presentationAnchor: self.view.window!, - options: options) -``` - - -## Prerequisites - -* An app set up according to the [getting started walkthrough](/[platform]/build-a-backend/auth/set-up-auth/) - -> **Warning:** When configuring Social sign-in, it's important to exercise caution when designating attributes as "required." Different social identity providers have varied scopes in terms of the information they respond back to Cognito with. User pool attributes that are initially set up as "required" cannot be changed later, and may require you to migrate the users or create a new user pool. - -## Configure Auth Category - - - -This library's Cognito plugin currently supports the [Authorization Code Grant](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html) OAuth Flow. - - - -Update the `auth/resource.ts` file like the following to enable the sign-in and sign-out capabilities with a web ui. - -```ts -export const auth = defineAuth({ - loginWith: { - email: true, - externalProviders: { - callbackUrls: ["myapp://callback/"], - logoutUrls: ["myapp://signout/"], - }, - }, -}); -``` - -## How It Works - -Sign-in with web UI will display the sign-in UI inside a webview. After the sign-in process is complete, the sign-in UI will redirect back to your app. - -## Platform Setup - -### Web - -To use Hosted UI in your Flutter web application locally, you must run the app with the `--web-port=3000` argument (with the value being whichever port you assigned to localhost host when configuring your redirect URIs). - -### Android - -Add the following `queries` element to the `AndroidManifest.xml` file in your app's `android/app/src/main` directory, as well as the following `intent-filter` to the `MainActivity` in the same file. - -Replace `myapp` with your redirect URI scheme as necessary: - -```xml - - - - - - - ... - - - - - - - - - ... - -``` - -### macOS - -Open XCode and enable the App Sandbox capability and then select "Incoming Connections (Server)" under "Network". - -![Incoming Connections setting selected in the App Sandbox section of the runner signing and capabilities tab.](/images/project-setup/flutter/mac/xcode-entitlements.png) - -### iOS, Windows and Linux - -No specific platform configuration is required. - -## Launch Web UI Sign In - -You're now ready to launch sign in with web UI. - -```dart -Future signInWithWebUI() async { - try { - final result = await Amplify.Auth.signInWithWebUI(); - safePrint('Sign in result: $result'); - } on AuthException catch (e) { - safePrint('Error signing in: ${e.message}'); - } -} -``` - -You can also specify a provider with the `provider` attribute: - -```dart -Future signInWithWebUIProvider() async { - try { - final result = await Amplify.Auth.signInWithWebUI( - provider: AuthProvider.google, - ); - safePrint('Result: $result'); - } on AuthException catch (e) { - safePrint('Error signing in: ${e.message}'); - } -} -``` - -Amplify Flutter currently supports the following social sign-in providers: - * Google - * Facebook - * Login With Amazon - * Apple - -### Prefer private session during signIn on iOS. - -Amplify.Auth.signInWithWebUI uses [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) internally for iOS. ASWebAuthenticationSession has a property, [prefersEphemeralWebBrowserSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/3237231-prefersephemeralwebbrowsersessio) which can be used to indicate whether the session should ask the browser for a private authentication session. To set this flag to true, set `preferPrivateSession` to true using `CognitoSignInWithWebUIPluginOptions`. - -This will bypass the permissions dialog that is displayed to the end user during sign in and sign out. However, it will also prevent reuse of existing sessions from the user's browser. For example, if the user is logged into Google in their browser and try to sign in using Google in your app, they would now need to re-enter their credentials. - -```dart -Future signInWithWebUIAndPrivateSession() async { - await Amplify.Auth.signInWithWebUI( - options: const SignInWithWebUIOptions( - pluginOptions: CognitoSignInWithWebUIPluginOptions( - isPreferPrivateSession: true, - ), - ), - ); -} -``` - -### Additional options during signIn - -You may pass additional parameters to the `Amplify.Auth.signInWithWebUI` which are added as query parameters in the requests to [Cognito authorization endpoint](https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html). - -```dart -Future signInWithWebUIAndOptions() async { - try { - final result = await Amplify.Auth.Amplify.Auth.signInWithWebUI( - options: SignInWithWebUIOptions(pluginOptions: CognitoSignInWithWebUIPluginOptions( - nonce: 'randomUUID', - language: 'en', - loginHint: 'username', - prompt: List.from([CognitoSignInWithWebUIPrompt.login, CognitoSignInWithWebUIPrompt.consent]), - resource: 'http://localhost' - ) - ) - ); - safePrint('Sign in result: $result'); - } on AuthException catch (e) { - safePrint('Error signing in: ${e.message}'); - } -} -``` - - ---- - ---- -title: "Uninstalling the app" -section: "build-a-backend/auth" -platforms: ["swift", "android"] -gen: 2 -last-updated: "2024-05-02T22:35:36.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/app-uninstall/" ---- - - -Some Amplify categories such as Analytics and Auth persist data to the local device. This application data is removed when a user uninstalls the application from the device. - -If the [Android Auto Backup for Apps](https://developer.android.com/guide/topics/data/autobackup) service was enabled, this service will attempt to restore application data. - -Amplify Auth uses [EncryptedSharedPreferences](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences) when persisting auth data. When an application is uninstalled, the [Android Keystore](https://developer.android.com/training/articles/keystore) keys used to create our EncryptedSharedPreferences files are deleted. Upon an application re-install, these restored files are no longer readable due to the key removal from the Android Keystore. - -Due to this limitation with EncryptedSharedPreferences, Auth information can’t be restored on an application re-install. The user will have to re-authenticate. - - - -Some Amplify categories such as Analytics and Auth persist data to the local device. Some of that data is automatically removed when a user uninstalls the app from the device. - -Amplify stores Auth information in the local [system keychain](https://developer.apple.com/documentation/security/keychain_services), which does not guarantee any particular behavior around whether data is removed when an app is uninstalled. - -Deciding on when to clear this auth information is not something that the SDK can do in a generic way, so App developers should decide when to clear the data by signing out. One strategy for accomplishing this would be to use [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) to detect whether or not the app is launching for the first time, and invoking [`Auth.signOut()`](/[platform]/build-a-backend/auth/connect-your-frontend/sign-out/) if the app has not been launched before. - - ---- - ---- -title: "Data usage policy information" -section: "build-a-backend/auth" -platforms: ["swift"] -gen: 2 -last-updated: "2024-05-02T22:35:36.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/data-usage-policy/" ---- - - -Apple requires app developers to provide the data usage policy of the app when they submit their app to the App Store. See Apple's [User privacy and data use](https://developer.apple.com/app-store/user-privacy-and-data-use/) for more details. The Amplify Library is used to interact with AWS resources under the developer’s ownership and management. The library cannot predict the usage of its APIs and it is up to the developer to provide the privacy manifest that accurately reflects the data collected by the app. Below are the different categories identified by Apple and the corresponding data type used by the Amplify Library. - -By utilizing the library, Amplify gathers API usage metrics from the AWS services accessed. This process involves adding a user agent to the request made to your AWS service. The user-agent header is included with information about the Amplify Library version, operating system name, and version. AWS collects this data to generate metrics related to our library usage. This information is not linked to the user’s identity and not used for tracking purposes as described in Apple's privacy and data use guidelines. - -Should you have any specific concerns or require additional information for the enhancement of your privacy manifest, please don't hesitate to reach out. - -## Contact info - -| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | -| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | -| **Name** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | βœ… | -| **Email Address** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | βœ… | -| **Phone Number** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | βœ… | - -## User Content - -| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | -| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | -| **Photos or Videos** | | | | | | -| | Storage | App Functionality | ❌ | ❌ | βœ… | -| | Predictions | App Functionality | ❌ | ❌ | βœ… | -| **Audio Data** | | | | | | -| | Predictions | App Functionality | ❌ | ❌ | βœ… | - -## Identifiers - -| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | -| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | -| **User ID** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| | Analytics | Analytics | βœ… | ❌ | ❌ | -| **Device ID** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| | Analytics | Analytics | βœ… | ❌ | ❌ | - -## Other Data - -| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | -| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | -| **OS Version** | | | | | | -| | All categories | Analytics | ❌ | ❌ | ❌ | -| **OS Name** | | | | | | -| | All categories | Analytics | ❌ | ❌ | ❌ | -| **Locale Info** | | | | | | -| | All categories | Analytics | ❌ | ❌ | ❌ | -| **App Version** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Min OS target of the app** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Timezone information** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Network information** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Has SIM card** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Cellular Carrier Name** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Device Model** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Device Name** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Device OS Version** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Device Height and Width** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Device Language** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **identifierForVendor** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | - -## Health and Fitness -No data is collected - -## Financial Info -No data is collected - -## Location -No data is collected - -## Sensitive Info -No data is collected - -## Contacts -No data is collected - -## Browsing History -No data is collected - -## Search History -No data is collected - -## Diagnostics -No data is collected - - -Some Amplify categories such as Analytics and Auth persist data to the local device. Some of that data is automatically removed when a user uninstalls the app from the device. - -Amplify stores Auth information in the local [system keychain](https://developer.apple.com/documentation/security/keychain_services), which does not guarantee any particular behavior around whether data is removed when an app is uninstalled. - -Deciding on when to clear this auth information is not something that the SDK can do in a generic way, so App developers should decide when to clear the data by signing out. One strategy for accomplishing this would be to use [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) to detect whether or not the app is launching for the first time, and invoking [`Auth.signOut()`](/[platform]/build-a-backend/auth/connect-your-frontend/sign-out/) if the app has not been launched before. - ---- - ---- -title: "Examples" -section: "build-a-backend/auth" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-10-18T22:15:30.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/examples/" ---- - - - ---- - ---- -title: "Microsoft Entra ID (SAML)" -section: "build-a-backend/auth/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-02-28T16:21:52.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/examples/microsoft-entra-id-saml/" ---- - -Microsoft Entra ID can be configured as a SAML provider for use with Amazon Cognito. Integrating Entra ID enables you to sign in with your existing enterprise users, and maintain profiles unique to the Amplify Auth resource for use within your Amplify app. To learn more, visit the [Azure documentation for SAML authentication with Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/architecture/auth-saml). - -> **Info:** **Note:** the following guidance showcases configuration with your [personal cloud sandbox](/[platform]/deploy-and-host/sandbox-environments/setup/). You will need to repeat the configuration steps for branch deployments after confirming functionality against your sandbox. - -## Start your personal cloud sandbox - -To get started, define your auth resource with the appropriate redirect URIs: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" - -/** - * Define and configure your auth resource - * @see https://docs.amplify.aws/gen2/build-a-backend/auth - */ -export const auth = defineAuth({ - loginWith: { - email: true, - externalProviders: { - logoutUrls: ["http://localhost:3000/come-back-soon"], - callbackUrls: ["http://localhost:3000/profile"], - }, - }, -}) -``` - -Deploy to your personal cloud sandbox with `npx ampx sandbox`. This will generate a domain you can use to configure your new Entra ID App. After deploying your changes successfully, copy the generated `domain` value from `amplify_outputs.json` - -```json title="amplify_outputs.json" -{ - "auth": { - "aws_region": "us-east-1", - "user_pool_id": "", - "user_pool_client_id": "", - "identity_pool_id": "", - "mfa_methods": [], - "standard_required_attributes": [ - "email" - ], - "username_attributes": [ - "email" - ], - "user_verification_types": [ - "email" - ], - "mfa_configuration": "OFF", - "password_policy": { - "min_length": 8, - "require_numbers": true, - "require_lowercase": true, - "require_uppercase": true, - "require_symbols": true - }, - "oauth": { - "identity_providers": [], - "redirect_sign_in_uri": [ - "http://localhost:3000/profile" - ], - "redirect_sign_out_uri": [ - "http://localhost:3000/come-back-soon" - ], - "response_type": "code", - "scopes": [ - "phone", - "email", - "openid", - "profile", - "aws.cognito.signin.user.admin" - ], - // highlight-next-line - "domain": ".auth.us-east-1.amazoncognito.com" - }, - }, - "version": "1" -} -``` - -## Set up Microsoft Entra ID - -Next, navigate to [portal.azure.com](https://portal.azure.com/), select **Entra ID**. In your default directory, or company's existing directory, under **Manage**, select **Enterprise Applications** - -![Entra ID default directory page highlighting Enterprise Applications](/images/auth/examples/microsoft-entra-id-saml/entra-id-select-enterprise-applications.png) - -Afterwards, select **New application**, then select **Create your own application**. Specify a name for the application and choose **Integrate any other application you don't find in the gallery (Non-gallery)** - -![Azure portal creating a new enterprise application for Entra ID](/images/auth/examples/microsoft-entra-id-saml/entra-id-new-enterprise-application.png) - -Now that you have created the new enterprise application you can begin to configure Single Sign-on with SAML. Select **Single sign-on** - -![Entra ID enterprise application highlighting "single sign-on"](/images/auth/examples/microsoft-entra-id-saml/entra-id-select-single-sign-on.png) - -Then select **SAML** - -![Entra ID enterprise application single sign-on setup highlighting "SAML"](/images/auth/examples/microsoft-entra-id-saml/entra-id-select-saml.png) - -You will be directed to a page to set up single sign-on with SAML, which needs a few pieces of information from your Amplify Auth resource. - -![Entra ID set up single sign-on page with a form requiring an entity ID and reply URL](/images/auth/examples/microsoft-entra-id-saml/entra-id-set-up-saml.png) - -In the **Basic SAML Configuration** step, select **Edit** and populate with the appropriate values. - -| Label | Value | -|-------|-------| -| Identifier (Entity ID) | `urn:amazon:cognito:sp:` | -| Reply URL (Assertion Consumer Service URL) | `https:///saml2/idpresponse` | -| Logout Url (Optional) | `https:///saml2/logout` | - -> **Info:** **Note:** Amazon Cognito redirect URIs for SAML providers follow the convention: -> -> ```text showLineNumbers={false} -https://.auth..amazoncognito.com/saml2/ -``` -> -> If you are using a custom domain the route remains the same: `/saml2/`. [Learn more about configuring Amazon Cognito with SAML identity providers](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-saml-idp.html) - -> **Warning:** **Warning:** there is a known limitation where upstream sign-out functionality successfully signs out of Entra ID, but fails to redirect back to the user app. This behavior is disabled by default with SAML integrations in Amplify Auth. - -Save the configuration and proceed to Step 3's **SAML Certificates** section. Copy the **App Federation Metadata Url** - -![Entra ID set up single sign-on page highlighting the app federation metadata URL](/images/auth/examples/microsoft-entra-id-saml/entra-id-copy-federation-url.png) - -## Configure your backend with Entra ID - -Now that you've configured your SAML provider with Microsoft Entra ID and copied the **App Federation Metadata Url**, configure your auth resource with the new SAML provider and paste the URL value into the `metadataContent` property: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" - -/** - * Define and configure your auth resource - * @see https://docs.amplify.aws/gen2/build-a-backend/auth - */ -export const auth = defineAuth({ - loginWith: { - email: true, - externalProviders: { - saml: { - name: "MicrosoftEntraIDSAML", - metadata: { - metadataType: "URL", - metadataContent: "", - }, - attributeMapping: { - email: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", - }, - }, - logoutUrls: ["http://localhost:3000/come-back-soon"], - callbackUrls: ["http://localhost:3000/profile"], - }, - }, -}) -``` - -User attributes can be found in Step 2's **Attributes & Claims** section, and are prefixed with a namespace by default. The example above shows mapping the default claim for the SAML user's email address, however additional attributes can be mapped. - -## Optionally upload the Cognito Signing Certificate - -In the AWS Console, navigate to your Cognito User Pool. Select the identity provider, **MicrosoftEntraIDSAML**, created after configuring Amplify Auth with the Entra ID SAML provider. Select **View signing certificate** and **download as .crt** - -![Amazon Cognito console highlighting "view signing certificate" for SAML provider](/images/auth/examples/microsoft-entra-id-saml/cognito-view-signing-certificate.png) - -Rename the file extension to `.cer` in order to upload to Azure. On the **Single sign-on** page, scroll down to **Step 3** (**SAML Certificates**), and under **Verification Certificates (optional)**, select **Edit**. - -![Entra ID single sign-on page highlighting "edit" for verification certificates](/images/auth/examples/microsoft-entra-id-saml/entra-id-edit-verification-certificate.png) - -Select **Require verification certificates** and upload the certificate. - -![Entra ID verification certificate upload pane](/images/auth/examples/microsoft-entra-id-saml/entra-id-upload-verification-certificate.png) - -Save your changes, and now requests to Entra ID from your Cognito User Pool will be verified. - -## Connect your frontend - -Now your users are ready to sign in with Microsoft Entra ID. To sign in with this custom provider, specify the provider name as the name specified in your auth resource definition: `MicrosoftEntraIDSAML` - - -```ts title="main.ts" -import { signInWithRedirect } from "aws-amplify/auth" - -signInWithRedirect({ - provider: { custom: "MicrosoftEntraIDSAML" } -}) -``` - - - -#### [Java] - -```java -Amplify.Auth.signInWithSocialWebUI( - AuthProvider.custom("MicrosoftEntraIDSAML") - this, - result -> Log.i("AuthQuickStart", result.toString()), - error -> Log.e("AuthQuickStart", error.toString()) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Auth.signInWithSocialWebUI( - AuthProvider.custom("MicrosoftEntraIDSAML"), - this, - { Log.i("AuthQuickstart", "Sign in OK: $it") }, - { Log.e("AuthQuickstart", "Sign in failed", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Auth.signInWithSocialWebUI(AuthProvider.custom("MicrosoftEntraIDSAML"), this) - Log.i("AuthQuickstart", "Sign in OK: $result") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign in failed", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Auth.signInWithSocialWebUI(AuthProvider.custom("MicrosoftEntraIDSAML"), this) - .subscribe( - result -> Log.i("AuthQuickstart", result.toString()), - error -> Log.e("AuthQuickstart", error.toString()) - ); -``` - - - -```dart -Future signInWithMicrosoftEntraID() async { - try { - final result = await Amplify.Auth.signInWithWebUI( - provider: AuthProvider.custom("MicrosoftEntraIDSAML"), - ); - safePrint('Sign in result: $result'); - } on AuthException catch (e) { - safePrint('Error signing in: ${e.message}'); - } -} -``` - - - -#### [Async/Await] - -```swift -func signInWithMicrosoftEntraID() async { - do { - let signInResult = try await Amplify.Auth.signInWithWebUI( - for: AuthProvider.custom("MicrosoftEntraIDSAML"), - presentationAnchor: self.view.window! - ) - if signInResult.isSignedIn { - print("Sign in succeeded") - } - } catch let error as AuthError { - print("Sign in failed \(error)") - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func signInWithMicrosoftEntraID() -> AnyCancellable { - Amplify.Publisher.create { - try await Amplify.Auth.signInWithWebUI( - for: AuthProvider.custom("MicrosoftEntraIDSAML"), - presentationAnchor: self.view.window! - ) - } - .sink { completion in - if case let .failure(authError) = completion { - print("Sign in failed \(authError)") - } - } receiveValue: { signInResult in - if signInResult.isSignedIn { - print("Sign in succeeded") - } - } -} -``` - - - ---- - ---- -title: "Grant access to auth resources" -section: "build-a-backend/auth" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-07-12T17:36:34.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/grant-access-to-auth-resources/" ---- - -Amplify Auth can be defined with an `access` property, which allows other resources to interact with auth by specifying actions. - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" -import { addUserToGroup } from "../functions/add-user-to-group/resource" - -/** - * Define and configure your auth resource - * @see https://docs.amplify.aws/gen2/build-a-backend/auth - */ -export const auth = defineAuth({ - loginWith: { - email: true, - }, - access: (allow) => [ - allow.resource(addUserToGroup).to(["addUserToGroup"]) - ], -}) -``` - - - -When you grant a function access to another resource in your Amplify backend it will configure environment variables for that function to make SDK calls to the AWS services it has access to. Those environment variables are typed and available as part of the `env` object. - - - -## List of actions - -|Action Name|Description|Cognito IAM Actions| -|-|-|-| -|manageUsers | Grants CRUD access to users in the UserPool |
    • cognito-idp:AdminConfirmSignUp
    • cognito-idp:AdminCreateUser
    • cognito-idp:AdminDeleteUser
    • cognito-idp:AdminDeleteUserAttributes
    • cognito-idp:AdminDisableUser
    • cognito-idp:AdminEnableUser
    • cognito-idp:AdminGetUser
    • cognito-idp:AdminListGroupsForUser
    • cognito-idp:AdminRespondToAuthChallenge
    • cognito-idp:AdminSetUserMFAPreference
    • cognito-idp:AdminSetUserSettings
    • cognito-idp:AdminUpdateUserAttributes
    • cognito-idp:AdminUserGlobalSignOut
    | -|manageGroupMembership | Grants permission to add and remove users from groups |
    • cognito-idp:AdminAddUserToGroup
    • cognito-idp:AdminRemoveUserFromGroup
    | -|manageGroups | Grants CRUD access to groups in the UserPool |
    • cognito-idp:GetGroup
    • cognito-idp:ListGroups
    • cognito-idp:CreateGroup
    • cognito-idp:DeleteGroup
    • cognito-idp:UpdateGroup
    | -|manageUserDevices | Manages devices registered to users|
    • cognito-idp:AdminForgetDevice
    • cognito-idp:AdminGetDevice
    • cognito-idp:AdminListDevices
    • cognito-idp:AdminUpdateDeviceStatus
    | -|managePasswordRecovery | Grants permission to reset user passwords |
    • cognito-idp:AdminResetUserPassword
    • cognito-idp:AdminSetUserPassword
    | -|addUserToGroup | Grants permission to add any user to any group. |
    • cognito-idp:AdminAddUserToGroup
    -|createUser | Grants permission to create new users and send welcome messages via email or SMS. |
    • cognito-idp:AdminCreateUser
    -|deleteUser | Grants permission to delete any user |
    • cognito-idp:AdminDeleteUser
    -|deleteUserAttributes | Grants permission to delete attributes from any user |
    • cognito-idp:AdminDeleteUserAttributes
    -|disableUser | Grants permission to deactivate any user |
    • cognito-idp:AdminDisableUser
    -|enableUser | Grants permission to activate any user |
    • cognito-idp:AdminEnableUser
    -|forgetDevice | Grants permission to deregister any user's devices |
    • cognito-idp:AdminForgetDevice
    -|getDevice | Grants permission to get information about any user's devices |
    • cognito-idp:AdminGetDevice
    -|getUser | Grants permission to look up any user by user name |
    • cognito-idp:AdminGetUser
    -|listUsers | Grants permission to list users and their basic details in the UserPool |
    • cognito-idp:ListUsers
    -|listDevices | Grants permission to list any user's remembered devices |
    • cognito-idp:AdminListDevices
    -|listGroupsForUser | Grants permission to list the groups that any user belongs to |
    • cognito-idp:AdminListGroupsForUser
    -|listUsersInGroup | Grants permission to list users in the specified group |
    • cognito-idp:ListUsersInGroup
    -|removeUserFromGroup | Grants permission to remove any user from any group |
    • cognito-idp:AdminRemoveUserFromGroup
    -|resetUserPassword | Grants permission to reset any user's password |
    • cognito-idp:AdminResetUserPassword
    -|setUserMfaPreference | Grants permission to set any user's preferred MFA method |
    • cognito-idp:AdminSetUserMFAPreference
    -|setUserPassword | Grants permission to set any user's password |
    • cognito-idp:AdminSetUserPassword
    -|setUserSettings | Grants permission to set user settings for any user |
    • cognito-idp:AdminSetUserSettings
    -|updateDeviceStatus | Grants permission to update the status of any user's remembered devices |
    • cognito-idp:AdminUpdateDeviceStatus
    -|updateUserAttributes | Grants permission to updates any user's standard or custom attributes |
    • cognito-idp:AdminUpdateUserAttributes
    - ---- - ---- -title: "Modify Amplify-generated Cognito resources with CDK" -section: "build-a-backend/auth" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-12-03T11:13:25.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/modify-resources-with-cdk/" ---- - -Amplify Auth provides sensible defaults for the underlying Amazon Cognito resource definitions. You can customize your authentication resource to enable it to behave exactly as needed for your use cases by modifying it directly using [AWS Cloud Development Kit (CDK)](https://aws.amazon.com/cdk/) - -## Override Cognito UserPool password policies - -You can override the password policy by using the L1 `cfnUserPool` construct and adding a `addPropertyOverride`. - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { auth } from './auth/resource'; - -const backend = defineBackend({ - auth, -}); -// extract L1 CfnUserPool resources -const { cfnUserPool } = backend.auth.resources.cfnResources; -// modify cfnUserPool policies directly -cfnUserPool.policies = { - passwordPolicy: { - minimumLength: 10, - requireLowercase: true, - requireNumbers: true, - requireSymbols: true, - requireUppercase: true, - temporaryPasswordValidityDays: 20, - }, -}; -``` - - -## Override Cognito UserPool to enable passwordless sign-in methods - -> **Info:** **Recommended approach:** Passwordless authentication can now be configured directly in `defineAuth` without requiring CDK overrides. [Learn how to configure passwordless authentication](/[platform]/build-a-backend/auth/concepts/passwordless/). - -For advanced use cases, you can still modify the underlying Cognito user pool resource to enable sign in with passwordless methods using CDK overrides. [Learn more about passwordless sign-in methods](/[platform]/build-a-backend/auth/concepts/passwordless/). - -You can also read more about how passwordless authentication flows are implemented in the [Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow-methods.html). - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend" -import { auth } from "./auth/resource" - -const backend = defineBackend({ - auth, -}) - -const { cfnResources } = backend.auth.resources; -const { cfnUserPool, cfnUserPoolClient } = cfnResources; - -// Specify which authentication factors you want to allow with USER_AUTH -cfnUserPool.addPropertyOverride( - 'Policies.SignInPolicy.AllowedFirstAuthFactors', - ['PASSWORD', 'WEB_AUTHN', 'EMAIL_OTP', 'SMS_OTP'] -); - -// The USER_AUTH flow is used for passwordless sign in -cfnUserPoolClient.explicitAuthFlows = [ - 'ALLOW_REFRESH_TOKEN_AUTH', - 'ALLOW_USER_AUTH' -]; - -/* Needed for WebAuthn */ -// The WebAuthnRelyingPartyID is the domain of your relying party, e.g. "example.domain.com" -cfnUserPool.addPropertyOverride('WebAuthnRelyingPartyID', ''); -cfnUserPool.addPropertyOverride('WebAuthnUserVerification', 'preferred'); -``` - - ---- - ---- -title: "Moving to production" -section: "build-a-backend/auth" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-10-10T22:55:38.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/moving-to-production/" ---- - -Amplify Auth provisions [Amazon Cognito](https://aws.amazon.com/cognito/) resources that are provisioned with limited capabilities for sending email and SMS messages. In its default state, it is not intended to handle production workloads, but is sufficient for developing your application and associated business logic. - -## Email - -Cognito provides a [default email functionality](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html#user-pool-email-default) that limits how many emails can be sent in one day. When considering production workloads, Cognito can be configured to send emails using [Amazon Simple Email Service (Amazon SES)](https://aws.amazon.com/ses/). - -> **Warning:** All new AWS accounts default to a "sandbox" status with Amazon SES. This comes with the primary caveat that you can only send mail **to** verified email addresses and domains - -To get started with Amazon SES in production, you must first [request production access](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). Once you submit your request the submission cannot be modified, however you will receive a response from AWS within 24 hours. - -After you have configured your account for production access and have verified your _sender_ email, you can configure your Cognito user pool to send emails using the verified sender: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" - -/** - * Define and configure your auth resource - * @see https://docs.amplify.aws/react/build-a-backend/auth - */ -export const auth = defineAuth({ - loginWith: { - email: true, - }, - senders: { - email: { - // configure using the email registered and verified in Amazon SES - fromEmail: "registrations@example.com", - }, - }, -}) -``` - -Now when emails are sent on new user sign-ups, password resets, etc., the sending account will be your verified email! To customize further, you can change the display name of the sender, or optionally apply a custom address for your users to reply. - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" - -/** - * Define and configure your auth resource - * @see https://docs.amplify.aws/react/build-a-backend/auth - */ -export const auth = defineAuth({ - loginWith: { - email: true, - }, - senders: { - email: { - fromEmail: "registrations@example.com", - // highlight-start - fromName: "MyApp", - replyTo: "inquiries@example.com" - // highlight-end - }, - }, -}) -``` - -## SMS - -In order to send SMS authentication codes, you must [request an origination number](https://docs.aws.amazon.com/pinpoint/latest/userguide/settings-request-number.html). Authentication codes will be sent from the origination number. If your AWS account is in the SMS sandbox, you must also add a destination phone number, which can be done by going to the [Amazon Pinpoint Console](https://console.aws.amazon.com/pinpoint/), selecting SMS and voice in the navigation pane, and selecting Add phone number in the Destination phone numbers tab. To check if your AWS account is in the SMS sandbox, go to the [SNS console](https://console.aws.amazon.com/sns/), select the Text messaging (SMS) tab from the navigation pane, and check the status under the Account information section. - ---- - ---- -title: "Advanced workflows" -section: "build-a-backend/auth" -platforms: ["javascript", "react-native", "flutter", "swift", "android", "angular", "nextjs", "react", "vue"] -gen: 2 -last-updated: "2025-11-19T19:55:05.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/advanced-workflows/" ---- - - -## Identity Pool Federation - -With identity federation, you don't need to create custom sign-in code or manage your own user identities. Instead, users of your app can sign in using a well-known external identity -provider (IdP), such as Login with Amazon, Facebook, Google, or any other OpenID Connect (OIDC)-compatible IdP. They can receive an authentication token, and then exchange that token for -temporary security credentials in AWS that map to an IAM role with permissions to use the resources in your AWS account. Using an IdP helps you keep your AWS account secure because you -don't have to embed and distribute long-term security credentials with your application. - -Imagine that you are creating a mobile app that accesses AWS resources, such as a game that runs on a mobile device and stores player and score information using Amazon S3 and DynamoDB. - -When you write such an app, you make requests to AWS services that must be signed with an AWS access key. However, we strongly recommend that you do not embed or distribute long-term -AWS credentials with apps that a user downloads to a device, even in an encrypted store. Instead, build your app so that it requests temporary AWS security credentials dynamically when -needed using identity federation. The supplied temporary credentials map to an AWS role that has only the permissions needed to perform the tasks required by the mobile app. - -You can use `federateToIdentityPool` to get AWS credentials directly from Cognito Federated Identities and not use User Pool federation. If you logged in with `Auth.signIn` you **cannot** -call `federateToIdentityPool` as Amplify will perform this federation automatically for you in the background. In general, you should only call `Auth.federatedSignIn()` when using OAuth flows. - -You can use the escape hatch API `federateToIdentityPool` with a valid token from other social providers. - -```dart -final cognitoPlugin = - Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); -const googleIdToken = 'idToken'; -final session = await cognitoPlugin.federateToIdentityPool( - token: googleIdToken, - provider: AuthProvider.google, -); -``` - - - -Note that when federated, APIs such as `Auth.getCurrentUser` will throw an error as the user is not authenticated with User Pools. - - - -### Retrieve Session - -After federated login, you can retrieve the session using the `Auth.fetchAuthSession` API. - -### Token Refresh - - - -Automatic authentication token refresh is NOT supported when federated. - - - -By default, Amplify will **NOT** automatically refresh the tokens from the federated providers. You will need to handle the token refresh logic and provide the new token to the `federateToIdentityPool` API. - -### Clear Session - -You can clear the federated session using the `clearFederationToIdentityPool` API. - -```dart -final cognitoPlugin = - Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); -await cognitoPlugin.clearFederationToIdentityPool(); -``` - - - -`clearFederationToIdentityPool` will only clear the session from the local cache; the developer needs to handle signing out from the federated identity provider. - - - -### Provide Custom Identity ID - -You can provide a custom identity ID to the `federateToIdentityPool` API. This is useful when you want to use the same identity ID across multiple sessions. - -```dart -final cognitoPlugin = - Amplify.Auth.getPlugin(AmplifyAuthCognito.pluginKey); -const googleIdToken = 'idToken'; -const identityId = 'us-west-2:b4cd4809-7ab1-42e1-b044-07dab9eaa768'; -final session = await cognitoPlugin.federateToIdentityPool( - token: googleIdToken, - provider: AuthProvider.google, - options: FederateToIdentityPoolOptions( - developerProvidedIdentityId: identityId, - ), -); -``` - - -## Subscribing Events - -You can take specific actions when users sign-in or sign-out by subscribing to authentication events in your app. Please see our [Hub Module Developer Guide](/[platform]/build-a-backend/auth/connect-your-frontend/listen-to-auth-events/) for more information. - -## Identity Pool Federation - -Imagine that you are creating a mobile app that accesses AWS resources, such as a game that runs on a mobile device and stores player and score information using Amazon S3 and DynamoDB. - -When you write such an app, you make requests to AWS services that must be signed with an AWS access key. However, we strongly recommend that you do not embed or distribute long-term AWS credentials with apps that a user downloads to a device, even in an encrypted store. Instead, build your app so that it requests temporary AWS security credentials dynamically when needed using web identity federation. The supplied temporary credentials map to an AWS role that has only the permissions needed to perform the tasks required by the mobile app. - -With web identity federation, you don't need to create custom sign-in code or manage your own user identities. Instead, users of your app can sign in using a well-known external identity provider (IdP), such as Login with Amazon, Facebook, Google, or any other OpenID Connect (OIDC)-compatible IdP. They can receive an authentication token, and then exchange that token for temporary security credentials in AWS that map to an IAM role with permissions to use the resources in your AWS account. Using an IdP helps you keep your AWS account secure because you don't have to embed and distribute long-term security credentials with your application. - -You can use `federateToIdentityPool` to get AWS credentials directly from Cognito Federated Identities and not use User Pool federation. If you logged in with `Auth.signIn` you **cannot** call `federateToIdentityPool` as Amplify will perform this federation automatically for you in the background. In general, you should only call `Auth.federatedSignIn()` when using OAuth flows. - -You can use the escape hatch API `federateToIdentityPool` with a valid token from other social providers. - -#### [Java] - -```java -if (Amplify.Auth.getPlugin("awsCognitoAuthPlugin") instanceof AWSCognitoAuthPlugin) { - AWSCognitoAuthPlugin plugin = (AWSCognitoAuthPlugin) Amplify.Auth.getPlugin("awsCognitoAuthPlugin"); - plugin.federateToIdentityPool( - "YOUR_TOKEN", - AuthProvider.facebook(), - result -> { - Log.i("AuthQuickstart", "Successful federation to Identity Pool."); - // use result.getCredentials() - }, - e -> { - Log.e("AuthQuickstart", "Failed to federate to Identity Pool.", e) - } - ); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -(Amplify.Auth.getPlugin("awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin)?.let { plugin -> - plugin.federateToIdentityPool( - "YOUR_TOKEN", - AuthProvider.facebook(), - { - Log.i("AuthQuickstart", "Successful federation to Identity Pool.") - // use "it.credentials" - }, - { - Log.e("AuthQuickstart", "Failed to federate to Identity Pool.", it) - } - ) -} -``` - - -Note that when federated, APIs such as Auth.getCurrentUser will throw an error as the user is not authenticated with User Pools. - - -### Retrieve Session - -After federated login, you can retrieve the session using the `Auth.fetchAuthSession` API. - -### Token Refresh - - -Automatic authentication token refresh is NOT supported when federated. - - -By default, Amplify will **NOT** automatically refresh the tokens from the federated providers. You will need to handle the token refresh logic and provide the new token to the `federateToIdentityPool` API. - -### Clear Session - -You can clear the federated session using the `clearFederationToIdentityPool` API. - -#### [Java] - -```java -if (Amplify.Auth.getPlugin("awsCognitoAuthPlugin") instanceof AWSCognitoAuthPlugin) { - AWSCognitoAuthPlugin plugin = (AWSCognitoAuthPlugin) Amplify.Auth.getPlugin("awsCognitoAuthPlugin"); - plugin.clearFederationToIdentityPool( - () -> Log.i("AuthQuickstart", "Federation cleared successfully."), - e -> Log.e("AuthQuickstart", "Failed to clear federation.", e) - ); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -(Amplify.Auth.getPlugin("awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin)?.let { plugin -> - plugin.clearFederationToIdentityPool( - { Log.i("AuthQuickstart", "Federation cleared successfully.") }, - { Log.e("AuthQuickstart", "Failed to clear federation.", it) } - ) -} -``` - - -`clearFederationToIdentityPool` will only clear the session from the local cache, the developer needs to handle signing out from the federated provider. - - -### Provide Custom Identity Id - -You can provide a custom identity id to the `federateToIdentityPool` API. This is useful when you want to use the same identity id across multiple devices. - -#### [Java] - -```java -FederateToIdentityPoolOptions options = FederateToIdentityPoolOptions.builder() - .developerProvidedIdentityId("YOUR_CUSTOM_IDENTITY_ID") - .build(); - -if (Amplify.Auth.getPlugin("awsCognitoAuthPlugin") instanceof AWSCognitoAuthPlugin) { - AWSCognitoAuthPlugin plugin = (AWSCognitoAuthPlugin) Amplify.Auth.getPlugin("awsCognitoAuthPlugin"); - plugin.federateToIdentityPool( - "YOUR_TOKEN", - AuthProvider.facebook(), - options, - result -> { - Log.i("AuthQuickstart", "Successful federation to Identity Pool."); - // use result.getCredentials() - }, - e -> { - Log.e("AuthQuickstart", "Failed to federate to Identity Pool.", e) - } - ); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -val options = FederateToIdentityPoolOptions.builder() - .developerProvidedIdentityId("YOUR_CUSTOM_IDENTITY_ID") - .build() - -(Amplify.Auth.getPlugin("awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin)?.let { plugin -> - plugin.federateToIdentityPool( - "YOUR_TOKEN", - AuthProvider.facebook(), - options, - { - Log.i("AuthQuickstart", "Successful federation to Identity Pool.") - // use "it.credentials" - }, - { - Log.e("AuthQuickstart", "Failed to federate to Identity Pool.", it) - } - ) -} -``` - - - -## Subscribing Events - -You can take specific actions when users sign-in or sign-out by subscribing authentication events in your app. Please see our [Hub Module Developer Guide](/[platform]/build-a-backend/auth/connect-your-frontend/listen-to-auth-events/) for more information. - -## Identity Pool Federation - -Imagine that you are creating a mobile app that accesses AWS resources, such as a game that runs on a mobile device and stores player and score information using Amazon S3 and DynamoDB. - -When you write such an app, you make requests to AWS services that must be signed with an AWS access key. However, we strongly recommend that you do not embed or distribute long-term AWS credentials with apps that a user downloads to a device, even in an encrypted store. Instead, build your app so that it requests temporary AWS security credentials dynamically when needed using web identity federation. The supplied temporary credentials map to an AWS role that has only the permissions needed to perform the tasks required by the mobile app. - -With web identity federation, you don't need to create custom sign-in code or manage your own user identities. Instead, users of your app can sign in using a well-known external identity provider (IdP), such as Login with Amazon, Facebook, Google, or any other OpenID Connect (OIDC)-compatible IdP. They can receive an authentication token, and then exchange that token for temporary security credentials in AWS that map to an IAM role with permissions to use the resources in your AWS account. Using an IdP helps you keep your AWS account secure, because you don't have to embed and distribute long-term security credentials with your application. - -You can use `federateToIdentityPool` to get AWS credentials directly from Cognito Federated Identities and not use User Pool federation. If you have logged in with `Auth.signIn` you **can not** call `federateToIdentityPool` as Amplify will perform this federation automatically for you in the background. In general, you should only call `Auth.federateToIdentityPool` when using OAuth flows. - -You can use the escape hatch API `federateToIdentityPool` with a valid token from other social providers. - -```swift -func federateToIdentityPools() async throws { - guard let authCognitoPlugin = try Amplify.Auth.getPlugin( - for: "awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin else { - fatalError("Unable to get the Auth plugin") - } - do { - let result = try await authCognitoPlugin.federateToIdentityPool( - withProviderToken: "YOUR_TOKEN", for: .facebook) - print("Federation successful with result: \(result)") - } catch { - print("Failed to federate to identity pools with error: \(error)") - } -} -``` - - -Note that when federated, API's such as Auth.getCurrentUser() will throw an error as the user is not authenticated with User Pools. - - -### Retrieve Session - -After federated login, you can retrieve session using the `Auth.fetchAuthSession` API. - -### Token Refresh - - -NOTE: Automatic authentication token refresh is NOT supported when federated. - - -By default, Amplify will **NOT** automatically refresh the tokens from the federated providers. You will need to handle the token refresh logic and provide the new token to the `federateToIdentityPool` API. - -### Clear Session - -You can clear the federated session using the `clearFederationToIdentityPool` API. - -```swift -func clearFederationToIdentityPools() async throws { - guard let authCognitoPlugin = try Amplify.Auth.getPlugin( - for: "awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin else { - fatalError("Unable to get the Auth plugin") - } - do { - try await authCognitoPlugin.clearFederationToIdentityPool() - print("Federation cleared successfully") - } catch { - print("Clear federation failed with error: \(error)") - } -} -``` - - -clearFederationToIdentityPool will only clear the session from local cache, developer need to handle signing out from the federated provider. - - -### Provide Custom Identity Id - -You can provide a custom identity id to the `federateToIdentityPool` API. This is useful when you want to use the same identity id across multiple devices. - -```swift -func federateToIdentityPoolsUsingCustomIdentityId() async throws { - guard let authCognitoPlugin = try Amplify.Auth.getPlugin( - for: "awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin else { - fatalError("Unable to get the Auth plugin") - } - do { - let identityId = "YOUR_CUSTOM_IDENTITY_ID" - let result = try await authCognitoPlugin.federateToIdentityPool( - withProviderToken: "YOUR_TOKEN", - for: .facebook, - options: .init(developerProvidedIdentityID: identityId)) - print("Federation successful with result: \(result)") - } catch { - print("Failed to federate to identity pools with error: \(error)") - } -} -``` - -## Keychain Sharing - -### Migrating to a Shared Keychain - -To use a shared keychain: - -1. In Xcode, go to Project Settings β†’ Your Target β†’ Signing & Capabilities -2. Select +Capability -3. Add Keychain Sharing capability -4. Add a keychain group -5. Repeat for all apps for which you want to share auth state, adding the same keychain group for all of them - -To move to the shared keychain using this new keychain access group, specify the `accessGroup` parameter when instantiating the `AWSCognitoAuthPlugin`. If a user is currently signed in, they will be signed out when first using the access group: - -```swift -let accessGroup = AccessGroup(name: "\(teamID)com.example.sharedItems") -let secureStoragePreferences = AWSCognitoSecureStoragePreferences( - accessGroup: accessGroup) -try Amplify.add( - plugin: AWSCognitoAuthPlugin( - secureStoragePreferences: secureStoragePreferences)) -try Amplify.configure() -``` - -If you would prefer the user session to be migrated (which will allow the user to continue to be signed in), then specify the `migrateKeychainItemsOfUserSession` boolean in the AccessGroup to be true like so: - -```swift -let accessGroup = AccessGroup( - name: "\(teamID)com.example.sharedItems", - migrateKeychainItemsOfUserSession: true) -let secureStoragePreferences = AWSCognitoSecureStoragePreferences( - accessGroup: accessGroup) -try Amplify.add( - plugin: AWSCognitoAuthPlugin( - secureStoragePreferences: secureStoragePreferences)) -try Amplify.configure() -``` - -Sign in a user with any sign-in method within one app that uses this access group. After reloading another app that uses this access group, the user will be signed in. Likewise, signing out of one app will sign out the other app after reloading it. - -### Migrating to another Shared Keychain - -To move to a different access group, update the name parameter of the AccessGroup to be the new access group. Set `migrateKeychainItemsOfUserSession` to `true` to migrate an existing user session under the previously used access group. - -### Migrating from a Shared Keychain - -If you'd like to stop sharing state between this app and other apps, you can set the access group to be `AccessGroup.none` or `AccessGroup.none(migrateKeychainItemsOfUserSession: true)` if you'd like the session to be migrated. - -### Retrieving Team ID - -First, ensure your Info.plist has the `AppIdentifierPrefix` key: - -```xml title="Info.plist" - - - - - AppIdentifierPrefix - $(AppIdentifierPrefix) - - -``` - -Then, you can retrieve the team ID from your Info.plist: - -```swift -guard let teamID = Bundle.main.infoDictionary?["AppIdentifierPrefix"] as? String else { - fatalError("AppIdentifierPrefix key not found in Info.plist") -} -``` - - -## Subscribing to Events - -You can take specific actions when users sign-in or sign-out by subscribing to authentication events in your app. Please see our [Hub Module Developer Guide](/[platform]/build-a-backend/auth/connect-your-frontend/listen-to-auth-events/) for more information. - -## Identity Pool Federation - -You can alternatively create your own custom credentials provider to get AWS credentials directly from Cognito Federated Identities and not use User Pool federation. You must supply the custom credentials provider to Amplify via the `Amplify.configure` method call. Below, you can see sample code of how such a custom provider can be built to achieve the use case. - -```js -import { Amplify } from 'aws-amplify'; -import { - fetchAuthSession, - CredentialsAndIdentityIdProvider, - CredentialsAndIdentityId, - GetCredentialsOptions, - AuthTokens, -} from 'aws-amplify/auth'; - -// Note: This example requires installing `@aws-sdk/client-cognito-identity` to obtain Cognito credentials -// npm add @aws-sdk/client-cognito-identity -import { CognitoIdentity } from '@aws-sdk/client-cognito-identity'; - -// You can make use of the sdk to get identityId and credentials -const cognitoidentity = new CognitoIdentity({ - region: '', -}); - -// Note: The custom provider class must implement CredentialsAndIdentityIdProvider -class CustomCredentialsProvider implements CredentialsAndIdentityIdProvider { - - // Example class member that holds the login information - federatedLogin?: { - domain: string, - token: string - }; - - // Custom method to load the federated login information - loadFederatedLogin(login?: typeof this.federatedLogin) { - // You may also persist this by caching if needed - this.federatedLogin = login; - } - - async getCredentialsAndIdentityId( - getCredentialsOptions: GetCredentialsOptions - ): Promise { - try { - - // You can add in some validation to check if the token is available before proceeding - // You can also refresh the token if it's expired before proceeding - - const getIdResult = await cognitoidentity.getId({ - // Get the identityPoolId from config - IdentityPoolId: '', - Logins: { [this.federatedLogin.domain]: this.federatedLogin.token }, - }); - - const cognitoCredentialsResult = await cognitoidentity.getCredentialsForIdentity({ - IdentityId: getIdResult.IdentityId, - Logins: { [this.federatedLogin.domain]: this.federatedLogin.token }, - }); - - const credentials: CredentialsAndIdentityId = { - credentials: { - accessKeyId: cognitoCredentialsResult.Credentials?.AccessKeyId, - secretAccessKey: cognitoCredentialsResult.Credentials?.SecretKey, - sessionToken: cognitoCredentialsResult.Credentials?.SessionToken, - expiration: cognitoCredentialsResult.Credentials?.Expiration, - }, - identityId: getIdResult.IdentityId, - }; - return credentials; - } catch (e) { - console.log('Error getting credentials: ', e); - } - } - // Implement this to clear any cached credentials and identityId. This can be called when signing out of the federation service. - clearCredentialsAndIdentityId(): void {} -} - -// Create an instance of your custom provider -const customCredentialsProvider = new CustomCredentialsProvider(); -Amplify.configure(awsconfig, { - Auth: { - // Supply the custom credentials provider to Amplify - credentialsProvider: customCredentialsProvider - }, -}); - -``` - -Now that the custom credentials provider is built and supplied to `Amplify.configure`, let's look at how you can use the custom credentials provider to finish federation into Cognito identity pool. - - -### Facebook Sign-in (React Native - Expo) - -```javascript -import Expo from 'expo'; -import React from 'react'; -import { fetchAuthSession } from 'aws-amplify/auth'; - -const App = () => { - const signIn = async () => { - const { type, token, expires } = - await Expo.Facebook.logInWithReadPermissionsAsync( - 'YOUR_FACEBOOK_APP_ID', - { - permissions: ['public_profile'] - } - ); - if (type === 'success') { - // sign in with federated identity - try { - customCredentialsProvider.loadFederatedLogin({ - domain: 'graph.facebook.com', - token: token - }); - const fetchSessionResult = await fetchAuthSession(); // will return the credentials - console.log('fetchSessionResult: ', fetchSessionResult); - } catch (err) { - console.log(err); - } - } - }; - - // ... - - return ( - - - - ); -} -``` - -### Google sign-in (React) - -```jsx -import React, { useEffect } from 'react'; -import jwt from 'jwt-decode'; -import { - fetchAuthSession, -} from 'aws-amplify/auth'; - -const SignInWithGoogle = () => { - useEffect(() => { - // Check for an existing Google client initialization - if (!window.google?.accounts) createScript(); - }, []); - - // Load the Google client - const createScript = () => { - const script = document.createElement('script'); - script.src = 'https://accounts.google.com/gsi/client'; - script.async = true; - script.defer = true; - script.onload = initGsi; - document.body.appendChild(script); - } - - // Initialize Google client and render Google button - const initGsi = () => { - if (window.google?.accounts) { - window.google.accounts.id.initialize({ - client_id: process.env.GOOGLE_CLIENT_ID, - callback: (response: any) => { - customCredentialsProvider.loadFederatedLogin({ - domain: 'accounts.google.com', - token: response.credential, - }); - const fetchSessionResult = await fetchAuthSession(); // will return the credentials - console.log('fetchSessionResult: ', fetchSessionResult); - }, - }); - window.google.accounts.id.renderButton( - document.getElementById('googleSignInButton'), - { theme: 'outline', size: 'large' } - ); - } - } - - return ( -
    -
    - ); -} -``` - -### Federate with Auth0 - -You can use `Auth0` as one of the providers of your Cognito Identity Pool. This will allow users authenticated via Auth0 have access to your AWS resources. - -Step 1. [Follow Auth0 integration instructions for Cognito Federated Identity Pools](https://auth0.com/docs/customize/integrations/aws/amazon-cognito) - -Step 2. Login with `Auth0`, then use the id token returned to get AWS credentials from `Cognito Federated Identity Pools` using custom credentials provider you created at the start: - -```js -import { fetchAuthSession } from 'aws-amplify/auth'; - -const { idToken, domain, name, email, phoneNumber } = getFromAuth0(); // get the user credentials and info from auth0 - -async function getCognitoCredentials() { - try { - customCredentialsProvider.loadFederatedLogin({ - domain, - token: idToken - }); - const fetchSessionResult = await fetchAuthSession(); // will return the credentials - console.log('fetchSessionResult: ', fetchSessionResult); - } catch (err) { - console.log(err); - } -} -``` - - -## Lambda Triggers - -With the triggers property of defineAuth and defineFunction from the new Functions implementation, you can define [Lambda Triggers](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html) for your Cognito User Pool. These enable you to add custom functionality to your registration and authentication flows. [Check out a preSignUp hook example here.](/[platform]/build-a-backend/functions/examples/email-domain-filtering/) - -### Pre Authentication and Pre Sign-up Lambda triggers - -If you have a Pre Authentication Lambda trigger enabled, you can pass `clientMetadata` as an option for `signIn`. This metadata can be used to implement additional validations around authentication. - -```ts -import { signIn } from 'aws-amplify/auth'; - -async function handleSignIn(username: string, password: string) { - try { - await signIn({ - username, - password, - options: { - clientMetadata: {} // Optional, an object of key-value pairs which can contain any key and will be passed to your Lambda trigger as-is. - } - }); - } catch (err) { - console.log(err); - } -} -``` - -### Passing metadata to other Lambda triggers - -Many Cognito Lambda Triggers also accept unsanitized key/value pairs in the form of a `clientMetadata` attribute. This attribute can be specified for various Auth APIs which result in Cognito Lambda Trigger execution. - -These APIs include: - -- `signIn` -- `signUp` -- `confirmSignIn` -- `confirmSignUp` -- `resetPassword` -- `confirmResetPassword` -- `resendSignUpCode` -- `updateUserAttributes` -- `fetchAuthSession` - -Additionally, you can configure a `ClientMetadataProvider` which passes the `clientMetadata` to internal `fetchAuthSession` calls: - -```javascript -// Set global clientMetadata (affects all token refreshes) -import { cognitoUserPoolsTokenProvider } from 'aws-amplify/auth/cognito'; - -const clientMetadataProvider = () => Promise.resolve({ - 'app-version': '1.0.0', - 'device-type': 'mobile' -}); - -cognitoUserPoolsTokenProvider.setClientMetadataProvider(clientMetadataProvider); -``` - -Please note that some of triggers which accept a `validationData` attribute will use `clientMetadata` as the value for `validationData`. Exercise caution with using `clientMetadata` when you are relying on `validationData`. - -## Working with AWS service objects - -You can use AWS _Service Interface Objects_ to work with AWS Services in authenticated State. You can call methods on any AWS Service interface object by passing your credentials from Amplify `fetchAuthSession` to the service call constructor: - -```javascript -import { fetchAuthSession } from 'aws-amplify/auth'; -import Route53 from 'aws-sdk/clients/route53'; - -async function changeResourceRecordSets() { - try { - const { credentials } = await fetchAuthSession(); - - const route53 = new Route53({ - apiVersion: '2013-04-01', - credentials - }); - - // more code working with route53 object - //route53.changeResourceRecordSets(); - } catch (err) { - console.log(err); - } -} -``` - -> **Warning:** Note: To work with Service Interface Objects, your Amazon Cognito users' [IAM role](https://docs.aws.amazon.com/cognito/latest/developerguide/iam-roles.html) must have the appropriate permissions to call the requested services. - -## Custom Token providers - -Create a custom Auth token provider for situations where you would like provide your own tokens for a service. For example, using OIDC Auth with AppSync. You must supply the token provider to Amplify via the `Amplify.configure` method call. Below, you can see sample code of how such a custom provider can be built to achieve the use case. - -```javascript -import { Amplify } from 'aws-amplify'; -import { TokenProvider, decodeJWT } from 'aws-amplify/auth'; - -// ... - -const myTokenProvider: TokenProvider = { - async getTokens({ forceRefresh } = {}) { - if (forceRefresh) { - // try to obtain new tokens if possible - } - - const accessTokenString = ''; - const idTokenString = ''; - - return { - accessToken: decodeJWT(accessTokenString), - idToken: decodeJWT(idTokenString), - }; - }, -}; - -Amplify.configure(awsconfig, { - Auth: { - tokenProvider: myTokenProvider - } -}); - -``` -## API reference - -For the complete API documentation for Authentication module, visit our [API Reference](https://aws-amplify.github.io/amplify-js/api/modules/aws_amplify.auth.html) - - ---- - ---- -title: "Use existing Cognito resources" -section: "build-a-backend/auth" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-02-19T23:36:30.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/use-existing-cognito-resources/" ---- - -Amplify Auth can be configured to use an existing Amazon Cognito user pool and identity pool. If you are in a team setting or part of a company that has previously created auth resources, you can [configure the client library directly](#use-auth-resources-without-an-amplify-backend), or maintain references with [AWS Cloud Development Kit (AWS CDK)](https://aws.amazon.com/cdk/) in your Amplify backend. - -> **Info:** **Note:** when using existing auth resources, it may be necessary to add additional policies or permissions for your authenticated and unauthenticated IAM roles. These changes must be performed manually. - -## Use auth resources without an Amplify backend - - -You can use existing resources without an Amplify backend by configuring the client library directly. - -```ts title="src/main.ts" -import { Amplify } from "aws-amplify" - -Amplify.configure({ - Auth: { - Cognito: { - userPoolId: "", - userPoolClientId: "", - identityPoolId: "", - loginWith: { - email: true, - }, - signUpVerificationMethod: "code", - userAttributes: { - email: { - required: true, - }, - }, - allowGuestAccess: true, - passwordFormat: { - minLength: 8, - requireLowercase: true, - requireUppercase: true, - requireNumbers: true, - requireSpecialCharacters: true, - }, - }, - }, -}) -``` - - -Configuring the mobile client libraries directly is not supported, however you can manually create `amplify_outputs.json` with the following schema: - -> **Info:** **Note:** it is strongly recommended to use backend outputs to generate this file for each sandbox or branch deployment - -```json title="amplify_outputs.json" -{ - "version": "1", - "auth": { - "aws_region": "", - "user_pool_id": "", - "user_pool_client_id": "", - "identity_pool_id": "", - "username_attributes": ["email"], - "standard_required_attributes": ["email"], - "user_verification_types": ["email"], - "unauthenticated_identities_enabled": true, - "password_policy": { - "min_length": 8, - "require_lowercase": true, - "require_uppercase": true, - "require_numbers": true, - "require_symbols": true - } - } -} -``` - - -## Use auth resources with an Amplify backend - -> **Warning:** Amplify cannot modify the configuration of your referenced resources and only captures the resource configuration at the time of reference, any subsequent changes made to the referenced resources will not be automatically reflected in your Amplify backend. - -If you have created Amazon Cognito resources outside of the context of your Amplify app such as creating resources through the AWS Console or consuming resources created by a separate team, you can use `referenceAuth` to reference the existing resources. It requires a user pool, a user pool client, identity pool, and an authenticated & unauthenticated IAM role configured on your identity pool. - -```ts title="amplify/auth/resource.ts" -import { referenceAuth } from '@aws-amplify/backend'; - -export const auth = referenceAuth({ - userPoolId: 'us-east-1_xxxx', - identityPoolId: 'us-east-1:b57b7c3b-9c95-43e4-9266-xxxx', - authRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthauthenticatedU-xxxx', - unauthRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthunauthenticate-xxxx', - userPoolClientId: 'xxxx', -}); -``` - -> **Info:** IAM policies specific to your Amplify application will be appended to your authenticated and unauthenticated roles, and applications using the referenced resource will be able to create users in the Cognito user pool and identities in the Cognito identity pool. - -You can also use the [`access` property](/[platform]/build-a-backend/auth/grant-access-to-auth-resources/) to grant permissions to your auth resource from other Amplify backend resources. For example, if you have a function that needs to retrieve details about a user: - -```ts title="amplify/auth/resource.ts" -import { referenceAuth } from '@aws-amplify/backend'; -import { getUser } from "../functions/get-user/resource"; - -export const auth = referenceAuth({ - userPoolId: 'us-east-1_xxxx', - identityPoolId: 'us-east-1:b57b7c3b-9c95-43e4-9266-xxxx', - authRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthauthenticatedU-xxxx', - unauthRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthunauthenticate-xxxx', - userPoolClientId: 'xxxx', - access: (allow) => [ - allow.resource(getUser).to(["getUser"]), - ], -}); -``` - -Additionally, you can also use the `groups` property to reference groups in your user pool. This is useful if you want to work with groups in your application and provide access to resources such as storage based on group membership. - -```ts title="amplify/auth/resource.ts" -import { referenceAuth } from '@aws-amplify/backend'; -import { getUser } from "../functions/get-user/resource"; - -export const auth = referenceAuth({ - userPoolId: 'us-east-1_xxxx', - identityPoolId: 'us-east-1:b57b7c3b-9c95-43e4-9266-xxxx', - authRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthauthenticatedU-xxxx', - unauthRoleArn: 'arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthunauthenticate-xxxx', - userPoolClientId: 'xxxx', - groups: { - admin: "arn:aws:iam::xxxx:role/amplify-xxxx-mai-amplifyAuthadminGroupRole-xxxx", - }, -}); -``` - -In a team setting you may want to reference a different set of auth resources depending on the deployment context. For instance if you have a `staging` branch that should reuse resources from a separate "staging" environment compared to a `production` branch that should reuse resources from the separate "production" environment. In this case we recommend using environment variables. - -```ts title="amplify/auth/resource.ts" -import { referenceAuth } from '@aws-amplify/backend'; - -export const auth = referenceAuth({ - userPoolId: process.env.MY_USER_POOL_ID, - identityPoolId: process.env.MY_IDENTITY_POOL_ID, - authRoleArn: process.env.MY_AUTH_ROLE_ARN, - unauthRoleArn: process.env.MY_UNAUTH_ROLE_ARN, - userPoolClientId: process.env.MY_USER_POOL_CLIENT_ID, -}); -``` - -Environment variables must be configured separately on your machine for sandbox deployments and Amplify console for branch deployments. - -## Next steps - -- [Learn how to connect your frontend](/[platform]/build-a-backend/auth/connect-your-frontend/) - ---- - ---- -title: "Use AWS SDK" -section: "build-a-backend/auth" -platforms: ["swift", "android"] -gen: 2 -last-updated: "2024-09-24T23:57:23.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/auth/use-aws-sdk/" ---- - -For advanced use cases where Amplify does not provide the functionality, you can retrieve an escape hatch to access the underlying Amazon Cognito client. - - -The escape hatch provides access to the underlying `AWSCognitoIdentityProvider` instance. Import the necessary types: - -```swift -import AWSCognitoAuthPlugin -import AWSCognitoIdentityProvider -``` - -Then retrieve the escape hatch with this code: - -```swift -func getEscapeHatch() { - let client: CognitoIdentityProviderClient - - // Get the instance of AWSCognitoAuthPlugin - let plugin = try? Amplify.Auth.getPlugin(for: "awsCognitoAuthPlugin") as? AWSCognitoAuthPlugin - - // Get the instance of CognitoIdentityProviderClient - if case .userPoolAndIdentityPool(let userPoolClient, _) = plugin?.getEscapeHatch() { - client = userPoolClient - } else if case .userPool(let userPoolClient) = plugin?.getEscapeHatch() { - client = userPoolClient - } else { - fatalError("No user pool configuration found") - } - print("Fetched escape hatch - \(String(describing: client))") -} -``` - - - -You can access the underlying `CognitoIdentityProviderClient` and `CognitoIdentityClient` as shown below - -```kotlin -implementation "aws.sdk.kotlin:cognitoidentityprovider:1.0.44" -implementation "aws.sdk.kotlin:cognitoidentity:1.0.44" -``` - -#### [Kotlin] - -```kotlin -suspend fun resendCodeUsingEscapeHatch() { - // Get the instance of AWSCognitoAuthPlugin - val cognitoAuthPlugin = Amplify.Auth.getPlugin("awsCognitoAuthPlugin") - val cognitoAuthService = cognitoAuthPlugin.escapeHatch as AWSCognitoAuthService - - // Get the instance of CognitoIdentityProviderClient - val cognitoIdentityProviderClient = cognitoAuthService.cognitoIdentityProviderClient - val request = ResendConfirmationCodeRequest { - clientId = "xxxxxxxxxxxxxxxx" - username = "user1" - } - val response = cognitoIdentityProviderClient?.resendConfirmationCode(request) -} -``` - -#### [Java] - - - -[Learn more about consuming Kotlin clients from Java using either a blocking interface or an equivalent async interface based on futures](https://github.com/awslabs/smithy-kotlin/blob/main/docs/design/kotlin-smithy-sdk.md#java-interop). - - - -```java -// Get the instance of AWSCognitoAuthPlugin -AWSCognitoAuthPlugin cognitoAuthPlugin = (AWSCognitoAuthPlugin) Amplify.Auth.getPlugin("awsCognitoAuthPlugin"); - -// Get the instance of CognitoIdentityProviderClient -CognitoIdentityProviderClient client = cognitoAuthPlugin.getEscapeHatch().getCognitoIdentityProviderClient(); -ResendConfirmationCodeRequest request = ResendConfirmationCodeRequest.Companion.invoke(dslBuilder -> { - dslBuilder.setClientId("xxxxxxxxxxxxxxxx"); - dslBuilder.setUsername("user1"); - return null; -}); - -assert client != null; -client.resendConfirmationCode(request, new Continuation() { - @NonNull - @Override - public CoroutineContext getContext() { - return GlobalScope.INSTANCE.getCoroutineContext(); - } - - @Override - public void resumeWith(@NonNull Object resultOrException) { - Log.i(TAG, "Result: " + resultOrException); - } -}); -``` - - - ---- - ---- -title: "API References" -section: "build-a-backend/auth" -platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] -gen: 2 -last-updated: "" -url: "https://docs.amplify.aws/react/build-a-backend/auth/reference/" ---- - - - ---- - ---- -title: "Data" -section: "build-a-backend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-02-21T20:06:17.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/" ---- - - - ---- - ---- -title: "Set up Amplify Data" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-11-13T16:29:27.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/set-up-data/" ---- - -In this guide, you will learn how to set up Amplify Data. This includes building a real-time API and database using TypeScript to define your data model, and securing your API with authorization rules. We will also explore using AWS Lambda to scale to custom use cases. - -Before you begin, you will need: - -- [Node.js](https://nodejs.org/) v18.16.0 or later -- [npm](https://www.npmjs.com/) v6.14.4 or later -- [git](https://git-scm.com/) v2.14.1 or later - -With Amplify Data, you can build a secure, real-time API backed by a database in minutes. After you define your data model using TypeScript, Amplify will deploy a real-time API for you. This API is powered by AWS AppSync and connected to an Amazon DynamoDB database. You can secure your API with authorization rules and scale to custom use cases with AWS Lambda. - -## Building your data backend - -If you've run `npm create amplify@latest` already, you should see an `amplify/data/resource.ts` file, which is the central location to configure your data backend. The most important element is the `schema` object, which defines your backend data models (`a.model()`) and custom queries (`a.query()`), mutations (`a.mutation()`), and subscriptions (`a.subscription()`). - -```ts title="amplify/data/resource.ts" -import { a, defineData, type ClientSchema } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a.model({ - content: a.string(), - isDone: a.boolean() - }) - .authorization(allow => [allow.publicApiKey()]) -}); - -// Used for code completion / highlighting when making requests from frontend -export type Schema = ClientSchema; - -// defines the data resource to be deployed -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { expiresInDays: 30 } - } -}); -``` - -Every `a.model()` automatically creates the following resources in the cloud: - -- a DynamoDB database table to store records -- query and mutation APIs to create, read (list/get), update, and delete records -- `createdAt` and `updatedAt` fields that help you keep track of when each record was initially created or when it was last updated -- real-time APIs to subscribe for create, update, and delete events of records - -The `allow.publicApiKey()` rule designates that anyone authenticated using an API key can create, read, update, and delete todos. - -To deploy these resources to your cloud sandbox, run the following CLI command in your terminal: - - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox -``` - - - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox --outputs-out-dir -``` - - - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox --outputs-out-dir -``` - - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox --outputs-format dart --outputs-out-dir lib -``` - - -## Connect your application code to the data backend - -Once the cloud sandbox is up and running, it will also create an `amplify_outputs.json` file, which includes the relevant connection information to your data backend, like your API endpoint URL and API key. - -To connect your frontend code to your backend, you need to: - -1. Configure the Amplify library with the Amplify client configuration file (`amplify_outputs.json`) -2. Generate a new API client from the Amplify library -3. Make an API request with end-to-end type-safety - - -First, install the Amplify client library to your project: - -```bash title="Terminal" showLineNumbers={false} -npm add aws-amplify -``` - - - -In your app's entry point, typically **main.tsx** for React apps created using Vite, make the following edits: - -```tsx title="src/main.tsx" -import { Amplify } from 'aws-amplify'; -import outputs from '../amplify_outputs.json'; - -Amplify.configure(outputs); -``` - - - -In your app's entry point, typically **main.ts** for Vue apps created using Vite, make the following edits: - -```tsx title="src/main.ts" -import { Amplify } from 'aws-amplify'; -import outputs from '../amplify_outputs.json'; - -Amplify.configure(outputs); -``` - - - -Under Gradle Scripts, open build.gradle (Module :app), add the following lines: - -```kotlin title="app/build.gradle.kts" -android { - compileOptions { - // Support for modern Java features - isCoreLibraryDesugaringEnabled = true - } -} - -dependencies { - // Amplify API dependencies - // highlight-start - implementation("com.amplifyframework:aws-api:ANDROID_VERSION") - // highlight-end - // ... other dependencies - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:ANDROID_DESUGAR_VERSION") -} -``` - -Click **Sync Now** in the notification bar above the file editor to sync these dependencies. - -Next, configure the Amplify client library with the generated `amplify_outputs.json` file to make it aware of the backend API endpoint. *Note: verify that the **amplify_outputs.json** file is present in your **res/raw/** folder. - -Create a new `MyAmplifyApp` class that inherits from `Application` with the following code: - -> **Warning:** Before calling the `Amplify.configure` function, make sure to either download the `amplify_outputs.json` file from the console, or generate it with the following command: -> -> ```bash title="Terminal" showLineNumbers={false} -npx ampx generate outputs --app-id --branch main --out-dir app/src/main/res/raw -``` -> -> Next, be sure the file you generated or downloaded is in the appropriate resource directory for your application (for example, `app/src/main/res/raw`) in your Android project. Otherwise, you will not be able to compile your application. - -```kt -package com.example.myapplication - -import android.app.Application -import android.util.Log -import com.amplifyframework.AmplifyException -import com.amplifyframework.api.aws.AWSApiPlugin -import com.amplifyframework.core.Amplify -import com.amplifyframework.core.configuration.AmplifyOutputs - -class MyAmplifyApp : Application() { - override fun onCreate() { - super.onCreate() - - try { - // Adds the API plugin that is used to issue queries and mutations - // to your backend. - Amplify.addPlugin(AWSApiPlugin()) - // Configures the client library to be aware of your backend API - // endpoint and authorization modes. - Amplify.configure(AmplifyOutputs(R.raw.amplify_outputs), applicationContext) - Log.i("Tutorial", "Initialized Amplify") - } catch (error: AmplifyException) { - Log.e("Tutorial", "Could not initialize Amplify", error) - } - } -} -``` - -This overrides the `onCreate()` to initialize Amplify when your application is launched. - -Next, configure your application to use your new custom Application class. Open **manifests** > **AndroidManifest.xml**, and add an `android:name` attribute with the value of your new class name: - -```xml - - - - - - -``` - -Build and run the application. In Logcat, you'll see a log line indicating success: - -```console title="Logcat" showLineNumbers={false} -com.example.MyAmplifyApp I/MyAmplifyApp: Initialized Amplify -``` - -Finally, let's generate the GraphQL client code for your Android application. Amplify Data uses GraphQL under the hood to make query, mutation, and subscription requests. The generated GraphQL client code helps you to author fully-typed API requests without needing to hand-author GraphQL requests and manually map them to Kotlin or Java code. - -```bash title="Terminal" showLineNumbers={false} -npx ampx generate graphql-client-code --format modelgen --model-target java --out -``` - - - -Drag and drop the **amplify_outputs.json** file from the Finder into Xcode. - -Next, add Amplify Library for Swift through Swift Package Manager. In Xcode, select **File** > **Add Packages...**. - -Then, enter the Amplify Library for Swift GitHub repo URL (https://github.com/aws-amplify/amplify-swift) into the search bar and hit **Enter**. - -Once the result is loaded, choose Up to **Next Major Version** as the **Dependency Rule**, then click **Add Package**. - -Choose which of the libraries you want added to your project. For this tutorial, select **AWSAPIPlugin** and **Amplify**, then click **Add Package**. - -Now let's add the necessary plugins into the Swift application by customizing the `init()` function of your app: - -```swift title="MyAmplifyApp" -import SwiftUI -// highlight-start -import Amplify -import AWSAPIPlugin -// highlight-end - -@main -struct MyAmplifyApp: App { - - // highlight-start - init() { - let awsApiPlugin = AWSAPIPlugin(modelRegistration: AmplifyModels()) - do { - try Amplify.add(plugin: awsApiPlugin) - try Amplify.configure(with: .amplifyOutputs) - print("Initialized Amplify"); - } catch { - // simplified error handling for the tutorial - print("Could not initialize Amplify: \(error)") - } - } - // highlight-end - - var body: some Scene { - WindowGroup { - ContentView() - } - } -} -``` - -Finally, let's generate the GraphQL client code for your Swift application. Amplify Data uses GraphQL under the hood to make query, mutation, and subscription requests. The generated GraphQL client code helps you to author fully-typed API requests without needing to hand-author GraphQL requests and manually map them to Swift code. - -```bash title="Terminal" showLineNumbers={false} -npx ampx generate graphql-client-code --format modelgen --model-target swift --out /AmplifyModels -``` - -Drag and drop the **AmplifyModels** folder into your Xcode project to add the generated files. - - - -From your project root directory, find and modify your **pubspec.yaml** and add the Amplify plugins to the project dependencies. - -```yaml title="pubspec.yaml" -dependencies: - // highlight-start - amplify_api: ^2.0.0 - amplify_flutter: ^2.0.0 - // highlight-end - flutter: - sdk: flutter -``` - -Install the dependencies by running the following command. Depending on your development environment, you may perform this step via your IDE (or it may even be performed for you automatically). - -```bash title="Terminal" showLineNumbers={false} -flutter pub get -``` - -Now, let's generate the GraphQL client code for your Flutter application. Amplify Data uses GraphQL under the hood to make query, mutation, and subscription requests. The generated GraphQL client code helps you to author fully-typed API requests without needing to hand-author GraphQL requests and manually map them to Dart code. - -```bash title="Terminal" showLineNumbers={false} -npx ampx generate graphql-client-code --format modelgen --model-target dart --out /lib/models -``` - -Finally, let's add the necessary plugins into the Flutter application by customizing the `main()` function of the **lib/main.dart** file: - -```dart title="lib/main.dart" -// highlight-start -import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -// highlight-end -import 'package:flutter/material.dart'; - -// highlight-start -import 'amplify_outputs.dart'; -import 'models/ModelProvider.dart'; -// highlight-end - -Future main() async { - // highlight-start - try { - final api = AmplifyAPI( - options: APIPluginOptions( - modelProvider: ModelProvider.instance - ) - ); - await Amplify.addPlugins([api]); - await Amplify.configure(amplifyConfig); - - safePrint('Successfully configured Amplify'); - } on Exception catch (e) { - safePrint('Error configuring Amplify: $e'); - } - // highlight-end - - runApp(const MyApp()); -} -``` - -## Write data to your backend - - -Let's first add a button to create a new todo item. To make a "create Todo" API request, generate the data client using `generateClient()` in your frontend code, and then call `.create()` operation for the Todo model. The Data client is a fully typed client that gives you in-IDE code completion. To enable this in-IDE code completion capability, pass in the `Schema` type to the `generateClient` function. - - - -```tsx title="src/TodoList.tsx" -import type { Schema } from '../amplify/data/resource' -import { generateClient } from 'aws-amplify/data' - -const client = generateClient() - -export default function TodoList() { - const createTodo = async () => { - await client.models.Todo.create({ - content: window.prompt("Todo content?"), - isDone: false - }) - } - - return
    - -
    -} -``` - - - -```html title="src/TodoList.vue" - - - -``` - - - -Run the application in local development mode with `npm run dev` and check your network tab after creating a todo. You should see a successful request to a `/graphql` endpoint. - - - -Try playing around with the code completion of `.update(...)` and `.delete(...)` to get a sense of other mutation operations. - - - - - -```ts title="todo-list.component.ts" -import type { Schema } from '../amplify/data/resource'; -import { Component } from '@angular/core'; -import { generateClient } from 'aws-amplify/data'; - -const client = generateClient(); - -@Component({ - selector: 'app-todo-list', - template: ` - - ` -}) -export class TodoListComponent { - async createTodo() { - await client.models.Todo.create({ - content: window.prompt("Todo content?"), - isDone: false - }); - } -} -``` - -Run the application in local development mode and check your network tab after creating a todo. You should see a successful request to a `/graphql` endpoint. - - - -Try playing around with the code completion of `.update(...)` and `.delete(...)` to get a sense of other mutation operations. - - - - - -In your MainActivity, add a button to create a new todo. - -```kt title="MainActivity.kt" -// imports - -class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - MyApplicationTheme { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - // highlight-start - Column { - Button(onClick = { - val todo = Todo.builder() - .content("My first todo") - .isDone(false) - .build() - - Amplify.API.mutate(ModelMutation.create(todo), - { Log.i("MyAmplifyApp", "Added Todo with id: ${it.data.id}")}, - { Log.e("MyAmplifyApp", "Create failed", it)}, - ) - }) { - Text(text = "Create Todo") - } - } - // highlight-end - } - } - } - } -} -``` - -Build and run your app. Then, click on "Create Todo" on the app. Your Logcat should show you that a todo was successfully added: - -```console title="Logcat" showLineNumbers={false} -com.example.MyAmplifyApp I/MyAmplifyApp: Added Todo with id: SOME_TODO_ID -``` - - - -Create a new file called `TodoViewModel.swift` and the `createTodo` function with the following code: - -```swift title="TodoViewModel.swift" -import Foundation -import Amplify - -@MainActor -class TodoViewModel: ObservableObject { - func createTodo() { - let todo = Todo( - content: "Build iOS Application", - isDone: false - ) - Task { - do { - let result = try await Amplify.API.mutate(request: .create(todo)) - switch result { - case .success(let todo): - print("Successfully created todo: \(todo)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } catch let error as APIError { - print("Failed to create todo: ", error) - } catch { - print("Unexpected error: \(error)") - } - } - } -} - -``` - -Update `ContentView.swift` with the following code: - -```swift title="ContentView.swift" -struct ContentView: View { - - // highlight-start - // Create an observable object instance. - @StateObject var vm = TodoViewModel() - // highlight-end - - var body: some View { - // highlight-start - VStack { - Button(action: { - vm.createTodo() - }) { - HStack { - Text("Add a New Todo") - Image(systemName: "plus") - } - } - .accessibilityLabel("New Todo") - } - // highlight-end - } -} -``` - -Now if you run the application, and click on the "Add a New Todo" button, you should see a log indicating a todo was created: - -```console title="Logs" showLineNumbers={false} -Successfully created todo: Todo(id: XYZ ...) -``` - - - -In your page, let's add a floating action button that creates a new todo. - -```dart title="lib/main.dart" -// ... main() -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return const MaterialApp( - home: MyHomePage(), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key}); - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Your todos', - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () async { - final newTodo = Todo(content: "New Flutter todo", isDone: false); - final request = ModelMutations.create(newTodo); - final response = await Amplify.API.mutate(request: request).response; - if (response.hasErrors) { - safePrint('Creating Todo failed.'); - } else { - safePrint('Creating Todo successful.'); - } - }, - tooltip: 'Add todo', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); - } -} -``` - -Now if you run the application, and click on the floating action button, you should see a log indicating a todo was created: - -```console showLineNumbers={false} -Creating Todo successful. -``` - - -## Read data from your backend - -Next, list all your todos and then refetch the todos after a todo has been added: - - -```tsx title="src/TodoList.tsx" -import { useState, useEffect } from "react"; -import type { Schema } from "../amplify/data/resource"; -import { generateClient } from "aws-amplify/data"; - -const client = generateClient(); - -export default function TodoList() { - const [todos, setTodos] = useState([]); - - const fetchTodos = async () => { - const { data: items, errors } = await client.models.Todo.list(); - setTodos(items); - }; - - useEffect(() => { - fetchTodos(); - }, []); - - const createTodo = async () => { - await client.models.Todo.create({ - content: window.prompt("Todo content?"), - isDone: false, - }); - - fetchTodos(); - } - - return ( -
    - -
      - {todos.map(({ id, content }) => ( -
    • {content}
    • - ))} -
    -
    - ); -} -``` - - - -```html title="src/TodoList.vue" - - - -``` - - - -```ts title="todo-list.component.ts" -import type { Schema } from '../amplify/data/resource'; -import { Component, OnInit } from '@angular/core'; -import { generateClient } from 'aws-amplify/data'; - -const client = generateClient(); - -@Component({ - selector: 'app-todo-list', - template: ` -
    - -
      -
    • {{ todo.content }}
    • -
    -
    - ` -}) -export class TodoListComponent implements OnInit { - todos: Schema['Todo']['type'][] = []; - - async ngOnInit() { - await this.fetchTodos(); - } - - async fetchTodos() { - const { data: items } = await client.models.Todo.list(); - this.todos = items; - } - - async createTodo() { - await client.models.Todo.create({ - content: window.prompt('Todo content?'), - isDone: false - }); - await this.fetchTodos(); - } -} -``` - - - -Start by creating a new `TodoList` @Composable that fetches the data on the initial display of the TodoList: - -```kt title="MainActivity.kt" -@Composable -fun TodoList() { - var todoList by remember { mutableStateOf(emptyList()) } - - LaunchedEffect(Unit) { - // API request to list all Todos - Amplify.API.query(ModelQuery.list(Todo::class.java), - { - todoList = it.data.items.toList() - }, - { Log.e("MyAmplifyApp", "Failed to query.", it)}) - } - - LazyColumn { - items(todoList) { todo -> - Row { - // Render your activity item here - Checkbox(checked = todo.isDone, onCheckedChange = null) - Text(text = todo.content) - } - } - } -} -``` - -If you build and rerun the application, you should see the todo that was created in the previous build. But notice how when you click on the "create Todo" button, it doesn't add any new todos to the list below until the next time your app relaunches. To solve this, let's add real-time updates to the todo list. - - -Update the `listTodos` function in the `TodoViewModel.swift` for listing to-do items: - -```swift title="TodoViewModel.swift" -@MainActor -class TodoViewModel: ObservableObject { - - // highlight-next-line - @Published var todos: [Todo] = [] - - func createTodo() { - /// ... - } - - // highlight-start - func listTodos() { - Task { - do { - let result = try await Amplify.API.query(request: .list(Todo.self)) - switch result { - case .success(let todos): - print("Successfully retrieved list of todos: \(todos)") - self.todos = todos.elements - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } catch let error as APIError { - print("Failed to query list of todos: ", error) - } catch { - print("Unexpected error: \(error)") - } - } - } - // highlight-end -} -``` - -Now let's update the UI code to observe the todos. - -```swift title="ContentView.swift" -import SwiftUI -import Amplify - -struct ContentView: View { - @StateObject var vm = TodoViewModel() - - var body: some View { - VStack { - // highlight-start - List(vm.todos, id: \.id) { todo in - Text(todo.content ?? "") - } - // highlight-end - // .. Add a new Todo button - } - // highlight-start - .task { - await vm.listTodos() - } - // highlight-end - } -} - -``` - - - -Start by adding a new list to track the todos and the ability to fetch the todo list when it first renders: - -```dart title="lib/main.dart" -// ...main() - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return const MaterialApp( - home: MyHomePage(), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key}); - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - List _todos = []; - - @override - void initState() { - super.initState(); - _refreshTodos(); - } - - Future _refreshTodos() async { - try { - final request = ModelQueries.list(Todo.classType); - final response = await Amplify.API.query(request: request).response; - - final todos = response.data?.items; - if (response.hasErrors) { - safePrint('errors: ${response.errors}'); - return; - } - setState(() { - safePrint(todos); - _todos = todos!.whereType().toList(); - }); - } on ApiException catch (e) { - safePrint('Query failed: $e'); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Your todos', - ), - _todos.isEmpty == true - ? const Center( - child: Text( - "The list is empty.\nAdd some items by clicking the floating action button.", - textAlign: TextAlign.center, - ), - ) - : ListView.builder( - scrollDirection: Axis.vertical, - shrinkWrap: true, - itemCount: _todos.length, - itemBuilder: (context, index) { - final todo = _todos[index]; - return CheckboxListTile.adaptive( - value: todo.isDone, - title: Text(todo.content!), - onChanged: (isChecked) async {}, - ); - }), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () async { - final newTodo = Todo(content: "New Flutter todo", isDone: false); - final request = ModelMutations.create(newTodo); - final response = await Amplify.API.mutate(request: request).response; - if (response.hasErrors) { - safePrint('Creating Todo failed.'); - } else { - safePrint('Creating Todo successful.'); - } - }, - tooltip: 'Add todo', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); - } -} - -``` - - -## Subscribe to real-time updates - - -You can also use `observeQuery` to subscribe to a live feed of your backend data. Let's refactor the code to use a real-time observeQuery instead. - -```tsx title="src/App.tsx" -import type { Schema } from "../amplify/data/resource"; -import { useState, useEffect } from "react"; -import { generateClient } from "aws-amplify/data"; - -const client = generateClient(); - -export default function TodoList() { - const [todos, setTodos] = useState([]); - - useEffect(() => { - const sub = client.models.Todo.observeQuery().subscribe({ - next: ({ items }) => { - setTodos([...items]); - }, - }); - - return () => sub.unsubscribe(); - }, []); - - const createTodo = async () => { - await client.models.Todo.create({ - content: window.prompt("Todo content?"), - isDone: false, - }); - // no more manual refetchTodos required! - // - fetchTodos() - }; - - return ( -
    - -
      - {todos.map(({ id, content }) => ( -
    • {content}
    • - ))} -
    -
    - ); -} -``` - - - -You can also use `observeQuery` to subscribe to a live feed of your backend data. Let's refactor the code to use a real-time observeQuery instead. - -```html title="src/TodoList.vue" - - - -``` - - - -You can also use `observeQuery` to subscribe to a live feed of your backend data. Let's refactor the code to use a real-time observeQuery instead. - -```ts title="todo-list.component.ts" -import type { Schema } from '../../../amplify/data/resource'; -import { Component, OnInit } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { generateClient } from 'aws-amplify/data'; -import { Subscription } from 'rxjs'; - -const client = generateClient(); - -@Component({ - selector: 'app-todos', - standalone: true, - imports: [CommonModule], - template: ` -
    -

    My todos

    - -
      -
    • - {{ todo.content }} -
    • -
    -
    - πŸ₯³ App successfully hosted. Try creating a new todo. -
    - - Review next steps of this tutorial. - -
    -
    - `, -}) -export class TodosComponent implements OnInit { - todos: Schema['Todo']['type'][] = []; - subscription?: Subscription; - - ngOnInit(): void { - this.listTodos(); - } - - ngOnDestroy(): void { - this.subscription?.unsubscribe(); - } - - listTodos() { - try { - this.subscription = client.models.Todo.observeQuery().subscribe({ - next: ({ items, isSynced }) => { - this.todos = items; - }, - }); - } catch (error) { - console.error('error fetching todos', error); - } - } - - createTodo() { - try { - client.models.Todo.create({ - content: window.prompt('Todo content'), - }); - this.listTodos(); - } catch (error) { - console.error('error creating todos', error); - } - } -} -``` - -Now try to open your app in two browser windows and see how creating a todo in one window automatically adds the todo in the second window as well. - - - -You can also use `.onCreate`, `.onUpdate`, or `.onDelete` to subscribe to specific events. Review [Subscribe to real-time events](/[platform]/build-a-backend/data/subscribe-data) to learn more about subscribing to specific mutation events. - - - - - -To add real-time updates, you can use the subscription feature of Amplify Data. It allows to subscribe to `onCreate`, `onUpdate`, and `onDelete` events of the application. In our example, let's append the list every time a new todo is added. - -```kt title="MainActivity.kt" -@Composable -fun TodoList() { - var todoList by remember { mutableStateOf(emptyList()) } - - LaunchedEffect(Unit) { - Amplify.API.query(ModelQuery.list(Todo::class.java), - { - todoList = it.data.items.toList() - }, - { Log.e("MyAmplifyApp", "Failed to query.", it)}) - // highlight-start - Amplify.API.subscribe(ModelSubscription.onCreate(Todo::class.java), - { Log.i("ApiQuickStart", "Subscription established") }, - { Log.i("ApiQuickStart", "Todo create subscription received: ${it.data}") - todoList = todoList + it.data - }, - { Log.e("ApiQuickStart", "Subscription failed", it) }, - { Log.i("ApiQuickStart", "Subscription completed") } - - ) - // highlight-end - } - - LazyColumn { - items(todoList) { todo -> - Row { - // Render your activity item here - Checkbox(checked = todo.isDone, onCheckedChange = null) - Text(text = todo.content) - } - } - } -} -``` -Now call `TodoList()` from the `onCreate()` function: - -```kt title="MainActivity.kt" -setContent { - MyAmplifyAppTheme { - // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - Authenticator { state -> - Column { - Text( - text = "Hello ${state.user.username}!", - ) - .... - //highlight-next-line - TodoList() -``` - - - -To add real-time updates, you can use the subscription feature of Amplify Data. It allows to subscribe to `onCreate`, `onUpdate`, and `onDelete` events of the application. In our example, let's append the list every time a new todo is added. - -First, add a private variable to store the subscription. Then create the subscription on the `init()` initializer, and add the `subscribe()` and `cancel()` functions. - -```swift title="TodoViewModel.swift" -@MainActor -class TodoViewModel: ObservableObject { - @Published var todos: [Todo] = [] - - // highlight-start - private var subscription: AmplifyAsyncThrowingSequence> - - init() { - self.subscription = Amplify.API.subscribe(request: .subscription(of: Todo.self, type: .onCreate)) - } - - func subscribe() { - Task { - do { - for try await subscriptionEvent in subscription { - handleSubscriptionEvent(subscriptionEvent) - } - } catch { - print("Subscription has terminated with \(error)") - } - } - } - - private func handleSubscriptionEvent(_ subscriptionEvent: GraphQLSubscriptionEvent) { - switch subscriptionEvent { - case .connection(let subscriptionConnectionState): - print("Subscription connect state is \(subscriptionConnectionState)") - case .data(let result): - switch result { - case .success(let createdTodo): - print("Successfully got todo from subscription: \(createdTodo)") - todos.append(createdTodo) - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } - } - - func cancel() { - self.subscription.cancel() - } - // highlight-end - - func createTodo() { - /// ... - } - - func listTodos() { - /// ... - } -} -``` - -Then in `ContentView.swift`, when the view appears, call `vm.subscribe()`. On disappear, cancel the subscription. - -```swift title="ContentView.swift" -struct ContentView: View { - @StateObject var vm = TodoViewModel() - - var body: some View { - VStack { - // ... - } - // highlight-start - .onDisappear { - vm.cancel() - } - .task { - vm.listTodos() - vm.subscribe() - } - // highlight-end - } -} -``` - -Now if you rerun your app, a new todo should be appended to the list every time you create a new todo. - - - -To add real-time updates, you can use the subscription feature of Amplify Data. It allows to subscribe to `onCreate`, `onUpdate`, and `onDelete` events of the application. In our example, let's append the list every time a new todo is added. - -When the page renders, subscribe to `onCreate` events and then unsubscribe when the Widget is disposed. - -```dart title="lib/main.dart" -// ...main() -// ...MyApp -// ...MyHomePage - -class _MyHomePageState extends State { - List _todos = []; - // highlight-next-line - StreamSubscription>? subscription; - - @override - void initState() { - super.initState(); - _refreshTodos(); - // highlight-next-line - _subscribe(); - } - - // highlight-start - @override - void dispose() { - _unsubscribe(); - super.dispose(); - } - // highlight-end - - // highlight-start - void _subscribe() { - final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); - final Stream> operation = Amplify.API.subscribe( - subscriptionRequest, - onEstablished: () => safePrint('Subscription established'), - ); - subscription = operation.listen( - (event) { - safePrint('Subscription event data received: ${event.data}'); - setState(() { - _todos.add(event.data!); - }); - }, - onError: (Object e) => safePrint('Error in subscription stream: $e'), - ); - } - // highlight-end - - // highlight-start - void _unsubscribe() { - subscription?.cancel(); - subscription = null; - } - // highlight-end - - // ..._refreshTodos() - // ...build() -} -``` - - -## Conclusion - -Success! You've learned how to create your first real-time API and database with Amplify Data. - -### Next steps - -There's so much more to discover with Amplify Data. Learn more about: - -- [How to model your database table and their access patterns](/[platform]/build-a-backend/data/data-modeling) -- [Secure your API with fine-grained authorization rules](/[platform]/build-a-backend/data/customize-authz) -- [Create relationships between different database model](/[platform]/build-a-backend/data/data-modeling/relationships) -- [Add custom business logic](/[platform]/build-a-backend/data/custom-business-logic) - ---- - ---- -title: "Connect your app code to API" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-07T16:31:16.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/connect-to-API/" ---- - -In this guide, you will connect your application code to the backend API using the Amplify Libraries. Before you begin, you will need: - -- Your cloud sandbox with an Amplify Data resource up and running (`npx ampx sandbox`) -- A frontend application set up with the Amplify library installed -- [npm installed](https://docs.npmjs.com/getting-started) - -## Configure the Amplify Library - -When you deploy you're iterating on your backend (`npx ampx sandbox`), an **amplify_outputs.json** file is generated for you. This file contains your API's endpoint information and auth configurations. Add the following code to your app's entrypoint to initialize and configure the Amplify client library: - - -```ts -import { Amplify } from 'aws-amplify'; -import outputs from '../amplify_outputs.json'; - -Amplify.configure(outputs); -``` - -## Generate the Amplify Data client - -Once the Amplify library is configured, you can generate a "Data client" for your frontend code to make fully-typed API requests to your backend. - -> **Info:** **If you're using Amplify with a JavaScript-only frontend (i.e. not TypeScript), then you can still get a fully-typed data fetching experience by annotating the generated client with a JSDoc comment**. Select the **JavaScript** in the code block below to see how. - -To generate a new Data client, use the following code: - -#### [TypeScript] -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient(); - -// Now you should be able to make CRUDL operations with the -// Data client -const fetchTodos = async () => { - const { data: todos, errors } = await client.models.Todo.list(); -}; -``` - -#### [JavaScript] -```js -import { generateClient } from 'aws-amplify/data'; - -/** - * @type {import('aws-amplify/data').Client} - */ -const client = generateClient(); - -// Now you should be able to make CRUDL operations with the -// Data client -const fetchTodos = async () => { - const { data: todos, errors } = await client.models.Todo.list(); -}; -``` - - - -## Configure authorization mode - -The **Authorization Mode** determines how a request should be authorized with the backend. By default, Amplify Data uses the "userPool" authorization which uses the signed-in user credentials to sign an API request. If you use a `allow.publicApiKey()` authorization rules for your data models, you need to use "apiKey" as an authorization mode. Review [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz) to learn more about which authorization modes to choose for which type of request. A **Default Authorization Mode** is provided as part of the **amplify_outputs.json** that is generated upon a successful deployment. - - -You can generate different Data clients with different authorization modes or pass in the authorization mode at the request time. - -### Set authorization mode on a per-client basis - -To apply the same authorization mode on all requests from a Data client, specify the `authMode` parameter on the `generateClient` function. - -#### [API Key] - -Use "API Key" as your authorization mode when if defined the `allow.publicApiKey()` authorization rule. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient({ - authMode: 'apiKey', -}); -``` - -#### [Amazon Cognito user pool] - -Use "userPool" as your authorization mode when using Amazon Cognito user pool-based authorization rules, such as `allow.authenticated()`, `allow.owner()`, `allow.ownerDefinedIn()`, `allow.groupsDefinedIn()`, or `allow.groups()`. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient({ - authMode: 'userPool', -}); -``` - -#### [AWS IAM (including Amazon Cognito identity pool roles)] - -Use "identityPool" as your authorization mode when using Amazon Cognito identity pool-based authorization rules, such as `allow.guest()` or `allow.authenticated('identityPool')`. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient({ - authMode: 'identityPool', -}); -``` - -#### [OpenID Connect (OIDC)] -Use "oidc" as your authorization mode when connecting applications to a trusted identity provider. Private, owner, and group authorization can be configured with an OIDC authorization mode. Review the [OIDC authorization docs](/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider) to learn more. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient({ - authMode: 'oidc', -}); -``` - -#### [Lambda Authorizer] - -Use "Lambda Authorizer" when using your own custom authorization logic via `allow.custom()`. Review [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz) to learn more about how to implement your authorization protocol. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const getAuthToken = () => 'myAuthToken'; -const lambdaAuthToken = getAuthToken(); - -const client = generateClient({ - authMode: 'lambda', - authToken: lambdaAuthToken, -}); -``` - - - -### Set authorization mode on the request-level - -You can also specify the authorization mode on each individual API request. This is useful if your application typically only uses one authorization mode with a small number of exceptions. - -#### [API Key] - - -```ts -const { data: todos, errors } = await client.models.Todo.list({ - authMode: 'apiKey', -}); -``` - - - -```kt -val query = ModelQuery.list(Todo::class.java) as AppSyncGraphQLRequest> -val apiKeyQuery = query - .newBuilder() - .authorizationType(AuthorizationType.API_KEY) - .build>() -Amplify.API.query(apiKeyQuery, - { Log.i("MyAmplifyApp", "Queried with API key ${it.data}")}, - { Log.e("MyAmplifyApp", "Error querying with API Key")}) -``` - - - -```swift -let result = try await Amplify.API.query( - request: .list( - Todo.self, - authMode: .apiKey)) -``` - - - -```dart -final apiKeyRequest = ModelQueries.list(Todo.classType, authorizationMode: APIAuthorizationType.apiKey); -final apiKeyResponse = await Amplify.API.query(request: apiKeyRequest).response; -``` - - -#### [Amazon Cognito user pool] - - -```ts -const { data: todos, errors } = await client.models.Todo.list({ - authMode: 'userPool', -}); -``` - - - -```kt -val query = ModelQuery.list(Todo::class.java) as AppSyncGraphQLRequest> -val userPoolQuery = query - .newBuilder() - .authorizationType(AuthorizationType.AMAZON_COGNITO_USER_POOLS) - .build>() -Amplify.API.query(userPoolQuery, - { Log.i("MyAmplifyApp", "Queried with Cognito user pool ${it.data}")}, - { Log.e("MyAmplifyApp", "Error querying with Cognito user pool")}) -``` - - - -```swift -let result = try await Amplify.API.query( - request: .list( - Todo.self, - authMode: .amazonCognitoUserPools)) -``` - - - -```dart -final userPoolRequest = ModelQueries.list(Todo.classType, authorizationMode: APIAuthorizationType.userPools); -final userPoolResponse = await Amplify.API.query(request: userPoolRequest).response; -``` - - -#### [AWS IAM (including Amazon Cognito identity pool roles)] - - -```ts -const { data: todos, errors } = await client.models.Todo.list({ - authMode: 'identityPool', -}); -``` - - - -```kt -val query = ModelQuery.list(Todo::class.java) as AppSyncGraphQLRequest> -val iamQuery = query - .newBuilder() - .authorizationType(AuthorizationType.AWS_IAM) - .build>() -Amplify.API.query(iamQuery, - { Log.i("MyAmplifyApp", "Queried with AWS IAM ${it.data}")}, - { Log.e("MyAmplifyApp", "Error querying with AWS IAM")}) -``` - - - -```swift -let result = try await Amplify.API.query( - request: .list( - Todo.self, - authMode: .awsIAM)) -``` - - - -```dart -final iamRequest = ModelQueries.list(Todo.classType, authorizationMode: APIAuthorizationType.iam); -final iamResponse = await Amplify.API.query(request: iamRequest).response; -``` - - -#### [OpenID Connect (OIDC)] - - -```ts -const { data: todos, errors } = await client.models.Todo.list({ - authMode: 'oidc', -}); -``` - - - -```kt -val query = ModelQuery.list(Todo::class.java) as AppSyncGraphQLRequest> -val oidcQuery = query - .newBuilder() - .authorizationType(AuthorizationType.OPENID_CONNECT) - .build>() -Amplify.API.query(oidcQuery, - { Log.i("MyAmplifyApp", "Queried with OIDC authorization mode ${it.data}")}, - { Log.e("MyAmplifyApp", "Error querying with OIDC authorization mode")}) -``` - - - -```swift -let result = try await Amplify.API.query( - request: .list( - Todo.self, - authMode: .openIDConnect)) -``` - - - -```dart -final oidcRequest = ModelQueries.list(Todo.classType, authorizationMode: APIAuthorizationType.oidc); -final oidcResponse = await Amplify.API.query(request: oidcRequest).response; -``` - - -#### [Lambda Authorizer] - -You can implement your own custom API authorization logic using a AWS Lambda function. Review [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz) to learn more about how to implement your authorization protocol with AWS Lambda. - - -```ts -const getAuthToken = () => 'myAuthToken'; -const lambdaAuthToken = getAuthToken(); - -const { data: todos, errors } = await client.models.Todo.list({ - authMode: 'lambda', - authToken: lambdaAuthToken, -}); -``` - - - -```kt -val query = ModelQuery.list(Todo::class.java) as AppSyncGraphQLRequest> -val lambdaQuery = query - .newBuilder() - .authorizationType(AuthorizationType.AWS_LAMBDA) - .build>() -Amplify.API.query(lambdaQuery, - { Log.i("MyAmplifyApp", "Queried with AWS Lambda authorizer ${it.data}")}, - { Log.e("MyAmplifyApp", "Error querying with AWS Lambda authorizer")}) -``` - - - -```swift -let result = try await Amplify.API.query( - request: .list( - Todo.self, - authMode: .function)) -``` - - - -```dart -final lambdaRequest = ModelQueries.list(Todo.classType, authorizationMode: APIAuthorizationType.function); -final lambdaResponse = await Amplify.API.query(request: lambdaRequest).response; -``` - - -## Set custom request headers - -When working with the Amplify Data endpoint, you may need to set request headers for authorization purposes or to pass additional metadata from your frontend to the backend API. - - -This is done by specifying a `headers` parameter into the configuration. You can define headers either on a per Data client-level or on a per-request level: - -#### [Custom headers per Data client] - -```ts -import type { Schema } from '../amplify/data/resource'; -import { generateClient } from 'aws-amplify/data'; - -const client = generateClient({ - headers: { - 'My-Custom-Header': 'my value', - }, -}); -``` - -#### [Custom headers per request] - -```ts -// same way for all CRUDL: .create, .get, .update, .delete, .list, .observeQuery -const { data: blog, errors } = await client.models.Blog.get( - { id: 'myBlogId' }, - { - headers: { - 'My-Custom-Header': 'my value', - }, - } -); -``` - -The examples above show you how to set static headers but you can also programmatically set headers by specifying an async function for `headers`: - -#### [Custom headers per Data client] - -```ts -import type { Schema } from '../amplify/data/resource'; -import { generateClient } from 'aws-amplify/data'; - -const client = generateClient({ - headers: async (requestOptions) => { - console.log(requestOptions); - /* The request options allow you to customize your headers based on the request options such - as http method, headers, request URI, and query string. These options are typically used - to create a request signature. - { - method: '...', - headers: { }, - uri: '/', - queryString: "" - } - */ - return { - 'My-Custom-Header': 'my value', - }; - }, -}); -``` - -#### [Custom headers per request] - -```ts -// same way for all CRUDL: .create, .get, .update, .delete, .list, .observeQuery -const res = await client.models.Blog.get( - { id: 'myBlogId' }, - { - headers: async (requestOptions) => { - console.log(requestOptions); - /* The request options allow you to customize your headers based on the request options such - as http method, headers, request URI, and query string. These options are typically used - to create a request signature. - { - method: '...', - headers: { }, - uri: '/', - queryString: "" - } - */ - return { - 'My-Custom-Header': 'my value', - }; - }, - } -); -``` - - - - -To specify your own headers, use the `configureClient()` configuration option on the `AWSApiPlugin`'s builder. Specify the name of one of the configured APIs in your **amplify_outputs.json**. Apply customizations to the underlying OkHttp instance by providing a lambda expression as below. - -#### [Java] - -```java -AWSApiPlugin plugin = AWSApiPlugin.builder() - .configureClient(AWSApiPlugin.DEFAULT_GRAPHQL_API, okHttpBuilder -> { - okHttpBuilder.addInterceptor(chain -> { - Request originalRequest = chain.request(); - Request updatedRequest = originalRequest.newBuilder() - .addHeader("customHeader", "someValue") - .build(); - return chain.proceed(updatedRequest); - }); - }) - .build(); -Amplify.addPlugin(plugin); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val plugin = AWSApiPlugin.builder() - .configureClient(AWSApiPlugin.DEFAULT_GRAPHQL_API) { okHttpBuilder -> - okHttpBuilder.addInterceptor { chain -> - val originalRequest = chain.request() - val updatedRequest = originalRequest.newBuilder() - .addHeader("customHeader", "someValue") - .build() - chain.proceed(updatedRequest) - } - } - .build() -Amplify.addPlugin(plugin) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val plugin = AWSApiPlugin.builder() - .configureClient(AWSApiPlugin.DEFAULT_GRAPHQL_API) { okHttpBuilder -> - okHttpBuilder.addInterceptor { chain -> - val originalRequest = chain.request() - val updatedRequest = originalRequest.newBuilder() - .addHeader("customHeader", "someValue") - .build() - chain.proceed(updatedRequest) - } - } - .build() -Amplify.addPlugin(plugin) -``` - -#### [RxJava] - -```java -AWSApiPlugin plugin = AWSApiPlugin.builder() - .configureClient(AWSApiPlugin.DEFAULT_GRAPHQL_API, okHttpBuilder -> { - okHttpBuilder.addInterceptor(chain -> { - Request originalRequest = chain.request(); - Request updatedRequest = originalRequest.newBuilder() - .addHeader("customHeader", "someValue") - .build(); - return chain.proceed(updatedRequest); - }); - }) - .build(); -RxAmplify.addPlugin(plugin); -``` - - - - -To include custom headers in your outgoing requests, add an `URLRequestInterceptor` to the `AWSAPIPlugin`. - -```swift -import Amplify -import AWSAPIPlugin - -struct CustomInterceptor: URLRequestInterceptor { - func intercept(_ request: URLRequest) throws -> URLRequest { - var request = request - request.setValue("headerValue", forHTTPHeaderField: "headerKey") - return request - } -} -let apiPlugin = AWSAPIPlugin(modelRegistration: AmplifyModels()) -try apiPlugin.add(interceptor: CustomInterceptor(), for: AWSAPIPlugin.defaultGraphQLAPI) -try Amplify.add(plugin: apiPlugin) -try Amplify.configure(with: .amplifyOutputs) - -``` - - - -The simplest option for GraphQL requests is to use the `headers` property of a `GraphQLRequest`. - -```dart -Future queryWithCustomHeaders() async { - final operation = Amplify.API.query( - request: GraphQLRequest( - document: graphQLDocumentString, - headers: {'customHeader': 'someValue'}, - ), - ); - final response = await operation.response; - final data = response.data; - safePrint('data: $data'); -} -``` - -Another option is to use the `baseHttpClient` property of the API plugin which can customize headers or otherwise alter HTTP functionality for all HTTP calls. - -```dart -// First create a custom HTTP client implementation to extend HTTP functionality. -class MyHttpRequestInterceptor extends AWSBaseHttpClient { - @override - Future transformRequest( - AWSBaseHttpRequest request, - ) async { - request.headers.putIfAbsent('customHeader', () => 'someValue'); - return request; - } -} - -// Then you can pass an instance of this client to `baseHttpClient` when you configure Amplify. -await Amplify.addPlugins([ - AmplifyAPI(baseHttpClient: MyHttpRequestInterceptor()), -]); -``` - - - -## Use an additional Data endpoint - -If you have an additional Data endpoint that you're managing with a different Amplify project or through other means, this section will show you how to utilize that endpoint in your frontend code. - -This is done by specifying the `endpoint` parameter on the `generateClient` function. - -```ts -import { generateClient } from 'aws-amplify/data'; - -const client = generateClient({ - endpoint: 'https://my-other-endpoint.com/graphql', -}); -``` - -If this Data endpoint shares its authorization configuration (for example, both endpoints share the same user pool and/or identity pool as the one in your `amplify_outputs.json` file), you can specify the `authMode` parameter on `generateClient`. - -```ts -const client = generateClient({ - endpoint: 'https://my-other-endpoint.com/graphql', - authMode: 'userPool', -}); -``` - -If the endpoint uses API Key authorization, you can pass in the `apiKey` parameter on `generateClient`. - -```ts -const client = generateClient({ - endpoint: 'https://my-other-endpoint.com/graphql', - authMode: 'apiKey', - apiKey: 'my-api-key', -}); -``` - -If the endpoint uses a different authorization configuration, you can manually pass in the authorization header using the instructions in the [Set custom request headers](#set-custom-request-headers) section. - - ---- - ---- -title: "Create, update, and delete application data" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-10-15T16:14:40.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/mutate-data/" ---- - -In this guide, you will learn how to create, update, and delete your data using Amplify Libraries' Data client. - -Before you begin, you will need: - -- An [application connected to the API](/[platform]/build-a-backend/data/connect-to-API/) - -## Create an item - - -You can create an item by first generating the Data client with your backend Data schema. Then you can add an item: - -```ts -import { generateClient } from 'aws-amplify/data'; -import { type Schema } from '../amplify/data/resource' - -const client = generateClient(); - -const { errors, data: newTodo } = await client.models.Todo.create({ - content: "My new todo", - isDone: true, -}) -``` - - - -**Note:** You do not need to specify `createdAt` or `updatedAt` fields because Amplify automatically populates these fields for you. - - - - - -You can run a GraphQL mutation with `Amplify.API.mutate` to create an item. - -#### [Async/Await] - -Make sure you have the following imports at the top of your file: - -```swift -import Amplify -``` - -```swift -func createTodo() async { - // Retrieve your Todo using Amplify.API.query - var todo = Todo(name: "my first todo", description: "todo description") - todo.description = "created description" - do { - let result = try await Amplify.API.mutate(request: .create(todo)) - switch result { - case .success(let todo): - print("Successfully created todo: \(todo)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } catch let error as APIError { - print("Failed to create todo: ", error) - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -You can run a GraphQL mutation with `Amplify.API.mutate`. Make sure you have the following imports at the top of your file: - -```swift -import Amplify -import Combine -``` - -```swift -func createTodo() -> AnyCancellable { - // Retrieve your Todo using Amplify.API.query - var todo = Todo(name: "my first todo", description: "todo description") - todo.description = "created description" - let todoCreated = todo - let sink = Amplify.Publisher.create { - try await Amplify.API.mutate(request: .create(todoCreated)) - } - .sink { - if case let .failure(error) = $0 { - print("Got failed event with error \(error)") - } - } - receiveValue: { result in - switch result { - case .success(let todo): - print("Successfully created todo: \(todo)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } - return sink -} -``` - - - - -Now that the client is set up, you can run a GraphQL mutation with `Amplify.API.mutate` to create your data. - -#### [Java] - -```java -Todo todo = Todo.builder() - .name("My todo") - .build(); - -Amplify.API.mutate(ModelMutation.create(todo), - response -> Log.i("MyAmplifyApp", "Todo with id: " + response.getData().getId()), - error -> Log.e("MyAmplifyApp", "Create failed", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val todo = Todo.builder() - .name("My todo") - .build() - -Amplify.API.mutate(ModelMutation.create(todo), - { Log.i("MyAmplifyApp", "Todo with id: ${it.data.id}") } - { Log.e("MyAmplifyApp", "Create failed", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val todo = Todo.builder() - .name("My todo") - .build() -try { - val response = Amplify.API.mutate(ModelMutation.create(todo)) - Log.i("MyAmplifyApp", "Todo with id: ${response.data.id}") -} catch (error: ApiException) { - Log.e("MyAmplifyApp", "Create failed", error) -} -``` - -#### [RxJava] - -```java -Todo todo = Todo.builder() - .name("My todo") - .build(); - -RxAmplify.API.mutate(ModelMutation.create(todo)) - .subscribe( - response -> Log.i("MyAmplifyApp", "Todo with id: " + response.getData().getId()), - error -> Log.e("MyAmplifyApp", "Create failed", error) - ); -``` - - - - -You can run a GraphQL mutation with `Amplify.API.mutate` to create your data. - -```dart -Future createTodo() async { - try { - final todo = Todo(name: 'my first todo', description: 'todo description'); - final request = ModelMutations.create(todo); - final response = await Amplify.API.mutate(request: request).response; - - final createdTodo = response.data; - if (createdTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - safePrint('Mutation result: ${createdTodo.name}'); - } on ApiException catch (e) { - safePrint('Mutation failed: $e'); - } -} -``` - - -## Update an item - - -To update the item, use the `update` function: - -```ts -import { generateClient } from 'aws-amplify/data'; -import { type Schema } from '../amplify/data/resource'; - -const client = generateClient(); - -const todo = { - id: 'some_id', - content: 'Updated content', -}; - -const { data: updatedTodo, errors } = await client.models.Todo.update(todo); -``` - - - -**Notes:** - -- You do not need to specify the `updatedAt` field. Amplify will automatically populate this field for you. -- If you specify _extra_ input fields not expected by the API, this query will fail. You can see this in the `errors` field returned by the query. With Amplify Data, errors are not thrown like exceptions. Instead, any errors are captured and returned as part of the query result in the `errors` field. - - - - - -To update data, replace the request with `.update`. - -```swift -try await Amplify.API.mutate(request: .update(todo)) -``` - - - -To update data, use `ModelMutation.update(todo)` instead. - - - -To update the `Todo` with a new name: - -```dart -Future updateTodo(Todo originalTodo) async { - final todoWithNewName = originalTodo.copyWith(name: 'new name'); - - final request = ModelMutations.update(todoWithNewName); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Response: $response'); -} -``` - - -## Delete an item - - -You can then delete the Todo by using the delete mutation. To specify which item to delete, you only need to provide the `id` of that item: - -```js -import { generateClient } from 'aws-amplify/data'; -import { type Schema } from '../amplify/data/resource' - -const client = generateClient(); - -const toBeDeletedTodo = { - id: '123123213' -} - -const { data: deletedTodo, errors } = await client.models.Todo.delete(toBeDeletedTodo) -``` - - - -**Note:** When deleting items in many-to-many relationships, the join table records must be deleted before deleting the associated records. For example, for a many-to-many relationship between Posts and Tags, delete the PostTags join record before deleting a Post or Tag. Review [Many-to-many relationships](/[platform]/build-a-backend/data/data-modeling/relationships/) for more details. - - - - - -Each API request uses an authorization mode. If you get unauthorized errors, you may need to update your authorization mode. To override the default authorization mode defined in your **amplify/data/resource.ts** file, pass an `authMode` property to the request or the client. The following examples show how you can mutate data with a custom authorization mode: - -```ts -import { generateClient } from 'aws-amplify/data'; -import { type Schema } from '../amplify/data/resource'; - -const client = generateClient(); - -const { errors, data: newTodo } = await client.models.Todo.create( - { - content: 'My new todo', - isDone: true, - }, - { - authMode: 'apiKey', - } -); -``` - - - - - -To delete data, replace the request with `.delete`. - -```swift -try await Amplify.API.mutate(request: .delete(todo)) -``` - - - -To delete data, use `ModelMutation.delete(todo)`. - - - -To delete the `Todo`: - -```dart -Future deleteTodo(Todo todoToDelete) async { - final request = ModelMutations.delete(todoToDelete); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Response: $response'); -} -``` - -Or you can delete by ID, which is ideal if you do not have the instance in memory yet: - -```dart -Future deleteTodoById(Todo todoToDelete) async { - final request = ModelMutations.deleteById( - Todo.classType, - TodoModelIdentifier(id: '8e0dd2fc-2f4a-4dc4-b47f-2052eda10775'), - ); - final response = await Amplify.API.mutate(request: request).response; - safePrint('Response: $response'); -} -``` - - - -## Cancel create, update, and delete requests - -You can cancel any mutation API request by calling `.cancel` on the mutation request promise that's returned by `.create(...)`, `.update(...)`, or `.delete(...)`. - -```ts -const promise = client.models.Todo.create({ content: 'New Todo' }); -// ^ Note: we're not awaiting the request, we're returning the promise - -try { - await promise; -} catch (error) { - console.log(error); - // If the error is because the request was cancelled you can confirm here. - if (client.isCancelError(error)) { - console.log(error.message); // "my message for cancellation" - // handle user cancellation logic - } -} - -//... - -// To cancel the above request -client.cancel(promise, 'my message for cancellation'); -``` - -You need to ensure that the promise returned from `.create()`, `.update()`, and `.delete()` has not been modified. Typically, async functions wrap the promise being returned into another promise. For example, the following will **not** work: - -```ts -async function makeAPICall() { - return client.models.Todo.create({ content: 'New Todo' }); -} -const promise = makeAPICall(); - -// The following will NOT cancel the request. -client.cancel(promise, 'my error message'); -``` - - -## Conclusion - -Congratulations! You have finished the **Create, update, and delete application data** guide. In this guide, you created, updated, and deleted your app data. - -### Next steps - -Our recommended next steps include using the API to query data and subscribe to real-time events to look for mutations in your data. Some resources that will help with this work include: - -- [Read application data](/[platform]/build-a-backend/data/query-data/) -- [Subscribe to real-time events](/[platform]/build-a-backend/data/subscribe-data/) - ---- - ---- -title: "Read application data" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-07-23T09:15:04.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/query-data/" ---- - - -You can read application data using the Amplify Data client. In this guide, we will review the difference between reading data and getting data, how to filter query results to get just the data you need, and how to paginate results to make your data more manageable. We will also show you how to cancel these requests when needed. - -Before you begin, you will need: - -- An [application connected to the API](/[platform]/build-a-backend/data/connect-to-API/) -- Data already created to view - -## List and get your data - -Queries are used to read data through the API and include the `list` and `get` operations. Amplify Data automatically creates `list` and `get` queries for any `a.model()` type in your schema. The `list` query retrieves multiple items, such as Todo items, without needing to specific an identifier for a particular record. This is best suited for getting an overview or summary of items, or for enhancing the `list` operation to filter the items by specific criteria. When you want to query a single entry by an identifier, you would use `get` to retrieve a specific Todo item. - - - -**Note:** The cost structure of your underlying data source can impact the cost to run some queries. For example, the `list` operation uses Amazon DynamoDB "scan operations," which can use more read request units than the `get` operation. You will want to review the associated costs for these operations for your data source. In our example, we are using DynamoDB. You can learn more about how DynamoDB costs are calculated by visiting [Amazon DynamoDB pricing](https://aws.amazon.com/dynamodb/pricing/). - - - -You can list items by first generating the Data client with your backend Data schema. Then you can list items of your desired model: - -```ts -import { generateClient } from 'aws-amplify/data'; -import { type Schema } from '@/amplify/data/resource'; - -const client = generateClient(); - -// list items -const { data: todos, errors } = await client.models.Todo.list(); - -// get a specific item -const { data: todo, errors } = await client.models.Todo.get({ - id: '...', -}); -``` - - - -Each API request uses an authorization mode. If you get unauthorized errors, you may need to update your authorization mode. To override the default authorization mode defined in your **amplify/data/resource.ts** file, pass an `authMode` property to the request or the client. The following examples show how you can mutate data with a custom authorization mode: - -```ts -import { generateClient } from 'aws-amplify/data'; -import { type Schema } from '@/amplify/data/resource'; - -const client = generateClient(); - -const { errors, data: todos } = await client.models.Todo.list({ - authMode: 'apiKey', -}); -``` - - - -## Filter list queries - -As your data grows, you will need to paginate your list queries. Fortunately, this is already built in to Amplify Data. - -```ts -import { generateClient } from 'aws-amplify/data'; -import { type Schema } from '@/amplify/data/resource'; - -const client = generateClient(); - -const { data: todos, errors } = await client.models.Todo.list({ - filter: { - content: { - beginsWith: 'hello' - } - } -}); -``` - -### Compound filters - -You can combine filters with `and`, `or`, and `not` Boolean logic. Observe that `filter` is recursive in respect to those fields. So if, for example, you wanted to filter for `priority` values of 1 _or_ 2, you would do this: - -```ts -import { generateClient } from 'aws-amplify/data'; -import { type Schema } from '@/amplify/data/resource'; - -const client = generateClient(); - -const { data: todos, errors } = await client.models.Todo.list({ - filter: { - or: [ - { - priority: { eq: '1' } - }, - { - priority: { eq: '2' } - } - ] - } -}); -``` - -Note that querying for `priority` of 1 and 2 would return no results, because this is Boolean logic instead of natural language. - -## Paginate list queries - -To paginate your list query results, make a subsequent list query request with the `nextToken` and `limit` input variable set. The `limit` variable limits how many results are returned. The response will include a `nextToken` you can use to request the next page of data. A `nextToken` is a very long string that represents the cursor to the starting item of the next query made with these filters. - -```ts -import { generateClient } from 'aws-amplify/data'; -import { type Schema } from '@/amplify/data/resource'; - -const client = generateClient(); - -const { - data: todos, - nextToken, // Repeat this API call with the nextToken until the returned nextToken is `null` - errors -} = await client.models.Todo.list({ - limit: 100, // default value is 100 - nextToken: 'eyJ2ZXJzaW9uejE1a2...' // previous nextToken -}); -``` - - -If you're building a React application, you can use the `usePagination` hook in Amplify UI to help with managing the pagination user experience. - -```js -import * as React from 'react'; -import { Pagination } from '@aws-amplify/ui-react'; - -export const PaginationHasMorePagesExample = () => { - const [pageTokens, setPageTokens] = React.useState([null]); - const [currentPageIndex, setCurrentPageIndex] = React.useState(1); - const [hasMorePages, setHasMorePages] = React.useState(true); - - const handleNextPage = async () => { - if (hasMorePages && currentPageIndex === pageTokens.length) { - const { data: todos, nextToken } = await client.models.Todo.list({ - nextToken: pageTokens[pageTokens.length - 1] - }); - - if (!nextToken) { - setHasMorePages(false); - } - - setPageTokens([...pageTokens, nextToken]); - } - - setCurrentPageIndex(currentPageIndex + 1); - }; - - return ( - setCurrentPageIndex(currentPageIndex - 1)} - onChange={(pageIndex) => setCurrentPageIndex(pageIndex)} - /> - ); -}; -``` - - - - -**Limitations:** - -- There is no API to get a total page count at this time. Note that scanning all items is a [potentially expensive operation](https://github.com/aws-amplify/amplify-js/issues/2901). -- You [cannot query by `page` number](https://github.com/aws-amplify/amplify-cli/issues/5086); you have to query by `nextToken`. - - - -## Fetch only the data you need with custom selection set - -A business domain model may contain many models with numerous fields. However, apps typically only need subsets of the data or fields to meet the requirements of different components or screens. It is necessary to have a mechanism to retrieve subsets of models and their relationships. This mechanism would help optimize data usage for screens and components by only transferring needed data. Having this capability would improve the app's data efficiency, latency, and the end user's perceived performance. - -A **custom selection set** allows consumers to specify, on a per-call basis, the fields the consumer wants to retrieve; this is possible for all operations that return data (CRUDL + `observeQuery`). The desired fields are specified in a strongly typed way (discoverable through IntelliSense) with a "dot notation". - -```ts -// same way for all CRUDL: .create, .get, .update, .delete, .list, .observeQuery -const { data: blogWithSubsetOfData, errors } = await client.models.Blog.get( - { id: blog.id }, - { - selectionSet: ['author.email', 'posts.*'], - } -); -``` - -## TypeScript type helpers for Amplify Data - -When using TypeScript, you frequently need to specify data model types for type generics. - - -For instance, with React's `useState`, you provide a type in TypeScript to ensure type-safety in your component code using the state. Use the `Schema["MODEL_NAME"]["type"]` pattern to get TypeScript types for the shapes of data models returned from the backend API. - -```ts -import { type Schema } from '@/amplify/data/resource'; - -type Post = Schema['Post']['type']; - -const [posts, setPosts] = useState([]); -``` - - - -```ts -import { type Schema } from '../../../amplify/data/resource'; - -type Post = Schema['Post']['type']; -``` - - -You can combine the `Schema["MODEL_NAME"]["type"]` type with the `SelectionSet` helper type to describe the return type of API requests using the `selectionSet` parameter: - - -```ts -import type { SelectionSet } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; - -const selectionSet = ['content', 'blog.author.*', 'comments.*'] as const; -type PostWithComments = SelectionSet; - -// ... -const [posts, setPosts] = useState([]); - -const fetchPosts = async () => { - const { data: postsWithComments } = await client.models.Post.list({ - selectionSet, - }); - setPosts(postsWithComments); -} -``` - - - -```ts - - - -``` - - - -```ts -import type { Schema } from '../../../amplify/data/resource'; -import { Component, OnInit } from '@angular/core'; -import { generateClient, type SelectionSet } from 'aws-amplify/data'; -import { CommonModule } from '@angular/common'; - -const client = generateClient(); - -const selectionSet = ['content', 'blog.author.*', 'comments.*'] as const; - -type PostWithComments = SelectionSet< - Schema['Post']['type'], - typeof selectionSet ->; - -@Component({ - selector: 'app-todos', - standalone: true, - imports: [CommonModule], - templateUrl: './todos.component.html', - styleUrls: ['./todos.component.css'], -}) -export class TodosComponent implements OnInit { - posts: PostWithComments[] = []; - - constructor() {} - - ngOnInit(): void { - this.fetchPosts(); - } - - async fetchPosts(): Promise { - const { data: postsWithComments } = await client.models.Post.list({ - selectionSet, - }); - this.posts = postsWithComments; - } -} -``` - - -## Cancel read requests - -You can cancel any query API request by calling `.cancel` on the query request promise that's returned by `.list(...)` or `.get(...)`. - -```javascript -const promise = client.models.Todo.list(); -// ^ Note: we're not awaiting the request, we're returning the promise - -try { - await promise; -} catch (error) { - console.log(error); - // If the error is because the request was cancelled you can confirm here. - if (client.isCancelError(error)) { - console.log(error.message); // "my message for cancellation" - // handle user cancellation logic - } -} -... - -// To cancel the above request -client.cancel(promise, "my message for cancellation"); -``` - -You need to ensure that the promise returned from `.list()` or `.get()` has not been modified. Typically, async functions wrap the promise being returned into another promise. For example, the following will **not** work: - -```javascript -async function makeAPICall() { - return client.models.Todo.list(); -} -const promise = makeAPICall(); - -// The following will NOT cancel the request. -client.cancel(promise, 'my error message'); -``` - -## Conclusion - -Congratulations! You have finished the **Read application data** guide. In this guide, you learned how to read your data through `get` and `list` queries. - -### Next steps - -Our recommended next steps include subscribing to real-time events to look for mutations in your data and continuing to build out and customize your information architecture for your data. Some resources that will help with this work include: - -- [Subscribe to real-time events](/[platform]/build-a-backend/data/subscribe-data/) -- [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz/) -- [Customize your data model](/[platform]/build-a-backend/data/data-modeling/) -- [Add custom business logic](/[platform]/build-a-backend/data/custom-business-logic/) - - - -## Query by Id - -Now that you were able to make a mutation, take the `Id` that was printed out and use it in your query to retrieve data. - -#### [Async/Await] - -```swift -func getTodo() async { - do { - let result = try await Amplify.API.query( - request: .get(Todo.self, byId: "9FCF5DD5-1D65-4A82-BE76-42CB438607A0") - ) - - switch result { - case .success(let todo): - guard let todo = todo else { - print("Could not find todo") - return - } - print("Successfully retrieved todo: \(todo)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } catch let error as APIError { - print("Failed to query todo: ", error) - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func getTodo() -> AnyCancellable { - let sink = Amplify.Publisher.create { - try await Amplify.API.query( - request: .get(Todo.self, byId: "9FCF5DD5-1D65-4A82-BE76-42CB438607A0") - ) - } - .sink { - if case let .failure(error) = $0 { - print("Got failed event with error \(error)") - } - } - receiveValue: { result in - switch result { - case .success(let todo): - guard let todo = todo else { - print("Could not find todo") - return - } - print("Successfully retrieved todo: \(todo)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } - return sink -} -``` - -## List Query - -You can get the list of items using `.list` with optional parameters `limit` and `where` to specify the page size and condition. By default, the page size is 1000. - -#### [Async/Await] - -```swift -func listTodos() async { - let todo = Todo.keys - let predicate = todo.name == "my first todo" && todo.description == "todo description" - let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) - do { - let result = try await Amplify.API.query(request: request) - switch result { - case .success(let todos): - print("Successfully retrieved list of todos: \(todos)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } catch let error as APIError { - print("Failed to query list of todos: ", error) - } catch { - print("Unexpected error: \(error)") - } -} -``` - -#### [Combine] - -```swift -func listTodos() -> AnyCancellable { - let todo = Todo.keys - let predicate = todo.name == "my first todo" && todo.description == "todo description" - let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) - let sink = Amplify.Publisher.create { - try await Amplify.API.query(request: request) - } - .sink { - if case let .failure(error) = $0 { - print("Got failed event with error \(error)") - } - } - receiveValue: { result in - switch result { - case .success(let todos): - print("Successfully retrieved list of todos: \(todos)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } - return sink -} -``` - -### List subsequent pages of items - -If you are using SwiftUI and have SwiftUI imported in the same code file, you will need to import the class `Amplify.List` to resolve name collision with `SwiftUI.List`: - -```swift -import SwiftUI -import Amplify -import class Amplify.List -``` - -For large data sets, you'll need to paginate through the results. After receiving the first page of results, you can check if there is a subsequent page and obtain the next page. - -```swift -var todos: [Todo] = [] -var currentPage: List? - -func listFirstPage() async { - let todo = Todo.keys - let predicate = todo.name == "my first todo" && todo.description == "todo description" - let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) - do { - let result = try await Amplify.API.query(request: request) - switch result { - case .success(let todos): - print("Successfully retrieved list of todos: \(todos)") - self.currentPage = todos - self.todos.append(contentsOf: todos) - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } catch let error as APIError { - print("Failed to query list of todos: ", error) - } catch { - print("Unexpected error: \(error)") - } -} - -func listNextPage() async { - if let current = self.currentPage, current.hasNextPage() { - do { - let todos = try await current.getNextPage() - self.todos.append(contentsOf: todos) - self.currentPage = todos - } catch { - print("Failed to get next page \(error)") - } - } -} -``` - -## List all pages - -If you want to get all pages, retrieve the subsequent page when you have successfully retrieved the first or next page. - -1. Update the above method `listFirstPage()` to `listAllPages()` -2. Call `listNextPageRecursively()` in the success block of the query in `listAllPages()` -2. Update the `listNextPage()` to `listNextPageRecursively()` -3. Call `listNextPageRecursively()` in the success block of the query in `listNextPageRecursively()` - -The completed changes should look like this: - -```swift -var todos: [Todo] = [] -var currentPage: List? - -func listAllPages() async { // 1. Updated from `listFirstPage()` - let todo = Todo.keys - let predicate = todo.name == "my first todo" && todo.description == "todo description" - let request = GraphQLRequest.list(Todo.self, where: predicate, limit: 1000) - do { - let result = try await Amplify.API.query(request: request) - switch result { - case .success(let todos): - print("Successfully retrieved list of todos: \(todos)") - self.currentPage = todos - self.todos.append(contentsOf: todos) - await self.listNextPageRecursively() // 2. Added - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } catch let error as APIError { - print("Failed to query list of todos: ", error) - } catch { - print("Unexpected error: \(error)") - } -} - -func listNextPageRecursively() async { // 3. Updated from `listNextPage()` - if let current = currentPage, current.hasNextPage() { - do { - let todos = try await current.getNextPage() - self.todos.append(contentsOf: todos) - self.currentPage = todos - await self.listNextPageRecursively() // 4. Added - } catch { - print("Failed to get next page \(error)") - } - } -} -``` - - - -## Query item - -Now that you were able to make a mutation, take the `Id` that was printed out and use it in your query to retrieve data. - -#### [Java] - -```java -private void getTodo(String id) { - Amplify.API.query( - ModelQuery.get(Todo.class, id), - response -> Log.i("MyAmplifyApp", ((Todo) response.getData()).getName()), - error -> Log.e("MyAmplifyApp", error.toString(), error) - ); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -private fun getTodo(id: String) { - Amplify.API.query(ModelQuery.get(Todo::class.java, id), - { Log.i("MyAmplifyApp", "Query results = ${(it.data as Todo).name}") }, - { Log.e("MyAmplifyApp", "Query failed", it) } - ); -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -suspend fun getTodo(id: String) { - try { - val response = Amplify.API.query(ModelQuery.get(Todo::class.java, id)) - Log.i("MyAmplifyApp", response.data.name) - } catch (error: ApiException) { - Log.e("MyAmplifyApp", "Query failed", error) - } -} -``` - -#### [RxJava] - -```java -private void getTodo(String id) { - RxAmplify.API.query(ModelQuery.get(Todo.class, id)) - .subscribe( - response -> Log.i("MyAmplifyApp", ((Todo) response.getData()).getName()), - error -> Log.e("MyAmplifyApp", error.toString(), error) - ); -} -``` - -## List items - -You can get the list of items that match a condition that you specify in `Amplify.API.query`: - -#### [Java] - -```java -Amplify.API.query( - ModelQuery.list(Todo.class, Todo.NAME.contains("first")), - response -> { - for (Todo todo : response.getData()) { - Log.i("MyAmplifyApp", todo.getName()); - } - }, - error -> Log.e("MyAmplifyApp", "Query failure", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.API.query( - ModelQuery.list(Todo::class.java, Todo.NAME.contains("first")), - { response -> - response.data.forEach { todo -> - Log.i("MyAmplifyApp", todo.name) - } - }, - { Log.e("MyAmplifyApp", "Query failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - Amplify.API - .query(ModelQuery.list(Todo::class.java, Todo.NAME.contains("first"))) - .response.data - .items.forEach { todo -> Log.i("MyAmplifyApp", todo.name) } -} catch (error: ApiException) { - Log.e("MyAmplifyApp", "Query failure", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.API.query(ModelQuery.list(Todo.class, Todo.NAME.contains("first")) - .subscribe( - response -> { - for (Todo todo : response.getData()) { - Log.i("MyAmplifyApp", todo.getName()); - } - }, - error -> Log.e("MyAmplifyApp", "Query failure", error) - )); -``` - -> **Note**: This approach will only return up to the first 1,000 items. To change this limit or make requests for additional results beyond this limit, use *pagination* as discussed below. - -## List subsequent pages of items - -A list query only returns the first 1,000 items by default, so for large data sets, you'll need to paginate through the results. After receiving a page of results, you can obtain a `GraphQLRequest` for requesting the next page, if there are more results available. The page size is configurable as well, as in the example below. - -#### [Java] - -```java -public void queryFirstPage() { - query(ModelQuery.list(Todo.class, ModelPagination.limit(1_000))); -} - -private static void query(GraphQLRequest> request) { - Amplify.API.query( - request, - response -> { - if (response.hasData()) { - for (Todo todo : response.getData()) { - Log.d("MyAmplifyApp", todo.getName()); - } - if (response.getData().hasNextResult()) { - query(response.getData().getRequestForNextResult()); - } - } - }, - failure -> Log.e("MyAmplifyApp", "Query failed.", failure) - ); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -fun queryFirstPage() { - query(ModelQuery.list(Todo::class.java, ModelPagination.limit(1_000))) -} - -fun query(request: GraphQLRequest>) { - Amplify.API.query(request, - { response -> - if (response.hasData()) { - response.data.items.forEach { todo -> - Log.d("MyAmplifyApp", todo.name) - } - if (response.data.hasNextResult()) { - query(response.data.requestForNextResult) - } - } - }, - { Log.e("MyAmplifyApp", "Query failed", it) } - ) -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -suspend fun queryFirstPage() { - query(ModelQuery.list(Todo::class.java, - ModelPagination.firstPage().withLimit(1_000))) -} - -suspend fun query(request: GraphQLRequest>) { - try { - val response = Amplify.API.query(request) - response.data.items.forEach { todo -> - Log.d("MyAmplifyApp", todo.name) - } - if (response.data.hasNextResult()) { - query(response.data.requestForNextResult) - } - } catch (error: ApiException) { - Log.e("MyAmplifyApp", "Query failed.", error) - } -} -``` - -#### [RxJava] - -```java -BehaviorSubject>> subject = - BehaviorSubject.createDefault(ModelQuery.list(Todo.class, ModelPagination.limit(1_000))); -subject.concatMap(request -> RxAmplify.API.query(request).toObservable()) - .doOnNext(response -> { - if (response.hasErrors()) { - subject.onError(new Exception(String.format("Query failed: %s", response.getErrors()))); - } else if (!response.hasData()) { - subject.onError(new Exception("Empty response from AppSync.")); - } else if(response.getData().hasNextResult()) { - subject.onNext(response.getData().getRequestForNextResult()); - } else { - subject.onComplete(); - } - }) - .concatMapIterable(GraphQLResponse::getData) - .subscribe( - todo -> Log.d(TAG, "Todo: " + todo), - error -> Log.e(TAG, "Error: " + error) - ); -``` - - - - -## Query item - -Now that you were able to make a mutation, take the `id` from the created `Todo` instance and use it to retrieve data. - -```dart -Future queryItem(Todo queriedTodo) async { - try { - final request = ModelQueries.get( - Todo.classType, - queriedTodo.modelIdentifier, - ); - final response = await Amplify.API.query(request: request).response; - final todo = response.data; - if (todo == null) { - safePrint('errors: ${response.errors}'); - } - return todo; - } on ApiException catch (e) { - safePrint('Query failed: $e'); - return null; - } -} -``` - -## List items - -You can get the list of items in `Amplify.API.query`: - -```dart -Future> queryListItems() async { - try { - final request = ModelQueries.list(Todo.classType); - final response = await Amplify.API.query(request: request).response; - - final todos = response.data?.items; - if (todos == null) { - safePrint('errors: ${response.errors}'); - return const []; - } - return todos; - } on ApiException catch (e) { - safePrint('Query failed: $e'); - return const []; - } -} -``` - -### List subsequent pages of items - -For large data sets, you'll need to paginate through the results. After receiving the first page of results, you can check if there is a subsequent page and obtain the next page. - -```dart -const limit = 100; - -Future> queryPaginatedListItems() async { - final firstRequest = ModelQueries.list(Todo.classType, limit: limit); - final firstResult = await Amplify.API.query(request: firstRequest).response; - final firstPageData = firstResult.data; - - // Indicates there are > 100 todos and you can get the request for the next set. - if (firstPageData?.hasNextResult ?? false) { - final secondRequest = firstPageData!.requestForNextResult; - final secondResult = - await Amplify.API.query(request: secondRequest!).response; - return secondResult.data?.items ?? []; - } else { - return firstPageData?.items ?? []; - } -} -``` - -## Query Predicates - -Models also support the use of query predicates for comparison. These are accessible from the Model's attributes, for example `Blog["attribute"]["operator"]`. - -Supported operators: -- `eq` - equal -- `ne` - not equal -- `gt` - greater than -- `ge` - greater than or equal -- `lt` - less than -- `le` - less than or equal -- `beginsWith` - Matches models where the given field begins with the provided value. -- `between` - Matches models where the given field is between the provided start and end values. -- `contains` - Matches models where the given field contains the provided value. - -### Basic Equality Operator - -Query for equality on a model's attribute. - -```dart -const blogTitle = 'Test Blog 1'; -final queryPredicate = Blog.NAME.eq(blogTitle); - -final request = ModelQueries.list( - Blog.classType, - where: queryPredicate, -); -final response = await Amplify.API.query(request: request).response; -final blogFromResponse = response.data?.items.first; -``` - -### Fetch by Parent ID - -Get all Posts by parent ID - -```dart -final blogId = blog.id; - -final request = ModelQueries.list( - Post.classType, - where: Post.BLOG.eq(blogId), -); -final response = await Amplify.API.query(request: request).response; -final data = response.data?.items ?? []; -``` - -### Less than - -Return Posts with a rating less than 5. - -```dart -const rating = 5; - -final request = ModelQueries.list( - Post.classType, - where: Post.RATING.lt(rating), -); -final response = await Amplify.API.query(request: request).response; - -final data = response.data?.items ?? []; -``` - - ---- - ---- -title: "Subscribe to real-time events" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-11-13T19:00:27.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/subscribe-data/" ---- - - -In this guide, we will outline the benefits of enabling real-time data integrations and how to set up and filter these subscriptions. We will also cover how to unsubscribe from subscriptions. - -Before you begin, you will need: - -- An [application connected to the API](/[platform]/build-a-backend/data/connect-to-API/) -- Data already created to modify - - - -## Set up a real-time list query - -The recommended way to fetch a list of data is to use `observeQuery` to get a real-time list of your app data at all times. You can integrate `observeQuery` with React's `useState` and `useEffect` hooks in the following way: - -```ts -import { useState, useEffect } from 'react'; -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; - -type Todo = Schema['Todo']['type']; - -const client = generateClient(); - -export default function MyComponent() { - const [todos, setTodos] = useState([]); - - useEffect(() => { - const sub = client.models.Todo.observeQuery().subscribe({ - next: ({ items, isSynced }) => { - setTodos([...items]); - }, - }); - return () => sub.unsubscribe(); - }, []); - - return ( -
      - {todos.map((todo) => ( -
    • {todo.content}
    • - ))} -
    - ); -} -``` - -`observeQuery` fetches and paginates through all of your available data in the cloud. While data is syncing from the cloud, snapshots will contain all of the items synced so far and an `isSynced` status of `false`. When the sync process is complete, a snapshot will be emitted with all the records in the local store and an `isSynced` status of `true`. - - - -If you don't see all of the real-time events and model fields you expect to see, here are a few things to look for. - -#### Authorization - -The model's [authorization rules](/[platform]/build-a-backend/data/customize-authz/) must grant the appropriate rights to the user. - -| Operation | Authorization | -| -- | -- | -| `onCreate` | `read` OR `listen` | -| `onUpdate` | `read` OR `listen` | -| `onDelete` | `read` OR `listen` | -| `observeQuery` | `read` OR (`listen` AND `list`) | - -If the authorization rules are correct, also ensure the session is authenticated as expected. - -#### Selection Set Parity - -All of the fields you expect to see in a real-time update must be present in the selection set of the **mutation** that triggers it. A mutation essentially "provides" the fields via its selection set that the corresponding subscription can then select from. - -One way to address this is to use a common selection set variable for both operations. For example: - -```ts -// Defining your selection set `as const` ensures the types -// propagate through to the response objects. -const selectionSet = ['title', 'author', 'posts.*'] as const; - -const sub = client.models.Blog.observeQuery( - filter: { id: { eq: 'blog-id' } }, - selectionSet: [...selectionSet] -).subscribe({ - next(data) { - handle(data.items) - } -}); - -// The update uses the same selection set, ensuring all the -// required fields are provided to the subscriber. -const { data } = await client.models.Blog.update({ - id: 'blog-id', - name: 'Updated Name' -}, { - selectionSet: [...selectionSet] -}); -``` - -This works well if all subscriptions to `Blog` require the same subset of fields. If multiple subscriptions are involved with various selection sets, you must ensure that all `Blog` mutations contain the superset of fields from all subscriptions. - -Alternatively, you can skip the custom selection sets entirely. The internally generated selection set for any given model is identical across operations by default. The trade-off is that the default selection sets exclude related models. So, when related models are required, you would need to either lazy load them or construct a query to fetch them separately. - -#### Related Model Mutations - -Mutations do not trigger real-time updates for *related* models. This is true even when the subscription includes a related model in the selection set. For example, if we're subscribed to a particular `Blog` and wish to see updates when a `Post` is added or changed, it's tempting to create a subscribe on `Blog` and assume it "just works": - -```ts -// Notice how we're fetching a few `Blog` details, but mostly using -// the selection set to grab all the related posts. -const selectionSet = ['title', 'author', 'posts.*'] as const; - -const sub = client.models.Blog.observeQuery( - filter: { id: { eq: 'blog-id' } }, - selectionSet: [...selectionSet] -).subscribe({ - next(data) { - handle(data.items) - } -}); -``` - -But, mutations on `Post` records won't trigger an real-time event for the related `Blog`. If you need `Blog` updates when a `Post` is added, you must manually "touch" the relevant `Blog` record. - -```ts -async function addPostToBlog( - post: Schema['Post']['createType'], - blog: Schema['Blog']['type'] -) { - // Create the post first. - await client.models.Post.create({ - ...post, - blogId: blog.id - }); - - // "Touch" the blog, notifying subscribers to re-render. - await client.models.Blog.update({ - id: blog.id - }, { - // Remember to include the selection set if the subscription - // is looking for related-model fields! - selectionSet: [...selectionSet] - }); -} -``` - - - -## Set up a real-time event subscription - -Subscriptions is a feature that allows the server to send data to its clients when a specific event happens. For example, you can subscribe to an event when a new record is created, updated, or deleted through the API. Subscriptions are automatically available for any `a.model()` in your Amplify Data schema. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; - -const client = generateClient(); - -// Subscribe to creation of Todo -const createSub = client.models.Todo.onCreate().subscribe({ - next: (data) => console.log(data), - error: (error) => console.warn(error), -}); - -// Subscribe to update of Todo -const updateSub = client.models.Todo.onUpdate().subscribe({ - next: (data) => console.log(data), - error: (error) => console.warn(error), -}); - -// Subscribe to deletion of Todo -const deleteSub = client.models.Todo.onDelete().subscribe({ - next: (data) => console.log(data), - error: (error) => console.warn(error), -}); - -// Stop receiving data updates from the subscription -createSub.unsubscribe(); -updateSub.unsubscribe(); -deleteSub.unsubscribe(); -``` - -## Set up server-side subscription filters - -Subscriptions take an optional `filter` argument to define service-side subscription filters: - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; - -const client = generateClient(); - -const sub = client.models.Todo.onCreate({ - filter: { - content: { - contains: 'groceries', - }, - }, -}).subscribe({ - next: (data) => console.log(data), - error: (error) => console.warn(error), -}); -``` - -If you want to get all subscription events, don't specify any `filter` parameters. - - - -**Limitations:** - -- Specifying an empty object `{}` as a filter is **not** recommended. Using `{}` as a filter might cause inconsistent behavior based on your data model's authorization rules. -- If you're using dynamic group authorization and you authorize based on a single group per record, subscriptions are only supported if the user is part of five or fewer user groups. -- Additionally, if you authorize by using an array of groups (`groups: [String]`), - - subscriptions are only supported if the user is part of 20 or fewer groups - - you can only authorize 20 or fewer user groups per record - - - -### Subscription connection status updates - -Now that your application is set up and using subscriptions, you may want to know when the subscription is finally established, or reflect to your users when the subscription isn't healthy. You can monitor the connection state for changes through the `Hub` local eventing system. - -```ts -import { CONNECTION_STATE_CHANGE, ConnectionState } from 'aws-amplify/data'; -import { Hub } from 'aws-amplify/utils'; - -Hub.listen('api', (data: any) => { - const { payload } = data; - if (payload.event === CONNECTION_STATE_CHANGE) { - const connectionState = payload.data.connectionState as ConnectionState; - console.log(connectionState); - } -}); -``` - -#### Subscription connection states - -- **`Connected`** - Connected and working with no issues. -- **`ConnectedPendingDisconnect`** - The connection has no active subscriptions and is disconnecting. -- **`ConnectedPendingKeepAlive`** - The connection is open, but has missed expected keep-alive messages. -- **`ConnectedPendingNetwork`** - The connection is open, but the network connection has been disrupted. When the network recovers, the connection will continue serving traffic. -- **`Connecting`** - Attempting to connect. -- **`ConnectionDisrupted`** - The connection is disrupted and the network is available. -- **`ConnectionDisruptedPendingNetwork`** - The connection is disrupted and the network connection is unavailable. -- **`Disconnected`** - Connection has no active subscriptions and is disconnecting. - - - -Connections between your application and backend subscriptions can be interrupted for various reasons, including network outages or the device entering sleep mode. Your subscriptions will automatically reconnect when it becomes possible to do so. - -While offline, your application will miss messages and will not automatically catch up when reconnected. Depending on your use case, you may want to take action for your app to catch up when it comes back online. - -```js -import { generateClient, CONNECTION_STATE_CHANGE, ConnectionState } from 'aws-amplify/data' -import { Hub } from 'aws-amplify/utils' -import { Schema } from '../amplify/data/resource'; - -const client = generateClient() - -const fetchRecentData = () => { - const { data: allTodos } = await client.models.Todo.list(); -} - -let priorConnectionState: ConnectionState; - -Hub.listen("api", (data: any) => { - const { payload } = data; - if ( - payload.event === CONNECTION_STATE_CHANGE - ) { - - if (priorConnectionState === ConnectionState.Connecting && payload.data.connectionState === ConnectionState.Connected) { - fetchRecentData(); - } - priorConnectionState = payload.data.connectionState; - } -}); - -const createSub = client.models.Todo.onCreate().subscribe({ - next: payload => // Process incoming messages -}); - -const updateSub = client.models.Todo.onUpdate().subscribe({ - next: payload => // Process incoming messages -}); - -const deleteSub = client.models.Todo.onDelete().subscribe({ - next: payload => // Process incoming messages -}); - -const cleanupSubscriptions = () => { - createSub.unsubscribe(); - updateSub.unsubscribe(); - deleteSub.unsubscribe(); -} -``` - - - -## Unsubscribe from a subscription - -You can also unsubscribe from events by using subscriptions by implementing the following: - -```ts -// Stop receiving data updates from the subscription -sub.unsubscribe(); -``` - -## Conclusion - -Congratulations! You have finished the **Subscribe to real-time events** guide. In this guide, you set up subscriptions for real-time events and learned how to filter and cancel these subscriptions when needed. - -### Next steps - -Our recommended next steps include continuing to build out and customize your information architecture for your data. Some resources that will help with this work include: - -- [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz/) -- [Customize your data model](/[platform]/build-a-backend/data/data-modeling/) -- [Add custom business logic](/[platform]/build-a-backend/data/custom-business-logic/) - - - -Subscribe to mutations for creating real-time clients. - -Because the lifetime of the subscription will last longer than the lifetime of a single function, you can create an instance variable at the top of your class: - -#### [Async/Await] - -```swift -var subscription: AmplifyAsyncThrowingSequence> -``` - -#### [Combine] - -```swift -var subscription: AnyCancellable? -``` - -To listen to creation updates, you can use the following code sample: - -#### [Async/Await] - -```swift -func createSubscription() { - subscription = Amplify.API.subscribe(request: .subscription(of: Todo.self, type: .onCreate)) - Task { - do { - for try await subscriptionEvent in subscription { - switch subscriptionEvent { - case .connection(let subscriptionConnectionState): - print("Subscription connect state is \(subscriptionConnectionState)") - case .data(let result): - switch result { - case .success(let createdTodo): - print("Successfully got todo from subscription: \(createdTodo)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } - } - } catch { - print("Subscription has terminated with \(error)") - } - } -} -``` - -#### [Combine] - -```swift -func createSubscription() { - let sequence = Amplify.API.subscribe(request: .subscription(of: Todo.self, type: .onCreate)) - subscription = Amplify.Publisher.create(sequence) - .sink { - if case let .failure(apiError) = $0 { - print("Subscription has terminated with \(apiError)") - } else { - print("Subscription has been closed successfully") - } - } - receiveValue: { result in - switch result { - case .connection(let subscriptionConnectionState): - print("Subscription connect state is \(subscriptionConnectionState)") - case .data(let result): - switch result { - case .success(let createdTodo): - print("Successfully got todo from subscription: \(createdTodo)") - case .failure(let error): - print("Got failed result with \(error.errorDescription)") - } - } - } -} -``` - -## Unsubscribing from updates - -### Async/Await - -To unsubscribe from updates, you can call `cancel()` on the subscription. - -```swift -func cancelSubscription() { - // Cancel the subscription listener when you're finished with it - subscription?.cancel() -} -``` - -### Combine - -Calling `cancel()` on the sequence will disconnect the subscription from the backend. Any downstream subscribers will also be cancelled. - -```swift -let sequence = Amplify.API.subscribe(...) -let subscription = Amplify.Publisher.create(sequence) -let allUpdates = subscription.sink(...) -let filteredUpdates = subscription.filter{...}.sink(...) -sequence.cancel() // sequence is now disconnected - // allUpdates and filteredUpdates will no longer receive data -``` - -Similarly, calling `cancel()` on the Combine subscriber (e.g., the `AnyCancellable` returned from `sink()`) will cause the underlying sequence to cancel. This will cause all attached subscribers to stop receiving updates. - -```swift -allUpdates.cancel() // sequence is disconnected - // filteredUpdates will no longer receive data -``` - - - -Subscribe to mutations for creating real-time clients: - -#### [Java] - -```java -ApiOperation subscription = Amplify.API.subscribe( - ModelSubscription.onCreate(Todo.class), - onEstablished -> Log.i("ApiQuickStart", "Subscription established"), - onCreated -> Log.i("ApiQuickStart", "Todo create subscription received: " + ((Todo) onCreated.getData()).getName()), - onFailure -> Log.e("ApiQuickStart", "Subscription failed", onFailure), - () -> Log.i("ApiQuickStart", "Subscription completed") -); - -// Cancel the subscription listener when you're finished with it -subscription.cancel(); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val subscription = Amplify.API.subscribe( - ModelSubscription.onCreate(Todo::class.java), - { Log.i("ApiQuickStart", "Subscription established") }, - { Log.i("ApiQuickStart", "Todo create subscription received: ${(it.data as Todo).name}") }, - { Log.e("ApiQuickStart", "Subscription failed", it) }, - { Log.i("ApiQuickStart", "Subscription completed") } -) - -// Cancel the subscription listener when you're finished with it -subscription.cancel(); -``` - -#### [Kotlin - Coroutines] - -```kotlin -val job = activityScope.launch { - try { - Amplify.API.subscribe(ModelSubscription.onCreate(Todo::class.java)) - .catch { Log.e("ApiQuickStart", "Error on subscription", it) } - .collect { Log.i("ApiQuickStart", "Todo created! ${it.data.name}") } - } catch (notEstablished: ApiException) { - Log.e("ApiQuickStart", "Subscription not established", it) - } -} - -// When done with subscription -job.cancel() -``` - -#### [RxJava] - -```java -RxSubscriptionOperation> subscription = - RxAmplify.API.subscribe(request); - -subscription - .observeConnectionState() - .subscribe(connectionStateEvent -> Log.i("ApiQuickStart", String.valueOf(connectionStateEvent))); - -subscription - .observeSubscriptionData() - .subscribe( - data -> Log.i("ApiQuickStart", data), - exception -> Log.e("ApiQuickStart", "Subscription failed.", exception), - () -> Log.i("ApiQuickStart", "Subscription completed.") - ); - -// Cancel the subscription listener when you're finished with it -subscription.cancel(); -``` - - - - -Subscribe to mutations for creating real-time clients. - -## Setup subscription with callbacks - -When creating subscriptions, a [`Stream`](https://api.dart.dev/dart-async/Stream-class.html) object will be returned to you. This `Stream` will continue producing events until either the subscription encounters an error or you cancel the subscription. In the case of need for limiting the amount of data that is omitted, you can take advantage of the Stream's helper functions such as `take`. The cancellation occurs when the defined amount of event has occurred: - -```dart -Stream> subscribe() { - final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); - final Stream> operation = Amplify.API - .subscribe( - subscriptionRequest, - onEstablished: () => safePrint('Subscription established'), - ) - // Listens to only 5 elements - .take(5) - .handleError( - (Object error) { - safePrint('Error in subscription stream: $error'); - }, - ); - return operation; -} -``` - -Alternatively, you can call [`Stream.listen`](https://api.dart.dev/dart-async/Stream/listen.html) to create a [`StreamSubscription`](https://api.dart.dev/dart-async/StreamSubscription-class.html) object which can be programmatically canceled. - -```dart -// Be sure to import this -import 'dart:async'; - -... - -StreamSubscription>? subscription; - -void subscribe() { - final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); - final Stream> operation = Amplify.API.subscribe( - subscriptionRequest, - onEstablished: () => safePrint('Subscription established'), - ); - subscription = operation.listen( - (event) { - safePrint('Subscription event data received: ${event.data}'); - }, - onError: (Object e) => safePrint('Error in subscription stream: $e'), - ); -} - -void unsubscribe() { - subscription?.cancel(); - subscription = null; -} -``` - -In addition to an `onCreate` subscription, you can also call `.onUpdate()` or `.onDelete()`. - -```dart -final onUpdateSubscriptionRequest = ModelSubscriptions.onUpdate(Todo.classType); -// or -final onDeleteSubscriptionRequest = ModelSubscriptions.onDelete(Todo.classType); -``` - -## Subscription connection status - -Now that you set up the application and are using subscriptions, you may want to know when the subscription is closed, or reflect to your users when the subscription isn’t healthy. You can monitor the subscription status for changes via `Amplify.Hub` - -```dart -Amplify.Hub.listen( - HubChannel.Api, - (ApiHubEvent event) { - if (event is SubscriptionHubEvent) { - safePrint(event.status); - } - }, -); -``` - -### SubscriptionStatus - -- **`connected`** - Connected and working with no issues -- **`connecting`** - Attempting to connect (both initial connection and reconnection) -- **`pendingDisconnect`** - Connection has no active subscriptions and is shutting down -- **`disconnected`** - Connection has no active subscriptions and is disconnected -- **`failed`** - Connection had a failure and has been disconnected - -## Automated Reconnection - -Under the hood, we will attempt to maintain a healthy web socket connection through network changes. For example, if a device’s connection changes from Wi-Fi to 5g network, the plugin will attempt to reconnect using the new network. - -Likewise, when disconnected from the internet unexpectedly, the subscription will attempt to reconnect using an exponential retry/back off strategy. By default, we will make 8 recovery attempts over about 50 seconds. If we cannot make a successful connection, then the web socket will be closed. You can customize this strategy when configuring the API plugin through `RetryOptions`. - -```dart -Future _configureAmplify() async { - final apiPlugin = AmplifyAPI( - options: APIPluginOptions( - modelProvider: ModelProvider.instance, - // Optional config - subscriptionOptions: const GraphQLSubscriptionOptions( - retryOptions: RetryOptions(maxAttempts: 10), - ), - ) - ); - await Amplify.addPlugin(apiPlugin); - - try { - await Amplify.configure(outputs); - } on AmplifyAlreadyConfiguredException { - safePrint( - "Tried to reconfigure Amplify; this can occur when your app restarts on Android."); - } -} -``` - - - -**Important**: While offline, your application will miss messages and will not automatically catch up when reconnection happens. Depending on your use case, you may want to take action to catch up when your app comes back online. The following example solves this problem by retrieving all data on reconnection. - - - -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_api/amplify_api.dart'; -import './models/ModelProvider.dart'; // <--- Update import to reflect your project -import 'dart:async'; - -// ... - -List allTodos = []; -SubscriptionStatus prevSubscriptionStatus = SubscriptionStatus.disconnected; -StreamSubscription>? subscription; - -/// ... - -// Init listeners -Amplify.Hub.listen( - HubChannel.Api, - (ApiHubEvent event) { - if (event is SubscriptionHubEvent) { - if (prevSubscriptionStatus == SubscriptionStatus.connecting && - event.status == SubscriptionStatus.connected) { - getTodos(); // refetch todos - } - prevSubscriptionStatus = event.status; - } - }, -); - -subscribe(); - -/// ... - -Future getTodos() async { - try { - final request = ModelQueries.list(Todo.classType); - final response = await Amplify.API.query(request: request).response; - - final todos = response.data?.items ?? []; - if (response.errors.isNotEmpty) { - safePrint('errors: ${response.errors}'); - } - - setState(() { - allTodos = todos; - }); - } on ApiException catch (e) { - safePrint('Query failed: $e'); - return; - } -} - -void subscribe() { - final subscriptionRequest = ModelSubscriptions.onCreate(Todo.classType); - final Stream> operation = Amplify.API.subscribe( - subscriptionRequest, - onEstablished: () => safePrint('Subscription established'), - ); - subscription = operation.listen( - (event) { - setState(() { - allTodos.add(event.data); - }); - }, - onError: (Object e) => safePrint('Error in subscription stream: $e'), - ); -} - -``` - - ---- - ---- -title: "Customize your data model" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-07-12T17:02:33.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/" ---- - -## Data modeling capabilities - -Every data model is defined as part of a data schema (`a.schema()`). You can enhance your data model with various fields, customize their identifiers, apply authorization rules, or model relationships. Every data model (`a.model()`) automatically provides create, read, update, and delete API operations as well as real-time subscription events. Below is a quick tour of the many functionalities you can add to your data model: - -```ts -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a - .schema({ - Customer: a - .model({ - customerId: a.id().required(), - // fields can be of various scalar types, - // such as string, boolean, float, integers etc. - name: a.string(), - // fields can be of custom types - location: a.customType({ - // fields can be required or optional - lat: a.float().required(), - long: a.float().required(), - }), - // fields can be enums - engagementStage: a.enum(["PROSPECT", "INTERESTED", "PURCHASED"]), - collectionId: a.id(), - collection: a.belongsTo("Collection", "collectionId") - // Use custom identifiers. By default, it uses an `id: a.id()` field - }) - .identifier(["customerId"]), - Collection: a - .model({ - customers: a.hasMany("Customer", "collectionId"), // setup relationships between types - tags: a.string().array(), // fields can be arrays - representativeId: a.id().required(), - // customize secondary indexes to optimize your query performance - }) - .secondaryIndexes((index) => [index("representativeId")]), - }) - .authorization((allow) => [allow.publicApiKey()]); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "apiKey", - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -## Gen 1 schema support - -If you are coming from Gen 1, you can continue to use the GraphQL Schema Definition Language (SDL) for defining your schema. However, we strongly recommend you use the TypeScript-first schema builder experience in your project as it provides type safety and is the recommended way of working with Amplify going forward. - - - -**Note:** Some features available in Gen 1 GraphQL SDL are not available in Gen 2. See the [feature matrix](/[platform]/start/migrate-to-gen2/#gen-1-vs-gen-2-feature-matrix) for features supported in Gen 2. - - - -```ts title="amplify/data/resource.ts" -import { defineData } from '@aws-amplify/backend'; - -const schema = /* GraphQL */` - type Todo @model @auth(rules: [{ allow: owner }]) { - content: String - isDone: Boolean - } -`; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "apiKey", - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - ---- - ---- -title: "Add fields to data model" -section: "build-a-backend/data/data-modeling" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-04-07T19:23:58.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/add-fields/" ---- - -Amplify Data supports all AWS AppSync scalar types as field types. The following scalar types are available: - -|Field type|Description|TypeScript validation|GraphQL Scalar Type| -|-|-|-|-| -|`a.id()`|A unique identifier for an object. This scalar is serialized like a String but isn't meant to be human-readable. If not specified on create operations, a UUID will be generated.|`string`|ID| -|`a.string()`|A UTF-8 character sequence.|`string`|String| -|`a.integer()`|An integer value between -(2^31) and 2^31-1.|`number` but rounded to closest integer value upon query/mutation|Int| -|`a.float()`|An IEEE 754 floating point value.|`number`|Float| -|`a.boolean()`|A Boolean value, either true or false.|`boolean`|Boolean| -|`a.date()`|An extended ISO 8601 date string in the format `YYYY-MM-DD`.|`string`|AWSDate| -|`a.time()`|An extended ISO 8601 time string in the format `hh:mm:ss.sss`.|`string`|AWSTime| -|`a.datetime()`|An extended ISO 8601 date and time string in the format `YYYY-MM-DDThh:mm:ss.sssZ`.|`string`|AWSDateTime| -|`a.timestamp()`|An integer value representing the number of seconds before or after 1970-01-01-T00:00Z.|`number`|AWSTimestamp| -|`a.email()`|An email address in the format local-part@domain-part as defined by RFC 822.|`string` with local-part and domain-part type enforcement|AWSEmail| -|`a.json()`|A JSON string. Any valid JSON construct is automatically parsed and loaded in the resolver code as maps, lists, or scalar values, rather than as the literal input strings. Unquoted strings or otherwise invalid JSON result in a validation error.|`any`|AWSJSON| -|`a.phone()`|A phone number. This value is stored as a string. Phone numbers can contain either spaces or hyphens to separate digit groups. Phone numbers without a country code are assumed to be US/North American numbers adhering to the North American Numbering Plan.|`string` validation only happening service-side|AWSPhone| -|`a.url()`|A URL as defined by RFC 1738. For example, https://www.amazon.com/dp/B000NZW3KC/ or mailto:example@example.com. URLs must contain a schema (http, mailto) and can't contain two forward slashes (//) in the path part.|`string` but with type enforcement on the schema part|AWSURL| -|`a.ipAddress()`|A valid IPv4 or IPv6 address. IPv4 addresses are expected in quad-dotted notation (123.12.34.56). IPv6 addresses are expected in non-bracketed, colon-separated format (1a2b:3c4b:1234:4567). You can include an optional CIDR suffix (123.45.67.89/16) to indicate subnet mask.|`string` with type enforcement for IPv4 and IPv6 pattern|AWSIPAddress| - -## Specify a custom field type - -Sometimes, the built-in types do not meet the needs of your application. In those cases, you can specify custom types. You can either define the custom types inline or explicitly define the custom type in the schema. - -**Inline definition:** The "location" field will become a new non-model type that uses PascalCase, a naming convention in which the first letter of each word in a compound word is capitalized. If there are conflicts with another schema-level definition (model, custom type, enum), you will receive a Type error with a warning that you need to sift the value out as a separate item and use a "ref". - -```ts -a.schema({ - Post: a.model({ - location: a.customType({ - lat: a.float(), - long: a.float(), - }), - content: a.string(), - }), -}).authorization((allow) => allow.publicApiKey()); -``` - -**Explicit definition:** Specify the "Location" as `a.customType()` in your schema. To use the custom type, reference it through `a.ref()` in the respective field definitions. - -```ts -a.schema({ - Location: a.customType({ - lat: a.float(), - long: a.float(), - }), - - Post: a.model({ - location: a.ref('Location'), - content: a.string(), - }), - - User: a.model({ - lastKnownLocation: a.ref('Location'), - }), -}).authorization((allow) => allow.publicApiKey()); -``` - - -To set or read the location field on the client side, you can expand a nested object and the type system will auto-infer the allowed values. - -```ts -const { data: newPost, errors } = await client.models.Post.create({ - location: { - lat: 48.837006, - long: 8.28245, - }, -}); - -console.log(newPost?.location?.lat, newPost?.location?.long); -``` - - - -To set or read the location field on the client side, you can create a Post object with the location values. - -```swift -let post = Post( - location: .init( - lat: 48.837006, - long: 8.28245)) -let createdPost = try await Amplify.API.mutate(request: .create(post)).get() -print("\(createdPost)") -``` - - -## Specify an enum field type - -Enum has a similar developer experience as custom types: short-hand and long-form approaches. - -Short-hand approach - -```ts -a.schema({ - Post: a.model({ - privacySetting: a.enum(['PRIVATE', 'FRIENDS_ONLY', 'PUBLIC']), - content: a.string(), - }), -}).authorization((allow) => allow.publicApiKey()); -``` - -Long-form approach - -```ts -a.schema({ - PrivacySetting: a.enum([ - 'PRIVATE', - 'FRIENDS_ONLY', - 'PUBLIC' - ]), - - Post: a.model({ - content: a.string(), - privacySetting: a.ref('PrivacySetting'), - }), - - Video: a.model({ - privacySetting: a.ref('PrivacySetting'), - }), -}).authorization((allow) => allow.publicApiKey()); -``` - - -When creating a new item client-side, the enums are also type-enforced: -```ts -client.models.Post.create({ - content: 'hello', - // WORKS - value auto-completed - privacySetting: 'PRIVATE', - - // DOES NOT WORK - TYPE ERROR - privacySetting: 'NOT_PUBLIC', -}); -``` - - - -Creating a new item client-side with the enum value: - -```swift -let post = Post( - content: "hello", - privacySetting: .private) -let createdPost = try await Amplify.API.mutate(request: .create(post)).get() -``` - - - -### List enum values client-side - -You can list available enum values client-side using the `client.enums..values()` API. For example, this allows you to display the available enum values within a dropdown UI. - -```ts -const availableSettings = client.enums.PrivacySetting.values() -// availableSettings returns ["PRIVATE", "FRIENDS_ONLY", "PUBLIC"] -``` - - -## Mark fields as required - -By default, fields are optional. To mark a field as required, use the `.required()` modifier. - -```ts -const schema = a.schema({ - Todo: a.model({ - content: a.string().required(), - }), -}).authorization((allow) => allow.publicApiKey()); -``` - -## Mark fields as arrays - -Any field can be modified to be an array using the `.array()` modifier. - -```ts -const schema = a.schema({ - Todo: a.model({ - content: a.string().required(), - notes: a.string().array(), - }), -}).authorization((allow) => allow.publicApiKey()); -``` - -## Assign default values for fields - -You can use the `.default(...)` modifier to specify a default value for optional [scalar type fields](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html). The `.default(...)` modifier is not available for custom types, arrays, or relationships. - -```ts -const schema = a.schema({ - Todo: a.model({ - content: a.string().default('My new Todo'), - }), -}).authorization((allow) => allow.publicApiKey()); -``` - -**Note:** The `.default(...)` modifier can't be applied to required fields. - - ---- - ---- -title: "Modeling relationships" -section: "build-a-backend/data/data-modeling" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-10T00:17:11.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/relationships/" ---- - -When modeling application data, you often need to establish relationships between different data models. In Amplify Data, you can create one-to-many, one-to-one, and many-to-many relationships in your Data schema. On the client-side, Amplify Data allows you to lazy or eager load of related data. - - - - ## Types of relationships - -|Relationship|Code|Description|Example| -|-|-|-|-| -|one to many|`a.hasMany(...)` & `a.belongsTo(...)`|Creates a one-to-many relationship between two models.|A **Team** has many **Members**. A **Member** belongs to a **Team**.| -|one to one|`a.hasOne(...)` & `a.belongsTo(...)`|Creates a one-to-one relationship between two models.|A **Customer** has one **Cart**. A **Cart** belongs to one **Customer**.| -|many to many| Two `a.hasMany(...)` & `a.belongsTo(...)` on join tables|Create two one-to-many relationships between the related models in a join table.|A **Post** has many **Tags**. A **Tag** has many **Posts**.| - -## Model one-to-many relationships - -Create a one-to-many relationship between two models using the `hasMany()` and `belongsTo()` method. In the example below, a Team has many Members and a Member belongs to exactly one Team. - -1. Create a **reference field** called `teamId` on the **Member** model. This reference field's type **MUST** match the type of **Team**'s identifier. In this case, it's an auto-generated `id: a.id().required()` field. -2. Add a **relationship field** called `team` that references the `teamId` field. This allows you to query for the team information from the **Member** model. -3. Add a **relationship field** called `members` that references the `teamId` field on the **Member** model. - -```typescript -const schema = a.schema({ - Member: a.model({ - name: a.string().required(), - // 1. Create a reference field - teamId: a.id(), - // 2. Create a belongsTo relationship with the reference field - team: a.belongsTo('Team', 'teamId'), - }), - - Team: a.model({ - mantra: a.string().required(), - // 3. Create a hasMany relationship with the reference field - // from the `Member`s model. - members: a.hasMany('Member', 'teamId'), - }), -}).authorization((allow) => allow.publicApiKey()); -``` - -### Create a "Has Many" relationship between records - - -```ts -const { data: team } = await client.models.Team.create({ - mantra: 'Go Frontend!', -}); - -const { data: member } = await client.models.Member.create({ - name: "Tim", - teamId: team.id, -}); -``` - - - -```kt -val team = Team.builder() - .mantra("Go Frontend!") - .build() - -Amplify.API.mutate(ModelMutation.create(team), - { - Log.i("MyAmplifyApp", "Added team with id: ${it.data.id}") - val member = Member.builder() - .name("Tim") - .team(it.data) - .build() - - Amplify.API.mutate(ModelMutation.create(member), - { Log.i("MyAmplifyApp", "Added Member with id: ${it.data.id}")}, - { Log.e("MyAmplifyApp", "Create failed", it)}, - ) - }, { - Log.e("MyAmplifyApp", "Create failed", it) - }) -``` - - - -```swift -do { - let team = Team(mantra: "Go Frontend!") - let createdTeam = try await Amplify.API.mutate(request: .create(team)).get() - - let member = Member( - name: "Tim", - team: createdTeam) // Directly pass in the team instance - let createdMember = try await Amplify.API.mutate(request: .create(member)) -} catch { - print("Create team or member failed", error) -} -``` - - - -```dart -final team = Team(mantra: "Go Frontend!"); -final teamRequest = ModelMutations.create(team); -final teamResponse = await Amplify.API.mutate(request: teamRequest).response; - -final member = Member(name: "Tim", team: teamResponse.data); -final memberRequest = ModelMutations.create(member); -final memberResponse = await Amplify.API.mutate(request: memberRequest).response; -``` - - -### Update a "Has Many" relationship between records - - -```ts -const { data: newTeam } = await client.models.Team.create({ - mantra: 'Go Fullstack', -}); - -await client.models.Member.update({ - id: "MY_MEMBER_ID", - teamId: newTeam.id, -}); -``` - - - -```kt -val newTeam = Team.builder() - .mantra("Go Fullstack!") - .build() - -Amplify.API.mutate(ModelMutation.create(newTeam), - { - Log.i("MyAmplifyApp", "Added team with id: ${it.data.id}") - - val updatingMember = existingMember.copyOfBuilder().team(it.data).build() - - Amplify.API.mutate(ModelMutation.update(updatingMember), - { Log.i("MyAmplifyApp", "Updated Member with id: ${it.data.id}")}, - { Log.e("MyAmplifyApp", "Create failed", it)}, - ) - }, { - Log.e("MyAmplifyApp", "Create failed", it) - }) -``` - - - -```swift -do { - let newTeam = Team(mantra: "Go Fullstack!") - let createdNewTeam = try await Amplify.API.mutate(request: .create(newTeam)).get() - - existingMember.setTeam(createdNewTeam) - let updatedMember = try await Amplify.API.mutate(request: .update(existingMember)).get() -} catch { - print("Create team or update member failed", error) -} -``` - - - -```dart -final newTeam = Team(mantra: "Go Fullstack!"); -final newTeamRequest = ModelMutations.create(team); -final newTeamResponse = await Amplify.API.mutate(request: teamRequest).response; - -final memberWithUpdatedTeam = existingMember.copyWith(team: newTeamResponse.data); -final memberUpdateRequest = ModelMutations.update(memberWithUpdatedTeam); -final memberUpdateResponse = await Amplify.API.mutate(request: memberUpdateRequest).response; -``` - - -### Delete a "Has Many" relationship between records - -If your reference field is not required, then you can "delete" a one-to-many relationship by setting the relationship value to `null`. - - -```ts -await client.models.Member.update({ - id: "MY_MEMBER_ID", - teamId: null, -}); -``` - - - -```kt -val memberWithRemovedTeam = existingMember.copyOfBuilder().team(null).build() - -Amplify.API.mutate(ModelMutation.update(memberWithRemovedTeam), - { Log.i("MyAmplifyApp", "Updated Member with id: ${it.data.id}")}, - { Log.e("MyAmplifyApp", "Create failed", it)}, -) -``` - - - -```swift -do { - existingMember.setTeam(nil) - let memberRemovedTeam = try await Amplify.API.mutate(request: .update(existingMember)).get() -} catch { - print("Failed to remove team from member", error) -} -``` - - - -```dart -final memberWithRemovedTeam = existingMember.copyWith(team: null); -final memberRemoveRequest = ModelMutations.update(memberWithRemovedTeam); -final memberRemoveResponse = await Amplify.API.mutate(request: memberRemoveRequest).response; -``` - - - -### Load related data in a "Has Many" relationship - -```dart -// Fetch the team with the team id. -final teamRequest = ModelQueries.get( - Team.classType, TeamModelIdentifier(id: "YOUR_TEAM_ID")); -final teamResult = await Amplify.API.query(request: teamRequest).response; -final team = teamResult.data!; - -// Define a limit for your pagination -const limit = 100; - -// Do the initial call to get the initial items -final firstRequest = ModelQueries.list(Member.classType, - limit: limit, where: Member.TEAMID.eq(team.id)); -final firstResult = await Amplify.API.query(request: firstRequest).response; -final firstPageData = firstResult.data; - -// If there are more than 100 items you can reiterate the following code to get next pages. -if (firstPageData?.hasNextResult ?? false) { - final secondRequest = firstPageData!.requestForNextResult; - final secondResult = - await Amplify.API.query(request: secondRequest!).response; - return secondResult.data?.items ?? []; -} else { - // You can return the page data by calling items property. - return firstPageData?.items ?? []; -} -``` - - - -### Lazy load a "Has Many" relationship - - -```ts -const { data: team } = await client.models.Team.get({ id: "MY_TEAM_ID"}); - -const { data: members } = await team.members(); - -members.forEach(member => console.log(member.id)); -``` - - - -```kt -Amplify.API.query( - ModelQuery.get(Team::class.java, Team.TeamIdentifier("YOUR_TEAM_ID")), - { - suspend { - try { - val members = - when (val membersModelList = it.data.members) { - is LoadedModelList -> { - // Eager loading loads the 1st page only. - membersModelList.items - } - - is LazyModelList -> { - var page = membersModelList.fetchPage() - var loadedMembers = - mutableListOf(page.items) // initial page of members - // loop through all pages to fetch the full list of members - while (page.hasNextPage) { - val nextToken = page.nextToken - page = - membersModelList.fetchPage(nextToken) - // add the page of members to the members variable - loadedMembers += page.items - } - loadedMembers - } - } - Log.i("MyAmplifyApp", "members: $members") - } catch (error: ApiException) { - Log.e("MyAmplifyApp", "Failed to fetch members", error) - } - } - }, - { Log.e("MyAmplifyApp", "Failed to fetch team")}) -``` - - - -```swift -do { - let queriedTeam = try await Amplify.API.query( - request: .get( - Team.self, - byIdentifier: team.identifier)).get() - - guard let queriedTeam, let members = queriedTeam.members else { - print("Missing team or members") - return - } - try await members.fetch() - print("Number of members: \(members.count)") -} catch { - print("Failed to fetch team or members", error) -} -``` - - -### Eagerly load a "Has Many" relationship - - -```ts -const { data: teamWithMembers } = await client.models.Team.get( - { id: "MY_TEAM_ID" }, - { selectionSet: ["id", "members.*"] }, -); - -teamWithMembers.members.forEach(member => console.log(member.id)); -``` - - - -```kt -Amplify.API.query( - ModelQuery.get( - Team::class.java, - Team.TeamIdentifier("YOUR_TEAM_ID") - ) { teamPath -> includes(teamPath.members) }, - { - val members = (it.data.members as? LoadedModelList)?.items - }, - { Log.e("MyAmplifyApp", "Failed to fetch team")} -) -``` - - - -```swift -do { - let queriedTeamWithMembers = try await Amplify.API.query( - request: .get( - Team.self, - byIdentifier: team.identifier, - includes: { team in [team.members]})) - .get() - guard let queriedTeamWithMembers, let members = queriedTeamWithMembers.members else { - print("Missing team or members") - return - } - print("Number of members: \(members.count)") -} catch { - print("Failed to fetch team with members", error) -} -``` - - - - -### Handling orphaned foreign keys on parent record deletion in "Has Many" relationship - -```ts -// Get the IDs of the related members. -const { data: teamWithMembers } = await client.models.Team.get( - { id: teamId }, - { selectionSet: ["id", "members.*"] }, -); - -// Delete Team -await client.models.Team.delete({ id: teamWithMembers.id }); - -// Delete all members in parallel -await Promise.all( - teamWithMembers.members.map(member => - client.models.Member.delete({ id: member.id }) -)); -``` - - -## Model a "one-to-one" relationship - -Create a one-to-one relationship between two models using the `hasOne()` and `belongsTo()` methods. In the example below, a **Customer** has a **Cart** and a *Cart* belongs to a **Customer**. - -1. Create a **reference field** called `customerId` on the **Cart** model. This reference field's type **MUST** match the type of **Customer**'s identifier. In this case, it's an auto-generated `id: a.id().required()` field. -2. Add a **relationship field** called `customer` that references the `customerId` field. This allows you to query for the customer information from the **Cart** model. -3. Add a **relationship field** called `activeCart` that references the `customerId` field on the **Cart** model. - -```typescript -const schema = a.schema({ - Cart: a.model({ - items: a.string().required().array(), - // 1. Create reference field - customerId: a.id(), - // 2. Create relationship field with the reference field - customer: a.belongsTo('Customer', 'customerId'), - }), - Customer: a.model({ - name: a.string(), - // 3. Create relationship field with the reference field - // from the Cart model - activeCart: a.hasOne('Cart', 'customerId') - }), -}).authorization((allow) => allow.publicApiKey()); -``` - -### Create a "Has One" relationship between records - -To create a "has one" relationship between records, first create the parent item and then create the child item and assign the parent. - - -```ts -const { data: customer, errors } = await client.models.Customer.create({ - name: "Rene", -}); - -const { data: cart } = await client.models.Cart.create({ - items: ["Tomato", "Ice", "Mint"], - customerId: customer?.id, -}); -``` - - - -```kt -val customer = Customer.builder() - .name("Rene") - .build() - -Amplify.API.mutate(ModelMutation.create(customer), - { - Log.i("MyAmplifyApp", "Added customer with id: ${it.data.id}") - val cart = Cart.builder() - .items(listOf("Tomato", "Ice", "Mint")) - .customer(customer) - .build() - - Amplify.API.mutate(ModelMutation.create(cart), - { Log.i("MyAmplifyApp", "Added Cart with id: ${it.data.id}")}, - { Log.e("MyAmplifyApp", "Create failed", it)}, - ) - }, { - Log.e("MyAmplifyApp", "Create failed", it) - }) -``` - - - -```swift -do { - let customer = Customer(name: "Rene") - let createdCustomer = try await Amplify.API.mutate(request: .create(customer)).get() - - let cart = Cart( - items: ["Tomato", "Ice", "Mint"], - customer: createdCustomer) - let createdCart = try await Amplify.API.mutate(request: .create(cart)).get() -} catch { - print("Create customer or cart failed", error) -} -``` - - - -```dart -final customer = Customer(name: "Rene"); -final customerRequest = ModelMutations.create(customer); -final customerResponse = await Amplify.API.mutate(request: customerRequest).response; - -final cart = Cart(items: ["Tomato", "Ice", "Mint"], customer: teamResponse.customer); -final cartRequest = ModelMutations.create(cart); -final cartResponse = await Amplify.API.mutate(request: cartRequest).response; -``` - - -### Update a "Has One" relationship between records - -To update a "Has One" relationship between records, you first retrieve the child item and then update the reference to the parent to another parent. For example, to reassign a Cart to another Customer: - - -```ts -const { data: newCustomer } = await client.models.Customer.create({ - name: 'Ian', -}); - -await client.models.Cart.update({ - id: cart.id, - customerId: newCustomer?.id, -}); -``` - - - -```kt -val newCustomer = Customer.builder() - .mantra("Ian") - .build() - -Amplify.API.mutate(ModelMutation.create(newCustomer), - { - Log.i("MyAmplifyApp", "Added customer with id: ${it.data.id}") - - val updatingCart = existingCart.copyOfBuilder().customer(it.data).build() - - Amplify.API.mutate(ModelMutation.update(updatingCart), - { Log.i("MyAmplifyApp", "Updated cart with id: ${it.data.id}")}, - { Log.e("MyAmplifyApp", "Create failed", it)}, - ) - }, { - Log.e("MyAmplifyApp", "Create failed", it) - }) -``` - - - -```swift -do { - let newCustomer = Customer(name: "Rene") - let newCustomerCreated = try await Amplify.API.mutate(request: .create(newCustomer)).get() - existingCart.setCustomer(newCustomerCreated) - let updatedCart = try await Amplify.API.mutate(request: .update(existingCart)).get() -} catch { - print("Create customer or cart failed", error) -} -``` - - - -```dart -final newCustomer = Customer(name: "Ian"); -final newCustomerRequest = ModelMutations.create(newCustomer); -final newCustomerResponse = await Amplify.API.mutate(request: newCustomerRequest).response; - -final cartWithUpdatedCustomer = existingCart.copyWith(customer: newCustomerResponse.data); -final cartUpdateRequest = ModelMutations.update(cartWithUpdatedCustomer); -final cartUpdateResponse = await Amplify.API.mutate(request: cartUpdateRequest).response; -``` - - -### Delete a "Has One" relationship between records - - -You can set the relationship field to `null` to delete a "Has One" relationship between records. - -```ts -await client.models.Cart.update({ - id: project.id, - customerId: null, -}); -``` - - - -You can set the relationship field to `null` to delete a "Has One" relationship between records. - -```kt -val cartWithRemovedCustomer = existingCart.copyOfBuilder().customer(null).build() - -Amplify.API.mutate(ModelMutation.update(cartWithRemovedCustomer), - { Log.i("MyAmplifyApp", "Updated cart with id: ${it.data.id}")}, - { Log.e("MyAmplifyApp", "Create failed", it)}, -) -``` - - - -You can set the relationship field to `nil` to delete a "Has One" relationship between records. - -```swift -do { - existingCart.setCustomer(nil) - let cartWithCustomerRemoved = try await Amplify.API.mutate(request: .update(existingCart)).get() -} catch { - print("Failed to remove customer from cart", error) -} -``` - - - -```dart -final cartWithRemovedCustomer = existingCart.copyWith(customer: null); -final cartRemoveRequest = ModelMutations.update(cartWithRemovedCustomer); -final cartRemoveResponse = await Amplify.API.mutate(request: cartRemoveRequest).response; -``` - - - -### Load related data in "Has One" relationships - - -```swift -do { - guard let queriedCart = try await Amplify.API.query( - request: .get( - Cart.self, - byIdentifier: existingCart.identifier)).get() else { - print("Missing cart") - return - } - - let customer = try await queriedCart.customer -} catch { - print("Failed to fetch cart or customer", error) -} -``` - - - -```dart -// Fetch the cart with the cart id. -final cartRequest = ModelQueries.get( - Cart.classType, CartModelIdentifier(id: "MY_CART_ID")); -final cartResult = await Amplify.API.query(request: cartRequest).response; -final cart = cartResult.data!; - -// Do the customer call to with the id from cart -if (cart.customerId != null) { - final customerRequest = ModelQueries.get( - Customer.classType, CustomerModelIdentifier(id: cart.customerId!)); - final customerResult = - await Amplify.API.query(request: customerRequest).response; - final customer = customerResult.data!; -} -``` - - - - -### Lazy load a "Has One" relationship - - -```ts -const { data: cart } = await client.models.Cart.get({ id: "MY_CART_ID"}); -const { data: customer } = await cart.customer(); -``` - - - -```kt -Amplify.API.query( - ModelQuery.get(Team::class.java, Team.TeamIdentifier("YOUR_TEAM_ID")), - { - suspend { - try { - val customer = when (val customerReference = cart.customer) { - is LoadedModelReference -> { - customerReference.value - } - - is LazyModelReference -> { - customerReference.fetchModel() - } - } - Log.i("MyAmplifyApp", "customer: $customer") - } catch (error: ApiException) { - Log.e("MyAmplifyApp", "Failed to fetch customer", error) - } - } - }, - { Log.e("MyAmplifyApp", "Failed to get team")} -) -``` - - -### Eagerly load a "Has One" relationship - - -```ts -const { data: cart } = await client.models.Cart.get( - { id: "MY_CART_ID" }, - { selectionSet: ['id', 'customer.*'] }, -); - -console.log(cart.customer.id) -``` - - - -```kt -val cart = Amplify.API.query( - ModelQuery.get( - Cart::class.java, - Cart.CartIdentifier("YOUR_CART_ID") - ) { cartPath -> - includes(cartPath.customer) - }, -{ val customer = (cart.customer as? LoadedModelReference)?.value }, -{ Log.e("MyAmplifyApp", "Failed to fetch cart", it) }) -``` - - - - -### Handling orphaned foreign keys on parent record deletion in "Has One" relationship - -```ts -// Get the customer with their associated cart -const { data: customerWithCart } = await client.models.Customer.get( - { id: customerId }, - { selectionSet: ["id", "activeCart.*"] }, -); - -// Delete Cart if exists -await client.models.Cart.delete({ id: customerWithCart.activeCart.id }); - -// Delete the customer -await client.models.Customer.delete({ id: customerWithCart.id }); -``` - - -## Model a "many-to-many" relationship -In order to create a many-to-many relationship between two models, you have to create a model that serves as a "join table". This "join table" should contain two one-to-many relationships between the two related entities. For example, to model a **Post** that has many **Tags** and a **Tag** has many **Posts**, you'll need to create a new **PostTag** model that represents the relationship between these two entities. - -```typescript -const schema = a.schema({ - PostTag: a.model({ - // 1. Create reference fields to both ends of - // the many-to-many relationship - // highlight-start - postId: a.id().required(), - tagId: a.id().required(), - // highlight-end - // 2. Create relationship fields to both ends of - // the many-to-many relationship using their - // respective reference fields - // highlight-start - post: a.belongsTo('Post', 'postId'), - tag: a.belongsTo('Tag', 'tagId'), - // highlight-end - }), - Post: a.model({ - title: a.string(), - content: a.string(), - // 3. Add relationship field to the join model - // with the reference of `postId` - // highlight-next-line - tags: a.hasMany('PostTag', 'postId'), - }), - Tag: a.model({ - name: a.string(), - // 4. Add relationship field to the join model - // with the reference of `tagId` - // highlight-next-line - posts: a.hasMany('PostTag', 'tagId'), - }), -}).authorization((allow) => allow.publicApiKey()); -``` - -## Model multiple relationships between two models - -Relationships are defined uniquely by their reference fields. For example, a Post can have separate relationships with a Person model for `author` and `editor`. - -```typescript -const schema = a.schema({ - Post: a.model({ - title: a.string().required(), - content: a.string().required(), - // highlight-start - authorId: a.id(), - author: a.belongsTo('Person', 'authorId'), - editorId: a.id(), - editor: a.belongsTo('Person', 'editorId'), - // highlight-end - }), - Person: a.model({ - name: a.string(), - // highlight-start - editedPosts: a.hasMany('Post', 'editorId'), - authoredPosts: a.hasMany('Post', 'authorId'), - // highlight-end - }), -}).authorization((allow) => allow.publicApiKey()); -``` - -On the client-side, you can fetch the related data with the following code: - - -```ts -const client = generateClient(); - -const { data: post } = await client.models.Post.get({ id: "SOME_POST_ID" }); - -const { data: author } = await post?.author(); -const { data: editor } = await post?.editor(); -``` - - - -```swift - do { - guard let queriedPost = try await Amplify.API.query( - request: .get( - Post.self, - byIdentifier: post.identifier)).get() else { - print("Missing post") - return - } - - let loadedAuthor = try await queriedPost.author - let loadedEditor = try await queriedPost.editor -} catch { - print("Failed to fetch post, author, or editor", error) -} -``` - - -## Model relationships for models with sort keys in their identifier - -In cases where your data model uses sort keys in the identifier, you need to also add reference fields and store the sort key fields in the related data model: - -```ts -const schema = a.schema({ - Post: a.model({ - title: a.string().required(), - content: a.string().required(), - // Reference fields must correspond to identifier fields. - // highlight-start - authorName: a.string(), - authorDoB: a.date(), - // Must pass references in the same order as identifiers. - author: a.belongsTo('Person', ['authorName', 'authorDoB']), - // highlight-end - }), - Person: a.model({ - name: a.string().required(), - dateOfBirth: a.date().required(), - // Must reference all reference fields corresponding to the - // identifier of this model. - authoredPosts: a.hasMany('Post', ['authorName', 'authorDoB']), - // highlight-next-line - }).identifier(['name', 'dateOfBirth']), -}).authorization((allow) => allow.publicApiKey()); -``` - -## Make relationships required or optional - -Amplify Data's relationships use reference fields to determine if a relationship is required or not. If you mark a reference field as required, then you can't "delete" a relationship between two models. You'd have to delete the related record as a whole. - -```ts -const schema = a.schema({ - Post: a.model({ - title: a.string().required(), - content: a.string().required(), - // You must supply an author when creating the post - // Author can't be set to `null`. - // highlight-next-line - authorId: a.id().required(), - author: a.belongsTo('Person', 'authorId'), - // You can optionally supply an editor when creating the post. - // Editor can also be set to `null`. - // highlight-next-line - editorId: a.id(), - editor: a.belongsTo('Person', 'editorId'), - }), - Person: a.model({ - name: a.string(), - // highlight-start - editedPosts: a.hasMany('Post', 'editorId'), - authoredPosts: a.hasMany('Post', 'authorId'), - // highlight-end - }), -}).authorization((allow) => allow.publicApiKey()); -``` - ---- - ---- -title: "Customize data model identifiers" -section: "build-a-backend/data/data-modeling" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-06-06T19:31:52.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/identifiers/" ---- - -Identifiers are defined using the `.identifier()` method on a model definition. Usage of the `.identifier()` method is optional; when it's not present, the model will automatically have a field called `id` of type `ID` that is automatically generated unless manually specified. - -```typescript -const schema = a.schema({ - Todo: a.model({ - content: a.string(), - completed: a.boolean(), - }) - .authorization(allow => [allow.publicApiKey()]), -}); -``` - - -```ts -const client = generateClient(); - -const todo = await client.models.Todo.create({ content: 'Buy Milk', completed: false }); -console.log(`New Todo created: ${todo.id}`); // New Todo created: 5DB6B4CC-CD41-49F5-9844-57C0AB506B69 -``` - - - -```swift -let todo = Todo( - content: "Buy Milk", - completed: false) -let createdTodo = try await Amplify.API.mutate(request: .create(todo)).get() -print("New Todo created: \(createdTodo)") -``` - - -If you want, you can use Amplify Data to define single-field and composite identifiers: -- Single-field identifier with a consumer-provided value (type: `id` or `string`, and must be marked `required`) -- Composite identifier with a set of consumer-provided values (type: `id` or `string`, and must be marked `required`) - -## Single-field identifier - -If the default `id` identifier field needs to be customized, you can do so by passing the name of another field. - -```typescript -const schema = a.schema({ - Todo: a.model({ - todoId: a.id().required(), - content: a.string(), - completed: a.boolean(), - }) - .identifier(['todoId']) - .authorization(allow => [allow.publicApiKey()]), -}); -``` - - -```ts -const client = generateClient(); - -const { data: todo, errors } = await client.models.Todo.create({ todoId: 'MyUniqueTodoId', content: 'Buy Milk', completed: false }); -console.log(`New Todo created: ${todo.todoId}`); // New Todo created: MyUniqueTodoId -``` - - - -```swift -let todo = Todo( - todoId: "MyUniqueTodoId", - content: "Buy Milk", - completed: false) -let createdTodo = try await Amplify.API.mutate(request: .create(todo)).get() -print("New Todo created: \(createdTodo)") -``` - - -## Composite identifier - -For cases where items are uniquely identified by more than a single field, you can pass an array of the field names to the `identifier()` function: - -```typescript -const schema = a.schema({ - StoreBranch: a.model({ - geoId: a.id().required(), - name: a.string().required(), - country: a.string(), - state: a.string(), - city: a.string(), - zipCode: a.string(), - streetAddress: a.string(), - }).identifier(['geoId', 'name']) - .authorization(allow => [allow.publicApiKey()]), -}); -``` - -```ts -const client = generateClient(); - -const branch = await client.models.StoreBranch.get({ geoId: '123', name: 'Downtown' }); // All identifier fields are required when retrieving an item -``` - - - -```swift -let queriedStoreBranch = try await Amplify.API.query( - request: .get( - StoreBranch.self, - byIdentifier: .identifier( - geoId: "123", - name: "Downtown"))) -``` - - ---- - ---- -title: "Customize secondary indexes" -section: "build-a-backend/data/data-modeling" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2026-02-09T14:57:32.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/secondary-index/" ---- - -// cspell:ignore ACCOUNTREPRESENTATIVEID -You can optimize your list queries based on "secondary indexes". For example, if you have a **Customer** model, you can query based on the customer's **id** identifier field by default but you can add a secondary index based on the **accountRepresentativeId** to get list customers for a given account representative. - -A secondary index consists of a "hash key" and, optionally, a "sort key". Use the "hash key" to perform strict equality and the "sort key" for greater than (gt), greater than or equal to (ge), less than (lt), less than or equal to (le), equals (eq), begins with, and between operations. - -```ts title="amplify/data/resource.ts" -export const schema = a.schema({ - Customer: a - .model({ - name: a.string(), - phoneNumber: a.phone(), - accountRepresentativeId: a.id().required(), - }) - // highlight-next-line - .secondaryIndexes((index) => [index("accountRepresentativeId")]) - .authorization(allow => [allow.publicApiKey()]), -}); -``` - - -The example client query below allows you to query for "Customer" records based on their `accountRepresentativeId`: - -```ts title="src/App.tsx" -import { type Schema } from '../amplify/data/resource'; -import { generateClient } from 'aws-amplify/data'; - -const client = generateClient(); - -const { data, errors } = - // highlight-start - await client.models.Customer.listCustomerByAccountRepresentativeId({ - accountRepresentativeId: "YOUR_REP_ID", - }); - // highlight-end -``` - - - -The example client query below creates a custom GraphQL request that allows you to query for "Customer" records based on their `accountRepresentativeId`: - -```swift -struct PaginatedList: Decodable { - let items: [ModelType] - let nextToken: String? -} -let operationName = "listCustomer8ByAccountRepresentativeId" -let document = """ -query ListCustomer8ByAccountRepresentativeId { - \(operationName)(accountRepresentativeId: "\(accountRepresentativeId)") { - items { - createdAt - accountRepresentativeId - id - name - phoneNumber - updatedAt - } - nextToken - } -} -""" - -let request = GraphQLRequest>( - document: document, - responseType: PaginatedList.self, - decodePath: operationName) - -let queriedCustomers = try await Amplify.API.query( - request: request).get() -``` - - - -The example client query below allows you to query for "Customer" records based on their `accountRepresentativeId`: - -```dart title="lib/main.dart" -import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'models/ModelProvider.dart'; - -// highlight-start -final request = ModelQueries.list( - Customer.classType, - where: Customer.ACCOUNTREPRESENTATIVEID.eq(YOUR_REP_ID), -); -// highlight-end - -``` - - -
    Review how this works under the hood with Amazon DynamoDB - -Amplify uses Amazon DynamoDB tables as the default data source for `a.model()`. For key-value databases, it is critical to model your access patterns with "secondary indexes". Use the `.secondaryIndexes()` modifier to configure a secondary index. - -**Amazon DynamoDB** is a key-value and document database that delivers single-digit millisecond performance at any scale but making it work for your access patterns requires a bit of forethought. DynamoDB query operations may use at most two attributes to efficiently query data. The first query argument passed to a query (the hash key) must use strict equality and the second attribute (the sort key) may use gt, ge, lt, le, eq, beginsWith, and between. DynamoDB can effectively implement a wide variety of access patterns that are powerful enough for the majority of applications. - -
    - -## Add sort keys to secondary indexes - -You can define "sort keys" to add a set of flexible filters to your query, such as "greater than" (gt), "greater than or equal to" (ge), "less than" (lt), "less than or equal to" (le), "equals" (eq), "begins with" (beginsWith), and "between" operations. - -```ts title="amplify/data/resource.ts" -export const schema = a.schema({ - Customer: a - .model({ - name: a.string(), - phoneNumber: a.phone(), - accountRepresentativeId: a.id().required(), - }) - .secondaryIndexes((index) => [ - index("accountRepresentativeId") - // highlight-next-line - .sortKeys(["name"]), - ]) - .authorization(allow => [allow.owner()]), -}); -``` - - -On the client side, you should find a new `listBy...` query that's named after hash key and sort keys. For example, in this case: `listByAccountRepresentativeIdAndName`. You can supply the filter as part of this new list query: - -```ts title="src/App.tsx" -const { data, errors } = - // highlight-next-line - await client.models.Customer.listCustomerByAccountRepresentativeIdAndName({ - accountRepresentativeId: "YOUR_REP_ID", - name: { - beginsWith: "Rene", - }, - }); -``` - - - -On the client side, you can create a custom GraphQL request based on the new `listBy...` query that's named after hash key and sort keys. You can supply the filter as part of this new list query: - -```swift -struct PaginatedList: Decodable { - let items: [ModelType] - let nextToken: String? -} -let operationName = "listCustomer9ByAccountRepresentativeIdAndName" -let document = """ -query ListCustomer8ByAccountRepresentativeId { - \(operationName)(accountRepresentativeId: "\(accountRepresentativeId)", name: {beginsWith: "\(name)"}) { - items { - accountRepresentativeId - createdAt - id - name - phoneNumber - updatedAt - } - nextToken - } -} -""" -let request = GraphQLRequest>( - document: document, - responseType: PaginatedList.self, - decodePath: operationName) - -let queriedCustomers = try await Amplify.API.query( - request: request).get() -``` - - - -The example client query below allows you to query for "Customer" records based on their `name` AND their `accountRepresentativeId`: - -```dart title="lib/main.dart" -import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'models/ModelProvider.dart'; - -// highlight-start -final request = ModelQueries.list( - Customer.classType, - where: Customer.ACCOUNTREPRESENTATIVEID.eq(YOUR_REP_ID) & Customer.NAME.beginsWith("Rene"), -); -// highlight-end - -``` - - -## Customize the query field for secondary indexes - -You can also customize the auto-generated query name under `client.models..listBy...` by setting the `queryField()` modifier. - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Customer: a - .model({ - name: a.string(), - phoneNumber: a.phone(), - accountRepresentativeId: a.id().required(), - }) - .secondaryIndexes((index) => [ - index("accountRepresentativeId") - // highlight-next-line - .queryField("listByRep"), - ]) - .authorization(allow => [allow.owner()]), -}); -``` - - -In your client app code, you'll see query updated under the Data client: - -```ts title="src/App.tsx" -const { - data, - errors - // highlight-next-line -} = await client.models.Customer.listByRep({ - accountRepresentativeId: 'YOUR_REP_ID', -}) -``` - - - -In your client app code, you can use the updated query name. - -```swift -struct PaginatedList: Decodable { - let items: [ModelType] - let nextToken: String? -} -let operationName = "listByRep" -let document = """ -query ListByRep { - \(operationName)(accountRepresentativeId: "\(accountRepresentativeId)") { - items { - accountRepresentativeId - createdAt - id - name - phoneNumber - updatedAt - } - nextToken - } -} -""" - -let request = GraphQLRequest>( - document: document, - responseType: PaginatedList.self, - decodePath: operationName) - -let queriedCustomers = try await Amplify.API.query( - request: request).get() -``` - - - -In your client app code, you can use the updated query name. - -```dart title="lib/main.dart" -import 'package:amplify_api/amplify_api.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'models/ModelProvider.dart'; - -var accountRepresentativeId = "John"; -var operationName = "listByRep"; -var document = """ - query ListByRep { - $operationName(accountRepresentativeId: "$accountRepresentativeId") { - items { - accountRepresentativeId - createdAt - id - name - phoneNumber - updatedAt - } - nextToken - } - } - """; - -final request = GraphQLRequest( - document: document, - variables: { - 'accountRepresentativeId': accountRepresentativeId, - 'operationName': operationName, - }, -); -``` - - -## Customize the name of secondary indexes - -To customize the underlying DynamoDB's index name, you can optionally provide the `name()` modifier. - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Customer: a - .model({ - name: a.string(), - phoneNumber: a.phone(), - accountRepresentativeId: a.id().required(), - }) - .secondaryIndexes((index) => [ - index("accountRepresentativeId") - // highlight-next-line - .name("MyCustomIndexName"), - ]) - .authorization(allow => [allow.owner()]), -}); -``` - -## Customize the projection type of secondary indexes - -You can control which attributes are projected (copied) into your secondary indexes to reduce storage costs and optimize for your query patterns. Use the `.projection()` modifier to specify a projection type. - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Product: a - .model({ - id: a.id().required(), - name: a.string().required(), - category: a.string().required(), - price: a.float().required(), - description: a.string(), - inStock: a.boolean().required(), - }) - .secondaryIndexes((index) => [ - // Project only keys - smallest index size - // highlight-next-line - index('category').projection('KEYS_ONLY'), - - // Project specific attributes you need to query in addition to the keys - // highlight-next-line - index('inStock').projection('INCLUDE', ['name', 'price']), - - // Project all attributes (default) - // highlight-next-line - index('name').projection('ALL'), - ]) - .authorization(allow => [allow.publicApiKey()]), -}); -``` - -
    Review projection types and when to use them - -DynamoDB supports three projection types: - -- **KEYS_ONLY** - Projects only index and primary keys. Smallest index size, lowest storage cost. -- **INCLUDE** - Projects keys plus specified attributes. Balances storage cost with query flexibility. -- **ALL** - Projects all attributes. Maximum query flexibility but highest storage cost (default). - -Choose based on your query patterns and storage cost requirements. For more details, see [Projections in DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.Projections). - -
    - ---- - ---- -title: "Disable Operations" -section: "build-a-backend/data/data-modeling" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-03-03T02:02:22.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/data-modeling/disable-operations/" ---- - -The `disableOperations` method allows you to selectively disable specific GraphQL operations for a model in your Amplify application. This can be useful for implementing specialized API designs and reduce the number of resources being deployed. - -You can disable operations by adding the `disableOperations` method to your model definition: - -```ts title="amplify/data/resource.ts" -export const schema = a.schema({ - Customer: a - .model({ - name: a.string(), - phoneNumber: a.phone(), - accountRepresentativeId: a.id().required(), - }) - // highlight-next-line - .disableOperations(["mutations", "subscriptions", "queries"]) - .authorization(allow => [allow.publicApiKey()]), -}); -``` - -## Available Operation Types - -The `disableOperations` method accepts an array of operation types that you want to disable: - -### General Operation Categories - -- `mutations`: Disables all mutation operations (create, update, delete) -- `subscriptions`: Disables all real-time subscription operations (onCreate, onUpdate, onDelete) -- `queries`: Disables all query operations (get, list) - -### Specific Operations -You can also disable more granular operations: -Query Operations - -- `get`: Disables the ability to fetch a single item by ID -- `list`: Disables the ability to fetch multiple items - -### Mutation Operations - -- `create`: Disables the ability to create new items -- `update`: Disables the ability to update existing items -- `delete`: Disables the ability to delete items - -### Subscription Operations - -- `onCreate`: Disables real-time notifications when items are created -- `onUpdate`: Disables real-time notifications when items are updated -- `onDelete`: Disables real-time notifications when items are deleted - -You can specify one or more operation types in the array to disable them: - -``` -// Disable all mutations -disableOperations: ["mutations"] - -// Disable both subscriptions and queries -disableOperations: ["subscriptions", "queries"] - -// Disable specific operations -disableOperations: ["create", "update", "list"] - -// Disable specific subscription types -disableOperations: ["onCreate", "onUpdate"] - -// Mix general categories with specific operations -disableOperations: ["queries", "create", "onDelete"] -``` - ---- - ---- -title: "Customize your auth rules" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-11-05T19:14:26.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/" ---- - -Use the `.authorization()` modifier to configure authorization rules for public, signed-in user, per user, and per user group data access. **Authorization rules operate on the deny-by-default principle**. Meaning that if an authorization rule is not specifically configured, it is denied. - -```ts -const schema = a.schema({ - Post: a.model({ - content: a.string() - }).authorization(allow => [ - // Allow anyone auth'd with an API key to read everyone's posts. - allow.publicApiKey().to(['read']), - // Allow signed-in user to create, read, update, - // and delete their __OWN__ posts. - allow.owner(), - ]) -}) -``` -In the example above, everyone (`public`) can read every Post but authenticated users (`owner`) can create, read, update, and delete their own posts. Amplify also allows you to restrict the allowed operations, combine multiple authorization rules, and apply fine-grained field-level authorization. - -## Available authorization strategies - -Use the guide below to select the correct authorization strategy for your use case: - -| **Recommended use case** | **Strategy** | **`authMode`** | -|--------------------------|--------------|----------------| -| [Public data access where users or devices are anonymous. Anyone with the AppSync API key is granted access.](/[platform]/build-a-backend/data/customize-authz/public-data-access) | `publicApiKey` | `apiKey` | -| [Recommended for production environment's public data access. Public data access where unauthenticated users or devices are granted permissions using Amazon Cognito identity pool's role for unauthenticated identities.]( /[platform]/build-a-backend/data/customize-authz/public-data-access/#add-public-authorization-rule-using-iam-authentication) | `guest` | `identityPool` | -| [Per user data access. Access is restricted to the "owner" of a record. Leverages `amplify/auth/resource.ts` Cognito user pool by default.](/[platform]/build-a-backend/data/customize-authz/per-user-per-owner-data-access) | `owner`/`ownerDefinedIn`/`ownersDefinedIn` | `userPool` / `oidc` | -| [Any signed-in data access. Unlike owner-based access, **any** signed-in user has access.](/[platform]/build-a-backend/data/customize-authz/signed-in-user-data-access) | `authenticated` | `userPool` / `oidc` / `identityPool` | -| [Per user group data access. A specific or dynamically configured group of users has access.](/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access) | `group`/`groupDefinedIn`/`groups`/`groupsDefinedIn` | `userPool` / `oidc` | -| [Define your own custom authorization rule within a serverless function.](/[platform]/build-a-backend/data/customize-authz/custom-data-access-patterns) | `custom` | `lambda` | - -## Understand how authorization rules are applied - -Authorization rules can be applied globally across all data models in a schema, onto specific data models, and onto specific fields. - -Amplify will always use the most specific authorization rule that is available. For example, if there is an authorization rule for a field and an authorization rule for the model that the field belongs to, Amplify will evaluate against the field-level authorization rule. Review [Field-level authorization rules](#field-level-authorization-rules) to learn more. - -If there are multiple authorization rules present, they will be logically OR'ed. Review [Configure multiple authorization rules](#configure-multiple-authorization-rules) to learn more. For `userPools` and `oidc` authorization modes, the rules are evaluated in the sequence `authenticated` > `group(s)` > `owner(s)DefinedIn` > `group(s)DefinedIn`. - -### Global authorization rule (only for getting started) - -To help you get started, you can define an authorization rule on the data schema that will be applied to all data models that **do not** have a model-level authorization rule. Instead of having a global authorization rule for all production environments, we recommend creating specific authorization rules for each model or field. - -The global authorization rule below uses `allow.publicApiKey()`. This example allows anyone to create, read, update, and delete and is applied to every data model. - -```ts -const schema = a.schema({ - // Because no model-level authorization rule is present - // this model will use the global authorization rule. - Todo: a.model({ - content: a.string() - }), - - // Will use model-level authorization rule - Notes: a.model({ - content: a.string() - // [Model-level authorization rule] - }).authorization(allow => [allow.publicApiKey().to(['read'])]) - -// [Global authorization rule] -}).authorization(allow => [ - allow.publicApiKey() -]) -``` - -### Model-level authorization rules - -Add an authorization rule to a model to apply the authorization rule to all fields of that model. - -```ts -const schema = a.schema({ - Post: a.model({ - content: a.string(), - createdBy: a.string() - // [Model-level authorization rule] - // All fields (content, createdBy) will be protected by - // this authorization rule - }).authorization(allow => [ - allow.publicApiKey().to(['read']), - allow.owner(), - ]) -}) -``` - -### Field-level authorization rules - -When an authorization rule is added to a field, it will strictly define the authorization rules applied on the field. Field-level authorization rules **do not** inherit model-level authorization rules. Meaning, only the specified field-level authorization rule is applied. - -In the example below: -- Owners are allowed to create, read, update, and delete Employee records they own -- Any signed in user has read access and can read data with the exception of the `ssn` field -- Only the `ssn` field has `owner` auth applied and this field-level auth rule means that model-level auth rules are not applied - -```ts -const schema = a.schema({ - Employee: a.model({ - name: a.string(), - email: a.string(), - // [Field-level authorization rule] - // This auth rule will be used for the "ssn" field - // All other fields will use the model-level auth rule - ssn: a.string().authorization(allow => [allow.owner()]), - }) - - // [Model-level authorization rule] - .authorization(allow => [ - allow.authenticated().to(["read"]), - allow.owner() - ]), -}); -``` - -### Non-model authorization rules - -**Non-model** types are any types added to the schema without using `a.model()`. These consist of modifiers such as `a.customType()`, `a.enum()`,`a.query()`, `a.mutation()`, or `a.subscription()`. - -Dynamic authorization rules such as `allow.owner()`, `allow.ownerDefinedIn()`, `allow.groupDefinedIn()` are not supported for **non-model** types. - -```ts -const schema = a.schema({ - // ... - listCustomType: a - .query() - .returns(a.ref("CustomType").array()) - .handler( - a.handler.custom({ - entry: "./handler.js", - }) - ) - .authorization((allow) => [ - // Static auth rules - Supported - allow.guest(), - allow.publicApiKey(), - allow.authenticated(), - allow.group("Admin"), - allow.groups(["Teacher", "Student"]), - - // Dynamic auth rules - Not supported - allow.owner(), - allow.ownerDefinedIn("owner"), - allow.ownersDefinedIn("otherOwners"), - allow.groupDefinedIn("group"), - allow.groupsDefinedIn("otherGroups"), - ]), -}); -``` - -There are TS warnings and validation checks in place that will cause a sandbox deployment to fail if unsupported auth rules are defined on custom queries and mutations. - -### Configure multiple authorization rules - -When combining multiple authorization rules, they are "logically OR"-ed. In the following example: -- Any user (using Amazon Cognito identity pool's unauthenticated roles) is allowed to read all posts -- Owners are allowed to create, read, update, and delete their own posts - -```ts -const schema = a.schema({ - Post: a.model({ - title: a.string(), - content: a.string() - }).authorization(allow => [ - allow.guest().to(["read"]), - allow.owner() - ]) -}) -``` - -On the client side, make sure to always authenticate with the corresponding authorization mode. - - -```ts -import { generateClient } from 'aws-amplify/data' -import type { Schema } from '@/amplify/data/resource' // Path to your backend resource definition - -const client = generateClient() - -// Creating a post is restricted to Cognito User Pools -const { data: newPostResult , errors } = await client.models.Post.create({ - query: queries.createPost, - variables: { input: { title: 'Hello World' } }, - authMode: 'userPool', -}); - -// Listing posts is available to unauthenticated users (verified by Amazon Cognito identity pool's unauthenticated role) -const { data: listPostsResult , errors } = await client.models.Post.list({ - query: queries.listPosts, - authMode: 'identityPool', -}); -``` - - - -Creating a post is restricted to Cognito User Pools. - -```swift -do { - let post = Post(title: "Hello World") - let createdTodo = try await Amplify.API.mutate(request: .create( - post, - authMode: .amazonCognitoUserPools)).get() -} catch { - print("Failed to create post", error) -} -``` - -Listing posts is available to unauthenticated users (verified by Amazon Cognito identity pool's unauthenticated role) - -```swift -do { - let queriedPosts = try await Amplify.API.query(request: .list( - Post.self, - authMode: .awsIAM)).get() - print("Number of posts:", queriedPosts.count) -} catch { - print("Failed to list posts", error) -} -``` - - -## IAM authorization - -All Amplify Gen 2 projects enable IAM authorization for data access. This ensures that the Amplify console's [data manager](/[platform]/build-a-backend/data/manage-with-amplify-console/) will be able to access your API. It also allows you to authorize other administrative or machine-to-machine access using your own IAM policies. See the [AWS AppSync Developer Guide](https://docs.aws.amazon.com/appsync/latest/devguide/security_iam_service-with-iam.html) for details on how AWS AppSync works with IAM. - -## Authorization on custom types - -Authorization rules are only supported on data models (model-level and field-level) and custom operations (queries, mutations and subscriptions). They are not fully supported on custom types, including custom types returned by custom operations. For example, consider a custom query that returns a custom type: - -```ts -const schema = a.schema({ - Counter: a.customType({ - value: a.integer(), - }) - .authorization(...), // <-- not supported - getCounter: a - .mutation() - .arguments({ - id: a.string().required(), - }) - .returns(a.ref("Counter")) - .handler( - a.handler.custom({ - entry: "./getCounter.js", - }) - ) - .authorization((allow) => [allow.authenticated()]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema: schema, - authorizationModes: { - defaultAuthorizationMode: "userPool", - }, -}); -``` - -As you can see, the custom `Counter` type does not support the `.authorization()` modifier. Instead, behind the scenes, Amplify will add appropriate authorization rules to `Counter` to allow authenticated users to access it. That means that any signed-in user will be able to access the custom operation and all fields of the custom type. - -> **Info:** **Note**: IAM authorization is not currently supported for custom operations that return custom types if `defaultAuthorizationMode` is not `iam`. See [GitHub issue #2929](https://github.com/aws-amplify/amplify-category-api/issues/2929) for details and suggested workarounds. - -## Learn more about specific authorization strategies - ---- - ---- -title: "Public data access" -section: "build-a-backend/data/customize-authz" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-12-12T20:29:55.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/public-data-access/" ---- - -The public authorization strategy grants everyone access to the API, which is protected behind the scenes with an API key. You can also override the authorization provider to use an unauthenticated IAM role from Cognito instead of an API key for public access. - -## Add public authorization rule using API key-based authentication - -To grant everyone access, use the `.public()` authorization strategy. Behind the scenes, the API will be protected with an API key. - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - .authorization(allow => [allow.publicApiKey()]), -}); -``` - - -In your application, you can perform CRUD operations against the model using `client.models.` by specifying the `apiKey` auth mode. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient(); - -const { errors, data: newTodo } = await client.models.Todo.create( - { - content: 'My new todo', - }, - // highlight-start - { - authMode: 'apiKey', - } - // highlight-end -); -``` - - - -In your application, you can perform CRUD operations against the model by specifying the `apiKey` auth mode. - - - -```dart -try { - final todo = Todo(content: 'My new todo'); - final request = ModelMutations.create( - todo, - authorizationMode: APIAuthorizationType.apiKey, - ); - final createdTodo = await Amplify.API.mutations(request: request).response; - - if (createdTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - safePrint('Mutation result: ${createdTodo.name}'); - -} on APIException catch (e) { - safePrint('Failed to create todo', e); -} -``` - - - -```swift -do { - let todo = Todo(content: "My new todo") - let createdTodo = try await Amplify.API.mutate(request: .create( - todo, - authMode: .apiKey)).get() -} catch { - print("Failed to create todo", error) -} -``` - - -### Extend API Key Expiration - -If the API key has not expired, you can extend the expiration date by deploying your app again. The API key expiration date will be set to `expiresInDays` days from the date when the app is deployed. In the example below, the API key will expire 7 days from the latest deployment. - -```ts title="amplify/data/resource.ts" -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 7, - }, - }, -}); -``` - -### Rotate an API Key - -You can rotate an API key if it was expired, compromised, or deleted. To rotate an API key, you can override the logical ID of the API key resource in the `amplify/backend.ts` file. This will create a new API key with a new logical ID. - -```ts title="amplify/backend.ts" -const backend = defineBackend({ - auth, - data, -}); - -backend.data.resources.cfnResources.cfnApiKey?.overrideLogicalId( - `recoverApiKey${new Date().getTime()}` -); -``` - -Deploy your app. After the deploy has finished, remove the override to the logical ID and deploy your app again to use the default logical ID. - -```ts title="amplify/backend.ts" -const backend = defineBackend({ - auth, - data, -}); - -// backend.data.resources.cfnResources.cfnApiKey?.overrideLogicalId( -// `recoverApiKey${new Date().getTime()}` -// ); -``` - -A new API key will be created for your app. - -## Add public authorization rule using Amazon Cognito identity pool's unauthenticated role - -You can also override the authorization provider. In the example below, `identityPool` is specified as the provider which allows you to use an "Unauthenticated Role" from the Cognito identity pool for public access instead of an API key. Your Auth resources defined in `amplify/auth/resource.ts` generates scoped down IAM policies for the "Unauthenticated role" in the Cognito identity pool automatically. - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - .authorization(allow => [allow.guest()]), -}); -``` - - -In your application, you can perform CRUD operations against the model using `client.models.` with the `identityPool` auth mode. - -> **Info:** If you're not using the auto-generated **amplify_outputs.json** file, then you must set the Amplify Library resource configuration's `allowGuestAccess` flag to `true`. This lets the Amplify Library use the unauthenticated role from your Cognito identity pool when your user isn't logged in. -> ->
    Amplify configuration -> ```ts title="src/App.tsx" -import { Amplify } from "aws-amplify"; -import outputs from "../amplify_outputs.json"; - -Amplify.configure( - { - ...outputs, - Auth: { - Cognito: { - identityPoolId: config.aws_cognito_identity_pool_id, - userPoolClientId: config.aws_user_pools_web_client_id, - userPoolId: config.aws_user_pools_id, - allowGuestAccess: true, - }, - }, - } -); -``` ->
    - -```ts title="src/App.tsx" -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient(); - -const { errors, data: newTodo } = await client.models.Todo.create( - { - content: 'My new todo', - }, - // highlight-start - { - authMode: 'identityPool', - } - // highlight-end -); -``` - - - -In your application, you can perform CRUD operations against the model with the `iam` auth mode. - -```dart -try { - final todo = Todo(content: 'My new todo'); - final request = ModelMutations.create( - todo, - authorizationMode: APIAuthorizationType.iam, - ); - final createdTodo = await Amplify.API.mutations(request: request).response; - - if (createdTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - safePrint('Mutation result: ${createdTodo.name}'); - -} on APIException catch (e) { - safePrint('Failed to create todo', e); -} -``` - - - -In your application, you can perform CRUD operations against the model with the `awsIAM` auth mode. - -```swift -do { - let todo = Todo(content: "My new todo") - let createdTodo = try await Amplify.API.mutate(request: .create( - todo, - authMode: .awsIAM)).get() -} catch { - print("Failed to create todo", error) -} -``` - - ---- - ---- -title: "Per-user/per-owner data access" -section: "build-a-backend/data/customize-authz" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-06-19T16:13:00.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/per-user-per-owner-data-access/" ---- - -The `owner` authorization strategy restricts operations on a record to only the record's owner. When configured, the `owner` field will automatically be added and populated with the identity of the created user. The API will authorize against the `owner` field to allow or deny operations. - -## Add per-user/per-owner authorization rule - -You can use the `owner` authorization strategy to restrict a record's access to a specific user. When `owner` authorization is configured, only the record's `owner` is allowed the specified operations. - -```ts title="amplify/data/resource.ts" -// The "owner" of a Todo is allowed to create, read, update, and delete their own todos -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - .authorization(allow => [allow.owner()]), -}); -``` - -```ts title="amplify/data/resource.ts" -// The "owner" of a Todo record is only allowed to create, read, and update it. -// The "owner" of a Todo record is denied to delete it. -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - .authorization(allow => [allow.owner().to(['create', 'read', 'update'])]), -}); -``` - - -In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient(); - -const { errors, data: newTodo } = await client.models.Todo.create( - { - content: 'My new todo', - }, - // highlight-start - { - authMode: 'userPool', - } - // highlight-end -); -``` - - - -In your application, you can perform CRUD operations against the model with the `userPools` auth mode. - -```dart -try { - final todo = Todo(content: 'My new todo'); - final request = ModelMutations.create( - todo, - authorizationMode: APIAuthorizationType.userPools, - ); - final createdTodo = await Amplify.API.mutations(request: request).response; - - if (createdTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - safePrint('Mutation result: ${createdTodo.name}'); - -} on APIException catch (e) { - safePrint('Failed to create todo', e); -} -``` - - - -In your application, you can perform CRUD operations against the model with the `amazonCognitoUserPools` auth mode. - -```swift -do { - let todo = Todo(content: "My new todo") - let createdTodo = try await Amplify.API.mutate(request: .create( - todo, - authMode: .amazonCognitoUserPools)).get() -} catch { - print("Failed to create todo", error) -} -``` - - -Behind the scenes, Amplify will automatically add a `owner: a.string()` field to each record which contains the record owner's identity information upon record creation. - -By default, the Cognito user pool's user information is populated into the `owner` field. The value saved includes `sub` and `username` in the format `::`. The API will authorize against the full value of `::` or `sub` / `username` separately and return `username`. You can alternatively configure [OpenID Connect as an authorization provider](/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider). - -> **Warning:** **By default, owners can reassign the owner of their existing record to another user.** -> -> To prevent an owner from reassigning their record to another user, protect the owner field (by default `owner: String`) with a [field-level authorization rule](/[platform]/build-a-backend/data/customize-authz/#field-level-authorization-rules). For example, in a social media app, you would want to prevent Alice from being able to reassign Alice's Post to Bob. -> -> ```ts -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - owner: a.string().authorization(allow => [allow.owner().to(['read', 'delete'])]), - }) - .authorization(allow => [allow.owner()]), -}); -``` - -## Customize the owner field - -You can override the `owner` field to your own preferred field, by specifying a custom `ownerField` in the authorization rule. - -```ts -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - author: a.string(), // record owner information now stored in "author" field - }) - .authorization(allow => [allow.ownerDefinedIn('author')]), -}); -``` - ---- - ---- -title: "Multi-user data access" -section: "build-a-backend/data/customize-authz" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-06-19T16:13:00.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/multi-user-data-access/" ---- - -The `ownersDefinedIn` rule grants a set of users access to a record by automatically creating an `owners` field to store the allowed record owners. You can override the default owners field name by specifying `inField` with the desired field name to store the owner information. You can dynamically manage which users can access a record by updating the owner field. - -## Add multi-user authorization rule - -If you want to grant a set of users access to a record, you use the `ownersDefinedIn` rule. This automatically creates a `owners: a.string().array()` field to store the allowed owners. - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - owners: a.string().array(), - }) - .authorization(allow => [allow.ownersDefinedIn('owners')]), -}); -``` - - -In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient(); - -// Create a record with current user as first owner -const { errors, data: newTodo } = await client.models.Todo.create( - { - content: 'My new todo', - }, - // highlight-start - { - authMode: 'userPool', - } - // highlight-end -); -``` - -Add another user as an owner - -```ts -await client.models.Todo.update( - { - id: newTodo.id, - owners: [...(newTodo.owners as string[]), otherUserId], - }, - // highlight-start - { - authMode: "userPool" - } - // highlight-end -); -``` - - - -In your application, you can perform CRUD operations against the model with the `userPools` auth mode. - -```dart -try { - final todo = Todo(content: 'My new todo'); - final request = ModelMutations.create( - todo, - authorizationMode: APIAuthorizationType.userPools, - ); - final createdTodo = await Amplify.API.mutations(request: request).response; - - if (createdTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - safePrint('Mutation result: ${createdTodo.name}'); - -} on APIException catch (e) { - safePrint('Failed to create todo', e); -} -``` - -Add another user as an owner - -```dart -try { - createdTodo.owners!.add(otherUserId); - let updateRequest = ModelMutations.update( - createdTodo, - authorizationMode: APIAuthorizationType.userPools, - ); - final updatedTodo = await Amplify.API.mutations(request: updateRequest).response; - - if (updatedTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - -} catch { - safePrint("Failed to update todo", error) -} -``` - - - -In your application, you can perform CRUD operations against the model with the `amazonCognitoUserPools` auth mode. - -```swift -do { - let todo = Todo(content: "My new todo") - let createdTodo = try await Amplify.API.mutate(request: .create( - todo, - authMode: .amazonCognitoUserPools)).get() -} catch { - print("Failed to create todo", error) -} -``` - -Add another user as an owner - -```swift -do { - createdTodo.owners?.append(otherUserId) - let updatedTodo = try await Amplify.API.mutate(request: .update( - createdTodo, - authMode: .amazonCognitoUserPools)).get() -} catch { - print("Failed to update todo", error) -} -``` - - -## Override to a list of owners - -You can override the `inField` to a list of owners. Use this if you want a dynamic set of users to have access to a record. In the example below, the `authors` list is populated with the creator of the record upon record creation. The creator can then update the `authors` field with additional users. Any user listed in the `authors` field can access the record. - -```ts -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - authors: a.string().array(), // record owner information now stored in "authors" field - }) - .authorization(allow => [allow.ownersDefinedIn('authors')]), -}); -``` - ---- - ---- -title: "Signed-in user data access" -section: "build-a-backend/data/customize-authz" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-06-19T16:13:00.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/signed-in-user-data-access/" ---- - -The `authenticated` authorization strategy restricts record access to only signed-in users authenticated through IAM, Cognito, or OpenID Connect, applying the authorization rule to all users. It provides a simple way to make data private to all authenticated users. - -## Add signed-in user authorization rule - -You can use the `authenticated` authorization strategy to restrict a record's access to every signed-in user. - - -**Note:** If you want to restrict a record's access to a specific user, see [Per-user/per-owner data access](/[platform]/build-a-backend/data/customize-authz/per-user-per-owner-data-access/). The `authenticated` authorization strategy detailed on this page applies the authorization rule for data access to **every** signed-in user. - - -In the example below, anyone with a valid JWT token from the Cognito user pool is allowed access to all Todos. - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - .authorization(allow => [allow.authenticated()]), -}); -``` - - -In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient(); - -const { errors, data: newTodo } = await client.models.Todo.create( - { - content: 'My new todo', - }, - // highlight-start - { - authMode: 'userPool', - } - // highlight-end -); -``` - - - -In your application, you can perform CRUD operations against the model with the `userPools` auth mode. - -```dart -try { - final todo = Todo(content: 'My new todo'); - final request = ModelMutations.create( - todo, - authorizationMode: APIAuthorizationType.userPools, - ); - final createdTodo = await Amplify.API.mutations(request: request).response; - - if (createdTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - safePrint('Mutation result: ${createdTodo.name}'); - -} on APIException catch (e) { - safePrint('Failed to create todo', e); -} -``` - - - -In your application, you can perform CRUD operations against the model with the `amazonCognitoUserPools` auth mode. - -```swift -do { - let todo = Todo(content: "My new todo") - let createdTodo = try await Amplify.API.mutate(request: .create( - todo, - authMode: .amazonCognitoUserPools)).get() -} catch { - print("Failed to create todo", error) -} -``` - - -## Use identity pool for signed-in user authentication - -You can also override the authorization provider. In the example below, `identityPool` is specified as the provider which allows you to use an "Unauthenticated Role" from the Cognito identity pool for public access instead of an API key. Your Auth resources defined in `amplify/auth/resource.ts` generates scoped down IAM policies for the "Unauthenticated role" in the Cognito identity pool automatically. - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - .authorization(allow => [allow.authenticated('identityPool')]), -}); -``` - - -In your application, you can perform CRUD operations against the model using `client.models.` with the `iam` auth mode. - -> **Info:** The user must be logged in for the Amplify Library to use the authenticated role from your Cognito identity pool. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient(); - -const { errors, data: newTodo } = await client.models.Todo.create( - { - content: 'My new todo', - }, - // highlight-start - { - authMode: 'identityPool', - } - // highlight-end -); -``` - - - -In your application, you can perform CRUD operations against the model with the `iam` auth mode. - -```dart -try { - final todo = Todo(content: 'My new todo'); - final request = ModelMutations.create( - todo, - authorizationMode: APIAuthorizationType.iam, - ); - final createdTodo = await Amplify.API.mutations(request: request).response; - - if (createdTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - safePrint('Mutation result: ${createdTodo.name}'); - -} on APIException catch (e) { - safePrint('Failed to create todo', e); -} -``` - - - -In your application, you can perform CRUD operations against the model with the `awsIAM` auth mode. - -> **Info:** The user must be logged in for the Amplify Library to use the authenticated role from your Cognito identity pool. - -```swift -do { - let todo = Todo(content: "My new todo") - let createdTodo = try await Amplify.API.mutate(request: .create( - todo, - authMode: .awsIAM)).get() -} catch { - print("Failed to create todo", error) -} -``` - - -In addition, you can also use OpenID Connect with `authenticated` authorization. See [OpenID Connect as an authorization provider](/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider/). - ---- - ---- -title: "User group-based data access" -section: "build-a-backend/data/customize-authz" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-12-23T16:31:56.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/user-group-based-data-access/" ---- - -You can use the `group` authorization strategy to restrict access based on user groups. The user group authorization strategy allows restricting data access to specific user groups or groups defined dynamically on each data record. - -## Add authorization rules for specific user groups - -When you want to restrict access to a specific set of user groups, provide the group names in the `groups` parameter. In the example below, only users that are part of the "Admin" user group are granted access to the Salary model. - -```ts title="amplify/data/resource.ts" -// allow one specific group -const schema = a.schema({ - Salary: a - .model({ - wage: a.float(), - currency: a.string(), - }) - .authorization(allow => [allow.group('Admin')]), -}); -``` - - -In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient(); - -// As a signed-in user that belongs to the 'Admin' User Pool Group -const { errors, data: newSalary } = await client.models.Salary.create( - { - wage: 50.25, - currency: 'USD' - }, - // highlight-start - { - authMode: 'userPool', - } - // highlight-end -); -``` - - - -In your application, you can perform CRUD operations against the model with the `userPools` auth mode. - -```dart -try { - final todo = Todo(content: 'My new todo'); - final request = ModelMutations.create( - todo, - authorizationMode: APIAuthorizationType.userPools, - ); - final createdTodo = await Amplify.API.mutations(request: request).response; - - if (createdTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - safePrint('Mutation result: ${createdTodo.name}'); - -} on APIException catch (e) { - safePrint('Failed to create todo', e); -} -``` - - - -In your application, you can perform CRUD operations against the model with the `amazonCognitoUserPools` auth mode. - -```swift -do { - let salary = Salary( - wage: 50.25, - currency: "USD") - let createdSalary = try await Amplify.API.mutate(request: .create( - salary, - authMode: .amazonCognitoUserPools)).get() -} catch { - print("Failed to create salary", error) -} -``` - - -This can then be updated to allow access to multiple defined groups; in this example below we added access for "Leadership". - -```ts -// allow multiple specific groups -const schema = a.schema({ - Salary: a - .model({ - wage: a.float(), - currency: a.string(), - }) - .authorization(allow => [allow.groups(['Admin', 'Leadership'])]), -}); -``` - -## Add authorization rules for dynamically set user groups - -With dynamic group authorization, each record contains an attribute specifying what Cognito groups should be able to access it. Use the first argument to specify which attribute in the underlying data store holds this group information. To specify that a single group should have access, use a field of type `a.string()`. To specify that multiple groups should have access, use a field of type `a.string().array()`. - -```ts -// Dynamic group authorization with multiple groups -const schema = a.schema({ - Post: a - .model({ - title: a.string(), - groups: a.string().array(), - }) - .authorization(allow => [allow.groupsDefinedIn('groups')]), -}); -``` - -```ts -// Dynamic group authorization with a single group -const schema = a.schema({ - Post: a - .model({ - title: a.string(), - group: a.string(), - }) - .authorization(allow => [allow.groupDefinedIn('group')]), -}); -``` - -By default, `group` authorization leverages Amazon Cognito user pool groups but you can also use OpenID Connect with `group` authorization. See [OpenID Connect as an authorization provider](/[platform]/build-a-backend/data/customize-authz/using-oidc-authorization-provider). - - -**Known limitations for real-time subscriptions when using dynamic group authorization:** - -1. If you authorize based on a single group per record, then subscriptions are only supported if the user is part of 5 or fewer user groups -2. If you authorize via an array of groups (`groups: a.string().array()` used in the example above), - - subscriptions are only supported if the user is part of 20 or fewer groups - - you can only authorize 20 or fewer user groups per record - - -## Access user groups from the session - - -You can access a user's groups from their session using the Auth category: - -```ts -import { fetchAuthSession } from 'aws-amplify/auth'; - -const session = await fetchAuthSession(); -const groups = session.tokens.accessToken.payload['cognito:groups'] || []; - -console.log('User groups:', groups); -``` - - ---- - ---- -title: "Custom data access using Lambda functions" -section: "build-a-backend/data/customize-authz" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-06-19T16:13:00.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/custom-data-access-patterns/" ---- - -You can define your own custom authorization rule with a Lambda function. - -```ts title="amplify/data/resource.ts" -import { - type ClientSchema, - a, - defineData, - defineFunction, -} from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - // STEP 1 - // Indicate which models / fields should use a custom authorization rule - .authorization(allow => [allow.custom()]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'lambda', - // STEP 2 - // Pass in the function to be used for a custom authorization rule - lambdaAuthorizationMode: { - function: defineFunction({ - entry: './custom-authorizer.ts', - }), - // (Optional) STEP 3 - // Configure the token's time to live - timeToLiveInSeconds: 300, - }, - }, -}); -``` - -In your application, you can perform CRUD operations against the model using `client.models.` with the `lambda` auth mode. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient(); - -const { errors, data: newTodo } = await client.models.Todo.create( - { - content: 'My new todo', - }, - // highlight-start - { - authMode: 'lambda', - } - // highlight-end -); -``` - - - -In your application, you can perform CRUD operations against the model with the `function` auth mode. - -```dart -try { - final todo = Todo(content: 'My new todo'); - final request = ModelMutations.create( - todo, - authorizationMode: APIAuthorizationType.function, - ); - final createdTodo = await Amplify.API.mutations(request: request).response; - - if (createdTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - safePrint('Mutation result: ${createdTodo.name}'); - -} on APIException catch (e) { - safePrint('Failed to create todo', e); -} -``` - - -The Lambda function of choice will receive an authorization token from the client and execute the desired authorization logic. The AppSync GraphQL API will receive a payload from Lambda after invocation to allow or deny the API call accordingly. - -To configure a Lambda function as the authorization mode, create a new file `amplify/data/custom-authorizer.ts`. You can use this Lambda function code template as a starting point for your authorization handler code: - -```ts -// amplify/data/custom-authorizer.ts - -// This is sample code. Update this to suite your needs -import type { AppSyncAuthorizerHandler } from 'aws-lambda'; // types imported from @types/aws-lambda - -type ResolverContext = { - userid: string; - info: string; - more_info: string; -}; - -export const handler: AppSyncAuthorizerHandler = async ( - event -) => { - console.log(`EVENT: ${JSON.stringify(event)}`); - const { - authorizationToken, - requestContext: { apiId, accountId } - } = event; - const response = { - isAuthorized: authorizationToken === 'custom-authorized', - resolverContext: { - // eslint-disable-next-line spellcheck/spell-checker - userid: 'user-id', - info: 'contextual information A', - more_info: 'contextual information B' - }, - deniedFields: [ - `arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Event/fields/comments`, - `Mutation.createEvent` - ], - ttlOverride: 300 - }; - console.log(`RESPONSE: ${JSON.stringify(response, null, 2)}`); - return response; -}; -``` - -You can use the template above as a starting point for your custom authorization rule. The authorization Lambda function receives the following event: - -```json -{ - "authorizationToken": "ExampleAuthToken123123123", # Authorization token specified by client - "requestContext": { - "apiId": "aaaaaa123123123example123", # AppSync API ID - "accountId": "111122223333", # AWS Account ID - "requestId": "f4081827-1111-4444-5555-5cf4695f339f", - "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", # GraphQL query - "operationName": "MyQuery", # GraphQL operation name - "variables": {} # any additional variables supplied to the operation - } -} -``` - -Your Lambda authorization function needs to return the following JSON: - -```json -{ - // required - "isAuthorized": true, // if "false" then an UnauthorizedException is raised, access is denied - "resolverContext": { "banana": "very yellow" }, // JSON object visible as $ctx.identity.resolverContext in VTL resolver templates - - // optional - "deniedFields": ["TypeName.FieldName"], // Forces the fields to "null" when returned to the client - "ttlOverride": 10 // The number of seconds that the response should be cached for. Overrides default specified in "amplify update api" -} -``` - -Review the Amplify documentation to set the custom authorization token for the [Data client](/[platform]/build-a-backend/data/connect-to-API). - ---- - ---- -title: "Use OpenID Connect as an authorization provider" -section: "build-a-backend/data/customize-authz" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-06-19T16:13:00.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/using-oidc-authorization-provider/" ---- - -Private, owner, and group authorization can be configured with an OpenID Connect (OIDC) authorization mode. Add `"oidc"` to the authorization rule as the provider. Use the `oidcAuthorizationMode` property to configure the *OpenID Connect provider name*, *OpenID Connect provider domain*, *Client ID*, *Issued at TTL*, and *Auth Time TTL*. - -The example below highlights the supported authorization strategies with a `oidc` authorization provider. For owner and group-based authorization, you also will need to [specify a custom identity and group claim](/[platform]/build-a-backend/data/customize-authz/configure-custom-identity-and-group-claim). - -```ts title="amplify/data/resource.ts" -// amplify/data/resource.ts -import { a, defineData, type ClientSchema } from '@aws-amplify/backend'; - -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - .authorization(allow => [ - allow.owner('oidc').identityClaim('user_id'), - allow.authenticated('oidc'), - allow - .groups(['testGroupName'], 'oidc') - .withClaimIn('user_groups'), - ]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'oidc', - oidcAuthorizationMode: { - oidcProviderName: 'oidc-provider-name', - oidcIssuerUrl: 'https://example.com', - clientId: 'client-id', - tokenExpiryFromAuthInSeconds: 300, - tokenExpireFromIssueInSeconds: 600, - }, - }, -}); -``` - - -In your application, you can perform CRUD operations against the model using `client.models.` by specifying the `oidc` auth mode. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient(); - -const { errors, data: todos } = await client.models.Todo.list({ - // highlight-start - authMode: "oidc", - // highlight-end -}); -``` - - - -In your application, you can perform CRUD operations against the model with the `oidc` auth mode. - -```dart -try { - final todo = Todo(content: 'My new todo'); - final request = ModelMutations.create( - todo, - authorizationMode: APIAuthorizationType.oidc, - ); - final createdTodo = await Amplify.API.mutations(request: request).response; - - if (createdTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - safePrint('Mutation result: ${createdTodo.name}'); - -} on APIException catch (e) { - safePrint('Failed to create todo', e); -} -``` - - ---- - ---- -title: "Configure custom identity and group claims" -section: "build-a-backend/data/customize-authz" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-06-19T16:13:00.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/configure-custom-identity-and-group-claim/" ---- - -Amplify Data supports using custom identity and group claims if you do not wish to use the default Amazon Cognito-provided `cognito:groups` or the double-colon-delimited claims, `sub::username`, from your JWT token. This can be helpful if you are using tokens from a 3rd party OIDC system or if you wish to populate a claim with a list of groups from an external system, such as when using a [Pre Token Generation Lambda Trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html) which reads from a database. - -To use custom claims specify `identityClaim` or `groupClaim` as appropriate. In the example below, the `identityClaim` is specified and the record owner will check against this `user_id` claim. Similarly, if the `user_groups` claim contains a "Moderator" string then access will be granted. - -```ts title="amplify/data/resource.ts" -import { a, defineData, type ClientSchema } from '@aws-amplify/backend'; - -const schema = a.schema({ - Post: a - .model({ - id: a.id(), - owner: a.string(), - postname: a.string(), - content: a.string(), - }) - .authorization(allow => [ - allow.owner().identityClaim('user_id'), - allow.groups(['Moderator']).withClaimIn('user_groups'), - ]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ schema }); - -``` - - -In your application, you can perform CRUD operations against the model using `client.models.` with the `userPool` auth mode. - -```ts -import { generateClient } from 'aws-amplify/data'; -import type { Schema } from '../amplify/data/resource'; // Path to your backend resource definition - -const client = generateClient(); - -const { errors, data: newTodo } = await client.models.Todo.create( - { - postname: 'My New Post' - content: 'My post content', - }, - // highlight-start - { - authMode: 'userPool', - } - // highlight-end -); -``` - - - -In your application, you can perform CRUD operations against the model with the `userPools` auth mode. - -```dart -try { - final todo = Todo(content: 'My new todo'); - final request = ModelMutations.create( - todo, - authorizationMode: APIAuthorizationType.userPools, - ); - final createdTodo = await Amplify.API.mutations(request: request).response; - - if (createdTodo == null) { - safePrint('errors: ${response.errors}'); - return; - } - safePrint('Mutation result: ${createdTodo.name}'); - -} on APIException catch (e) { - safePrint('Failed to create todo', e); -} -``` - - ---- - ---- -title: "Grant Lambda function access to API and Data" -section: "build-a-backend/data/customize-authz" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-04-18T18:44:52.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/" ---- - -Function access to `defineData` can be configured using an authorization rule on the schema object. - -```ts title="amplify/data/resource.ts" -import { - a, - defineData, - type ClientSchema -} from '@aws-amplify/backend'; -import { functionWithDataAccess } from '../function/data-access/resource'; - -const schema = a - .schema({ - Todo: a.model({ - name: a.string(), - description: a.string(), - isDone: a.boolean() - }) - }) - // highlight-next-line - .authorization(allow => [allow.resource(functionWithDataAccess)]); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema -}); -``` - -Create a new directory and a resource file, `amplify/functions/data-access/resource.ts`. Then, define the Function with `defineFunction`: - -```ts title="amplify/functions/data-access/resource.ts" -import { defineFunction } from '@aws-amplify/backend'; - -export const functionWithDataAccess = defineFunction({ - name: 'data-access', -}); -``` - -The object returned from `defineFunction` can be passed directly to `allow.resource()` in the schema authorization rules. This will grant the function the ability to execute Query, Mutation, and Subscription operations against the GraphQL API. Use the `.to()` method to narrow down access to one or more operations. - -```ts title="amplify/data/resource.ts" -const schema = a - .schema({ - Todo: a.model({ - name: a.string(), - description: a.string(), - isDone: a.boolean() - }) - }) - // highlight-start - .authorization(allow => [ - allow.resource(functionWithDataAccess).to(['query', 'listen']) - ]); // allow query and subscription operations but not mutations -// highlight-end -``` - -> **Info:** Function access can only be configured on the schema object. It cannot be configured on individual models or fields. - -## Access the API using `aws-amplify` - -In the handler file for your function, configure the Amplify data client - -```ts title="amplify/functions/data-access/handler.ts" -import type { Handler } from 'aws-lambda'; -import type { Schema } from '../../data/resource'; -import { Amplify } from 'aws-amplify'; -import { generateClient } from 'aws-amplify/data'; -import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; -import { env } from '$amplify/env/'; // replace with your function name - -const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig(env); - -Amplify.configure(resourceConfig, libraryOptions); - -const client = generateClient(); - -export const handler = async (event) => { - // your function code goes here -} -``` - -> **Warning:** When configuring Amplify with `getAmplifyDataClientConfig`, your function consumes schema information from an S3 bucket created during backend deployment with grants for the access your function need to use it. Any changes to this bucket outside of backend deployment may break your function. - -Once you have generated the client code, update the function to access the data. The following code creates a todo and then lists all todos. - -```ts title="amplify/functions/data-access/handler.ts" -//... -const client = generateClient(); - -export const handler: Handler = async (event) => { - const { errors: createErrors, data: newTodo } = await client.models.Todo.create({ - name: "My new todo", - description: "Todo description", - isDone: false, - }) - - const { errors: listErrors, data: todos } = await client.models.Todo.list(); - - return event; -}; -``` - ---- - ---- -title: "Add custom queries and mutations" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-02-25T18:57:30.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/" ---- - -The `a.model()` data model provides a solid foundation for querying, mutating, and fetching data. However, you may need additional customizations to meet specific requirements around custom API requests, response formatting, and/or fetching from external data sources. - -In the following sections, we walk through the three steps to create a custom query or mutation: - -1. Define a custom query or mutation -2. Configure custom business logic handler code -3. Invoke the custom query or mutation - -## Step 1 - Define a custom query or mutation - -| Type | When to choose | -| --- | --- | -| Query | When the request only needs to read data and will not modify any backend data | -| Mutation | When the request will modify backend data | - -For every custom query or mutation, you need to set a return type and, optionally, arguments. Use `a.query()` or `a.mutation()` to define your custom query or mutation in your **amplify/data/resource.ts** file: - -#### [Custom query] - -```ts -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - // 1. Define your return type as a custom type - EchoResponse: a.customType({ - content: a.string(), - executionDuration: a.float() - }), - - // 2. Define your query with the return type and, optionally, arguments - echo: a - .query() - // arguments that this query accepts - .arguments({ - content: a.string() - }) - // return type of the query - .returns(a.ref('EchoResponse')) - // only allow signed-in users to call this API - .authorization(allow => [allow.authenticated()]) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema -}); -``` - -#### [Custom mutation] - -```ts -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - // 1. Define your return type as a custom type or model - Post: a.model({ - id: a.id(), - content: a.string(), - likes: a.integer() - }), - - // 2. Define your mutation with the return type and, optionally, arguments - likePost: a - .mutation() - // arguments that this query accepts - .arguments({ - postId: a.string() - }) - // return type of the query - .returns(a.ref('Post')) - // only allow signed-in users to call this API - .authorization(allow => [allow.authenticated()]) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema -}); -``` - -## Step 2 - Configure custom business logic handler code - -After your query or mutation is defined, you need to author your custom business logic. You can either define it in a [function](/[platform]/build-a-backend/functions/) or using a [custom resolver powered by AppSync JavaScript resolver](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html). - -#### [Function] - -In your `amplify/data/echo-handler/` folder, create a `handler.ts` file. You can import a utility type for your function handler via the `Schema` type from your backend resource. This gives you type-safe handler parameters and return values. - -```ts title="amplify/data/echo-handler/handler.ts" -import type { Schema } from '../resource' - -export const handler: Schema["echo"]["functionHandler"] = async (event, context) => { - const start = performance.now(); - return { - content: `Echoing content: ${event.arguments.content}`, - executionDuration: performance.now() - start - }; -}; -``` - -In your `amplify/data/resource.ts` file, define the function using `defineFunction` and then reference the function with your query or mutation using `a.handler.function()` as a handler. - -```ts title="amplify/data/resource.ts" -import { - type ClientSchema, - a, - defineData, - defineFunction // 1.Import "defineFunction" to create new functions -} from '@aws-amplify/backend'; - -// 2. define a function -const echoHandler = defineFunction({ - entry: './echo-handler/handler.ts' -}) - -const schema = a.schema({ - EchoResponse: a.customType({ - content: a.string(), - executionDuration: a.float() - }), - - echo: a - .query() - .arguments({ content: a.string() }) - .returns(a.ref('EchoResponse')) - .authorization(allow => [allow.publicApiKey()]) - // 3. set the function has the handler - .handler(a.handler.function(echoHandler)) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30 - }, - }, -}); -``` - -If you want to use an existing Lambda function, you can reference it by its name: `a.handler.function('name-of-existing-lambda-fn')`. Note that Amplify will not update this external Lambda function or its dependencies. - -#### [Custom resolver powered by AppSync JavaScript resolvers] - -Custom resolvers work on a "request/response" basis. You choose a data source, map your request to the data source's input parameters, and then map the data source's response back to the query/mutation's return type. Custom resolvers provide the benefit of no cold starts, less infrastructure to manage, and no additional charge for Lambda function invocations. Review [Choosing between custom resolver and function](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-overview-js.html#choosing-data-source). - -In your `amplify/data/resource.ts` file, define a custom handler using `a.handler.custom`. - -```ts title="amplify/data/resource.ts" -import { - type ClientSchema, - a, - defineData, -} from '@aws-amplify/backend'; - -const schema = a.schema({ - Post: a.model({ - content: a.string(), - likes: a.integer() - .authorization(allow => [allow.authenticated().to(['read'])]) - }).authorization(allow => [ - allow.owner(), - allow.authenticated().to(['read']) - ]), - - likePost: a - .mutation() - .arguments({ postId: a.id() }) - .returns(a.ref('Post')) - .authorization(allow => [allow.authenticated()]) - .handler(a.handler.custom({ - dataSource: a.ref('Post'), - entry: './increment-like.js' - })) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30 - } - }, -}); -``` - -```ts title="amplify/data/increment-like.js" -import { util } from '@aws-appsync/utils'; - -export function request(ctx) { - return { - operation: 'UpdateItem', - key: util.dynamodb.toMapValues({ id: ctx.args.postId}), - update: { - expression: 'ADD likes :plusOne', - expressionValues: { ':plusOne': { N: 1 } }, - } - } -} - -export function response(ctx) { - return ctx.result -} -``` - -By default, you'll be able to access any existing database tables (powered by Amazon DynamoDB) using `a.ref('MODEL_NAME')`. But you can also reference any other external data source from within your AWS account, by adding them to your backend definition. - -The supported data sources are: -- Amazon DynamoDB -- AWS Lambda -- Amazon RDS databases with Data API -- Amazon EventBridge -- OpenSearch -- HTTP endpoints - -You can add these additional data sources via our `amplify/backend.ts` file: - -```ts title="amplify/backend.ts" -import * as dynamoDb from 'aws-cdk-lib/aws-dynamodb' -import { defineBackend } from '@aws-amplify/backend'; -import { auth } from './auth/resource'; -import { data } from './data/resource'; - -export const backend = defineBackend({ - auth, - data, -}); - -const externalDataSourcesStack = backend.createStack("MyExternalDataSources") - -const externalTable = dynamoDb.Table.fromTableName(externalDataSourcesStack, "MyTable", "MyExternalTable") - -backend.data.addDynamoDbDataSource( - // highlight-next-line - "ExternalTableDataSource", - externalTable) -``` - -In your schema you can then reference these additional data sources based on their name: - -```ts title="amplify/data/resource.ts" -import { - type ClientSchema, - a, - defineData, -} from '@aws-amplify/backend'; - -const schema = a.schema({ - Post: a.model({ - content: a.string(), - likes: a.integer() - .authorization(allow => [allow.authenticated().to(['read'])]) - }).authorization(allow => [ - allow.owner(), - allow.authenticated().to(['read']) - ]), - - likePost: a - .mutation() - .arguments({ postId: a.id() }) - .returns(a.ref('Post')) - .authorization(allow => [allow.authenticated()]) - .handler(a.handler.custom({ - // highlight-next-line - dataSource: "ExternalTableDataSource", - entry: './increment-like.js' - })) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30 - } - }, -}); -``` - -> **Warning:** All handlers must be of the same type. For example, you can't mix and match `a.handler.function` with `a.handler.custom` within a single `.handler()` modifier. - -## Step 3 - Invoke the custom query or mutation - - -From your generated Data client, you can find all your custom queries and mutations under the `client.queries.` and `client.mutations.` APIs respectively. - -#### [Custom query] - -```ts -const { data, errors } = await client.queries.echo({ - content: 'hello world!!!' -}); -``` - -#### [Custom mutation] - -```ts -const { data, errors } = await client.mutations.likePost({ - postId: 'hello' -}); -``` - - - - -```swift -struct EchoResponse: Codable { - public let echo: Echo - - struct Echo: Codable { - public let content: String - public let executionDuration: Float - } -} - -let document = """ - query EchoQuery($content: String!) { - echo(content: $content) { - content - executionDuration - } - } - """ - -let result = try await Amplify.API.query(request: GraphQLRequest( - document: document, - variables: [ - "content": "hello world!!!" - ], - responseType: EchoResponse.self -)) -switch result { -case .success(let response): - print(response.echo) -case .failure(let error): - print(error) -} -``` - - - -```kt -data class EchoDetails( - val content: String, - val executionDuration: Float -) - -data class EchoResponse( - val echo: EchoDetails -) - -val document = """ - query EchoQuery(${'$'}content: String!) { - echo(content: ${'$'}content) { - content - executionDuration - } - } -""".trimIndent() -val echoQuery = SimpleGraphQLRequest( - document, - mapOf("content" to "hello world!!!"), - String::class.java, - GsonVariablesSerializer()) - -Amplify.API.query( - echoQuery, - { - var gson = Gson() - val response = gson.fromJson(it.data, EchoResponse::class.java) - Log.i("MyAmplifyApp", "${response.echo.content}") - }, - { Log.e("MyAmplifyApp", "$it")} -) -``` - - - -First define a class that matches your response shape: - -```dart -class EchoResponse { - final Echo echo; - - EchoResponse({required this.echo}); - - factory EchoResponse.fromJson(Map json) { - return EchoResponse( - echo: Echo.fromJson(json['echo']), - ); - } -} - -class Echo { - final String content; - final double executionDuration; - - Echo({required this.content, required this.executionDuration}); - - factory Echo.fromJson(Map json) { - return Echo( - content: json['content'], - executionDuration: json['executionDuration'], - ); - } -} -``` - -Next, make the request and map the response to the classes defined above: - -```dart -// highlight-next-line -import 'dart:convert'; - -// highlight-start -const graphQLDocument = ''' - query Echo(\$content: String!) { - echo(content: \$content) { - content - executionDuration - } - } -'''; - -final echoRequest = GraphQLRequest( - document: graphQLDocument, - variables: {"content": "Hello world!!!"}, -); - -final response = - await Amplify.API.query(request: echoRequest).response; -safePrint(response); - -Map jsonMap = json.decode(response.data!); -EchoResponse echoResponse = EchoResponse.fromJson(jsonMap); -safePrint(echoResponse.echo.content); -// highlight-end -``` - -## Supported Argument Types in Custom Operations - -Custom operations can accept different types of arguments. Understanding these options helps define flexible and well-structured APIs. - -### Defining Arguments in Custom Operations - -When defining a custom operation, you can specify arguments using different types: - -- **Scalar Fields**: Basic types such as `string`, `integer`, `float`, etc -- **Custom Types**: Define inline `customType` -- **Reference Types**: Use `a.ref()` to reference enums and custom types - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Status: a.enum(['ACCEPTED', 'REJECTED']), - - getPost: a - .query() - .arguments({ - // scalar field - content: a.string(), - // inline custom type - config: a.customType({ - filter: a.string(), - // reference to enum - status: a.ref('Status') - }), - }) - .returns(a.string()) - .authorization(allow => [allow.authenticated()]) -}); -``` - -## Async function handlers - -Async function handlers allow you to execute long-running operations asynchronously, improving the responsiveness of your API. This is particularly useful for tasks that don't require an immediate response, such as batch processing, putting messages in a queue, and initiating a generative AI model inference. - -### Usage - -To define an async function handler, use the `.async()` method when defining your handler: - -```ts title="amplify/data/resource.ts" -const signUpForNewsletter = defineFunction({ - entry: './sign-up-for-newsletter/handler.ts' -}); - -const schema = a.schema({ - someAsyncOperation: a.mutation() - .arguments({ - email: a.email().required() - }) - .handler(a.handler.function(signUpForNewsletter).async()) - .authorization((allow) => allow.guest()), -}) -``` - -### Key Characteristics - -1. **Single Return Type**: Async handlers return a static type `EventInvocationResponse` and don't support specifying a return type. The `.returns()` method is not available for operations using async handlers. - -2. **Fire and Forget**: The client is informed whether the invocation was successfully queued, but doesn't receive data from the Lambda function execution. - -3. **Pipeline Support**: Async handlers can be used in function pipelines. If the final handler is an async function, the return type of the query or mutation is `EventInvocationResponse`. - ---- - ---- -title: "Connect to Amazon OpenSearch for search and aggregate queries" -section: "build-a-backend/data/custom-business-logic" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-05-05T16:41:40.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/search-and-aggregate-queries/" ---- - -Amazon OpenSearch Service provides a managed platform for deploying search and analytics solutions with OpenSearch or Elasticsearch. The zero-ETL integration between Amazon DynamoDB and OpenSearch Service allows seamless search on DynamoDB data by automatically replicating and transforming it without requiring custom code or infrastructure. This integration simplifies processes and reduces the operational workload of managing data pipelines. - -DynamoDB users gain access to advanced OpenSearch features like full-text search, fuzzy search, auto-complete, and vector search for machine learning capabilities. Amazon OpenSearch Ingestion synchronizes data between DynamoDB and OpenSearch Service, enabling near-instant updates and comprehensive insights across multiple DynamoDB tables. Developers can adjust index mapping templates to match Amazon DynamoDB fields with OpenSearch Service indexes. - -Amazon OpenSearch Ingestion, combined with S3 exports and DynamoDB streams, facilitates seamless data input from DynamoDB tables and automatic ingestion into OpenSearch. Additionally, the pipeline can back up data to S3 for potential future re-ingestion as needed. - -## Step 1: Setup the project - -Begin by setting up your project by following the instructions in the [Quickstart guide](/[platform]/start/quickstart/). For the purpose of this guide, we'll sync a Todo table from DynamoDB to OpenSearch. - -Firstly, add the Todo model to your schema: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - // highlight-start - Todo: a - .model({ - content: a.string(), - done: a.boolean(), - priority: a.enum(["low", "medium", "high"]), - }) - .authorization((allow) => [allow.publicApiKey()]) - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "apiKey", - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); - -``` - - -Important considerations: - -Ensure Point in Time Recovery (PITR) is enabled, which is crucial for the pipeline integration. -Enable DynamoDB streams to capture item changes that will be ingested into OpenSearch. - - - -```ts title="amplify/backend.ts" -// highlight-start -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -// highlight-end -import { defineBackend } from '@aws-amplify/backend'; -import { auth } from './auth/resource'; -import { data } from './data/resource'; - -const backend = defineBackend({ - auth, - data -}); - -// highlight-start -const todoTable = - backend.data.resources.cfnResources.amplifyDynamoDbTables['Todo']; - -// Update table settings -todoTable.pointInTimeRecoveryEnabled = true; - -todoTable.streamSpecification = { - streamViewType: dynamodb.StreamViewType.NEW_IMAGE -}; - -// Get the DynamoDB table ARN -const tableArn = backend.data.resources.tables['Todo'].tableArn; -// Get the DynamoDB table name -const tableName = backend.data.resources.tables['Todo'].tableName; -// highlight-end -``` - -## Step 2: Setting Up the OpenSearch Instance - -Create an OpenSearch instance with encryption. - -```ts title="amplify/backend.ts" -// highlight-start -import * as opensearch from 'aws-cdk-lib/aws-opensearchservice'; -import { RemovalPolicy } from "aws-cdk-lib"; -// highlight-end -import { defineBackend } from '@aws-amplify/backend'; -import { auth } from './auth/resource'; -import { data } from './data/resource'; - -const backend = defineBackend({ - auth, - data -}); - -const todoTable = - backend.data.resources.cfnResources.amplifyDynamoDbTables['Todo']; - -// Update table settings -todoTable.pointInTimeRecoveryEnabled = true; - -todoTable.streamSpecification = { - streamViewType: dynamodb.StreamViewType.NEW_IMAGE -}; - -// Get the DynamoDB table ARN -const tableArn = backend.data.resources.tables['Todo'].tableArn; -// Get the DynamoDB table name -const tableName = backend.data.resources.tables['Todo'].tableName; - -// highlight-start - -// Create the OpenSearch domain -const openSearchDomain = new opensearch.Domain( - backend.data.stack, - 'OpenSearchDomain', - { - version: opensearch.EngineVersion.OPENSEARCH_2_11, - capacity: { - // upgrade instance types for production use - masterNodeInstanceType: "t3.small.search", - masterNodes: 0, - dataNodeInstanceType: "t3.small.search", - dataNodes: 1, - }, - nodeToNodeEncryption: true, - // set removalPolicy to DESTROY to make sure the OpenSearch domain is deleted on stack deletion. - removalPolicy: RemovalPolicy.DESTROY, - encryptionAtRest: { - enabled: true - } - } -); -// highlight-end -``` -> **Warning:** **Important considerations:** -> -> We recommend configuring the `removalPolicy` to destroy resources for sandbox environments. By default, OpenSearch instances are not deleted when you run `npx ampx sandbox delete`, as the default removal policy for stateful resources is set to retain the resource. - -## Step 3: Setting Up Zero ETL from DynamoDB to OpenSearch - -### Step 3a: Setup Storage and IAM Role - -Establish Storage to back up raw events consumed by the OpenSearch pipeline. -Generate a file named `amplify/storage/resource.ts` and insert the provided content to set up a storage resource. Tailor your storage configurations to regulate access to different paths within your storage bucket. - -```ts title="amplify/storage/resource.ts" -import { defineStorage } from "@aws-amplify/backend" - -export const storage = defineStorage({ - name: "opensearch-backup-bucket-amplify-gen-2", - access: allow => ({ - 'public/*': [ - allow.guest.to(['list', 'write', 'get']) - ] - }) -}) -``` - -Get the `s3BucketArn` and `s3BucketName` values from storage resource as shown below. Additionally, configure an IAM role for the pipeline and assign the roles as indicated below. For further information on the required IAM roles, please refer to the [Setting up roles and users](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/pipeline-security-overview.html#pipeline-security-create) documentation. - -```ts title="amplify/backend.ts" -import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; -import * as opensearch from "aws-cdk-lib/aws-opensearchservice"; -import { RemovalPolicy } from "aws-cdk-lib"; -// highlight-next-line -import * as iam from "aws-cdk-lib/aws-iam"; -import { defineBackend } from "@aws-amplify/backend"; -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; -// highlight-next-line -import { storage } from "./storage/resource"; - -// Define backend resources -const backend = defineBackend({ - auth, - data, - //highlight-start - storage, - //highlight-end -}); - -const todoTable = - backend.data.resources.cfnResources.amplifyDynamoDbTables["Todo"]; - -// Update table settings -todoTable.pointInTimeRecoveryEnabled = true; - -todoTable.streamSpecification = { - streamViewType: dynamodb.StreamViewType.NEW_IMAGE, -}; - -// Get the DynamoDB table ARN -const tableArn = backend.data.resources.tables["Todo"].tableArn; -// Get the DynamoDB table name -const tableName = backend.data.resources.tables["Todo"].tableName; - -// Create the OpenSearch domain -const openSearchDomain = new opensearch.Domain( - backend.data.stack, - "OpenSearchDomain", - { - version: opensearch.EngineVersion.OPENSEARCH_2_11, - capacity: { - // upgrade instance types for production use - masterNodeInstanceType: "t3.small.search", - masterNodes: 0, - dataNodeInstanceType: "t3.small.search", - dataNodes: 1, - }, - nodeToNodeEncryption: true, - // set removalPolicy to DESTROY to make sure the OpenSearch domain is deleted on stack deletion. - removalPolicy: RemovalPolicy.DESTROY, - encryptionAtRest: { - enabled: true, - }, - } -); -// highlight-start -// Get the S3Bucket ARN -const s3BucketArn = backend.storage.resources.bucket.bucketArn; -// Get the S3Bucket Name -const s3BucketName = backend.storage.resources.bucket.bucketName; - -// Create an IAM role for OpenSearch integration -const openSearchIntegrationPipelineRole = new iam.Role( - backend.data.stack, - "OpenSearchIntegrationPipelineRole", - { - assumedBy: new iam.ServicePrincipal("osis-pipelines.amazonaws.com"), - inlinePolicies: { - openSearchPipelinePolicy: new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - actions: ["es:DescribeDomain"], - resources: [ - openSearchDomain.domainArn, - openSearchDomain.domainArn + "/*", - ], - effect: iam.Effect.ALLOW, - }), - new iam.PolicyStatement({ - actions: ["es:ESHttp*"], - resources: [ - openSearchDomain.domainArn, - openSearchDomain.domainArn + "/*", - ], - effect: iam.Effect.ALLOW, - }), - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "s3:GetObject", - "s3:AbortMultipartUpload", - "s3:PutObject", - "s3:PutObjectAcl", - ], - resources: [s3BucketArn, s3BucketArn + "/*"], - }), - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "dynamodb:DescribeTable", - "dynamodb:DescribeContinuousBackups", - "dynamodb:ExportTableToPointInTime", - "dynamodb:DescribeExport", - "dynamodb:DescribeStream", - "dynamodb:GetRecords", - "dynamodb:GetShardIterator", - ], - resources: [tableArn, tableArn + "/*"], - }), - ], - }), - }, - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName( - "AmazonOpenSearchIngestionFullAccess" - ), - ], - } -); -// highlight-end -``` - -For the S3 bucket, follow standard security practices: block public access, encrypt data at rest, and enable versioning. - -The IAM role should allow the OpenSearch Ingestion Service (OSIS) pipelines to assume it. Grant specific OpenSearch Service permissions and also provide DynamoDB and S3 access. You may customize permissions to follow the principle of least privilege. - -### Step 3b: OpenSearch Service Pipeline - -Define the pipeline construct and its configuration. - -When using OpenSearch, you can define the index template or mapping in advance based on your data structure, which allows you to set data types for each field in the document. This approach can be incredibly powerful for precise data ingestion and search. For more information on index mapping/templates, please refer to [OpenSearch documentation](https://opensearch.org/docs/latest/im-plugin/index-templates/). - -Customize the `template_content` JSON-representation to define the data structure for the ingestion pipeline. - -```ts title="amplify/backend.ts" -import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; -import * as opensearch from "aws-cdk-lib/aws-opensearchservice"; -import { RemovalPolicy } from "aws-cdk-lib"; -import * as iam from "aws-cdk-lib/aws-iam"; -import { defineBackend } from "@aws-amplify/backend"; -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; -import { storage } from "./storage/resource"; - -// Define backend resources -const backend = defineBackend({ - auth, - data, - storage, -}); - -const todoTable = - backend.data.resources.cfnResources.amplifyDynamoDbTables["Todo"]; - -// Update table settings -todoTable.pointInTimeRecoveryEnabled = true; - -todoTable.streamSpecification = { - streamViewType: dynamodb.StreamViewType.NEW_IMAGE, -}; - -// Get the DynamoDB table ARN -const tableArn = backend.data.resources.tables["Todo"].tableArn; -// Get the DynamoDB table name -const tableName = backend.data.resources.tables["Todo"].tableName; - -// Create the OpenSearch domain -const openSearchDomain = new opensearch.Domain( - backend.data.stack, - "OpenSearchDomain", - { - version: opensearch.EngineVersion.OPENSEARCH_2_11, - capacity: { - // upgrade instance types for production use - masterNodeInstanceType: "t3.small.search", - masterNodes: 0, - dataNodeInstanceType: "t3.small.search", - dataNodes: 1, - }, - nodeToNodeEncryption: true, - // set removalPolicy to DESTROY to make sure the OpenSearch domain is deleted on stack deletion. - removalPolicy: RemovalPolicy.DESTROY, - encryptionAtRest: { - enabled: true, - }, - } -); - -// Get the S3Bucket ARN -const s3BucketArn = backend.storage.resources.bucket.bucketArn; -// Get the S3Bucket Name -const s3BucketName = backend.storage.resources.bucket.bucketName; - -// Create an IAM role for OpenSearch integration -const openSearchIntegrationPipelineRole = new iam.Role( - backend.data.stack, - "OpenSearchIntegrationPipelineRole", - { - assumedBy: new iam.ServicePrincipal("osis-pipelines.amazonaws.com"), - inlinePolicies: { - openSearchPipelinePolicy: new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - actions: ["es:DescribeDomain"], - resources: [ - openSearchDomain.domainArn, - openSearchDomain.domainArn + "/*", - ], - effect: iam.Effect.ALLOW, - }), - new iam.PolicyStatement({ - actions: ["es:ESHttp*"], - resources: [ - openSearchDomain.domainArn, - openSearchDomain.domainArn + "/*", - ], - effect: iam.Effect.ALLOW, - }), - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "s3:GetObject", - "s3:AbortMultipartUpload", - "s3:PutObject", - "s3:PutObjectAcl", - ], - resources: [s3BucketArn, s3BucketArn + "/*"], - }), - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "dynamodb:DescribeTable", - "dynamodb:DescribeContinuousBackups", - "dynamodb:ExportTableToPointInTime", - "dynamodb:DescribeExport", - "dynamodb:DescribeStream", - "dynamodb:GetRecords", - "dynamodb:GetShardIterator", - ], - resources: [tableArn, tableArn + "/*"], - }), - ], - }), - }, - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName( - "AmazonOpenSearchIngestionFullAccess" - ), - ], - } -); - -// highlight-start -// Define OpenSearch index mappings -const indexName = "todo"; - -const indexMapping = { - settings: { - number_of_shards: 1, - number_of_replicas: 0, - }, - mappings: { - properties: { - id: { - type: "keyword", - }, - done: { - type: "boolean", - }, - content: { - type: "text", - }, - }, - }, -}; -// highlight-end -``` - -The configuration is a data-prepper feature of OpenSearch. For specific documentation on DynamoDB configuration, refer to [OpenSearch data-prepper documentation](https://opensearch.org/docs/latest/data-prepper/pipelines/configuration/sources/dynamo-db/). - -```ts title="amplify/backend.ts" -import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; -import * as opensearch from "aws-cdk-lib/aws-opensearchservice"; -import { RemovalPolicy } from "aws-cdk-lib"; -import * as iam from "aws-cdk-lib/aws-iam"; -import { defineBackend } from "@aws-amplify/backend"; -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; -import { storage } from "./storage/resource"; - -// Define backend resources -const backend = defineBackend({ - auth, - data, - storage, -}); - -const todoTable = - backend.data.resources.cfnResources.amplifyDynamoDbTables["Todo"]; - -// Update table settings -todoTable.pointInTimeRecoveryEnabled = true; - -todoTable.streamSpecification = { - streamViewType: dynamodb.StreamViewType.NEW_IMAGE, -}; - -// Get the DynamoDB table ARN -const tableArn = backend.data.resources.tables["Todo"].tableArn; -// Get the DynamoDB table name -const tableName = backend.data.resources.tables["Todo"].tableName; - -// Create the OpenSearch domain -const openSearchDomain = new opensearch.Domain( - backend.data.stack, - "OpenSearchDomain", - { - version: opensearch.EngineVersion.OPENSEARCH_2_11, - capacity: { - // upgrade instance types for production use - masterNodeInstanceType: "t3.small.search", - masterNodes: 0, - dataNodeInstanceType: "t3.small.search", - dataNodes: 1, - }, - nodeToNodeEncryption: true, - // set removalPolicy to DESTROY to make sure the OpenSearch domain is deleted on stack deletion. - removalPolicy: RemovalPolicy.DESTROY, - encryptionAtRest: { - enabled: true, - }, - } -); - -// Get the S3Bucket ARN -const s3BucketArn = backend.storage.resources.bucket.bucketArn; -// Get the S3Bucket Name -const s3BucketName = backend.storage.resources.bucket.bucketName; - -// Create an IAM role for OpenSearch integration -const openSearchIntegrationPipelineRole = new iam.Role( - backend.data.stack, - "OpenSearchIntegrationPipelineRole", - { - assumedBy: new iam.ServicePrincipal("osis-pipelines.amazonaws.com"), - inlinePolicies: { - openSearchPipelinePolicy: new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - actions: ["es:DescribeDomain"], - resources: [ - openSearchDomain.domainArn, - openSearchDomain.domainArn + "/*", - ], - effect: iam.Effect.ALLOW, - }), - new iam.PolicyStatement({ - actions: ["es:ESHttp*"], - resources: [ - openSearchDomain.domainArn, - openSearchDomain.domainArn + "/*", - ], - effect: iam.Effect.ALLOW, - }), - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "s3:GetObject", - "s3:AbortMultipartUpload", - "s3:PutObject", - "s3:PutObjectAcl", - ], - resources: [s3BucketArn, s3BucketArn + "/*"], - }), - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "dynamodb:DescribeTable", - "dynamodb:DescribeContinuousBackups", - "dynamodb:ExportTableToPointInTime", - "dynamodb:DescribeExport", - "dynamodb:DescribeStream", - "dynamodb:GetRecords", - "dynamodb:GetShardIterator", - ], - resources: [tableArn, tableArn + "/*"], - }), - ], - }), - }, - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName( - "AmazonOpenSearchIngestionFullAccess" - ), - ], - } -); - -// Define OpenSearch index mappings -const indexName = "todo"; - -const indexMapping = { - settings: { - number_of_shards: 1, - number_of_replicas: 0, - }, - mappings: { - properties: { - id: { - type: "keyword", - }, - isDone: { - type: "boolean", - }, - content: { - type: "text", - }, - priority: { - type: "text", - }, - }, - }, -}; - -// highlight-start - -// OpenSearch template definition -const openSearchTemplate = ` -version: "2" -dynamodb-pipeline: - source: - dynamodb: - acknowledgments: true - tables: - - table_arn: "${tableArn}" - stream: - start_position: "LATEST" - export: - s3_bucket: "${s3BucketName}" - s3_region: "${backend.storage.stack.region}" - s3_prefix: "${tableName}/" - aws: - sts_role_arn: "${openSearchIntegrationPipelineRole.roleArn}" - region: "${backend.data.stack.region}" - sink: - - opensearch: - hosts: - - "https://${openSearchDomain.domainEndpoint}" - index: "${indexName}" - index_type: "custom" - template_content: | - ${JSON.stringify(indexMapping)} - document_id: '\${getMetadata("primary_key")}' - action: '\${getMetadata("opensearch_action")}' - document_version: '\${getMetadata("document_version")}' - document_version_type: "external" - bulk_size: 4 - aws: - sts_role_arn: "${openSearchIntegrationPipelineRole.roleArn}" - region: "${backend.data.stack.region}" -`; -// highlight-end -``` - -This configuration defines the desired behavior of the pipeline for a single model. - -In the source configuration, DynamoDB is specified as the data source, along with the target table for ingestion and the starting point of the stream. Additionally, besides ingesting the stream into OpenSearch, a target S3 bucket is defined for backup purposes. Furthermore, an IAM role is set for the ingestion pipeline, ensuring it possesses the necessary permissions and policies as detailed in the documentation. - -Regarding the sink configuration, the OpenSearch domain cluster is specified by setting the host, index name, type, and template content (index mapping) for data formatting. Document-related metadata is configured along with the maximum bulk size for requests to OpenSearch in MB. Once again, an IAM role is specified for the sink portion of the pipeline. For further details on Sink configuration, please refer to the [OpenSearch documentation](https://opensearch.org/docs/latest/data-prepper/pipelines/configuration/sinks/sinks/). - -The sink configuration is an array. To create a different index on the same table, you can achieve this by adding a second OpenSearch configuration to the sink array. - -To index multiple tables, you'll need to configure multiple pipelines in the configuration. For further guidance, please consult the [pipeline section](https://opensearch.org/docs/latest/data-prepper/pipelines/pipelines/) of the OpenSearch documentation. - - - -**Note**: An OpenSearch Ingestion pipeline supports only one DynamoDB table as its source. For more details on current limitations, Please refer to [Amazon OpenSearch Limitation](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/configure-client-ddb.html#ddb-pipeline-limitations) section. - - - -Now, create the OSIS pipeline resource: - -```ts title="amplify/backend.ts" -import * as dynamodb from "aws-cdk-lib/aws-dynamodb"; -import * as opensearch from "aws-cdk-lib/aws-opensearchservice"; -import { RemovalPolicy } from "aws-cdk-lib"; -import * as iam from "aws-cdk-lib/aws-iam"; -// highlight-start -import * as osis from "aws-cdk-lib/aws-osis"; -import * as logs from "aws-cdk-lib/aws-logs"; -import { RemovalPolicy } from "aws-cdk-lib"; -// highlight-end -import { defineBackend } from "@aws-amplify/backend"; -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; -import { storage } from "./storage/resource"; - -// Define backend resources -const backend = defineBackend({ - auth, - data, - storage, -}); - -const todoTable = - backend.data.resources.cfnResources.amplifyDynamoDbTables["Todo"]; - -// Update table settings -todoTable.pointInTimeRecoveryEnabled = true; - -todoTable.streamSpecification = { - streamViewType: dynamodb.StreamViewType.NEW_IMAGE, -}; - -// Get the DynamoDB table ARN -const tableArn = backend.data.resources.tables["Todo"].tableArn; -// Get the DynamoDB table name -const tableName = backend.data.resources.tables["Todo"].tableName; - -// Create the OpenSearch domain -const openSearchDomain = new opensearch.Domain( - backend.data.stack, - "OpenSearchDomain", - { - version: opensearch.EngineVersion.OPENSEARCH_2_11, - capacity: { - // upgrade instance types for production use - masterNodeInstanceType: "t3.small.search", - masterNodes: 0, - dataNodeInstanceType: "t3.small.search", - dataNodes: 1, - }, - nodeToNodeEncryption: true, - // set removalPolicy to DESTROY to make sure the OpenSearch domain is deleted on stack deletion. - removalPolicy: RemovalPolicy.DESTROY, - encryptionAtRest: { - enabled: true, - }, - } -); - -// Get the S3Bucket ARN -const s3BucketArn = backend.storage.resources.bucket.bucketArn; -// Get the S3Bucket Name -const s3BucketName = backend.storage.resources.bucket.bucketName; - -// Create an IAM role for OpenSearch integration -const openSearchIntegrationPipelineRole = new iam.Role( - backend.data.stack, - "OpenSearchIntegrationPipelineRole", - { - assumedBy: new iam.ServicePrincipal("osis-pipelines.amazonaws.com"), - inlinePolicies: { - openSearchPipelinePolicy: new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - actions: ["es:DescribeDomain"], - resources: [ - openSearchDomain.domainArn, - openSearchDomain.domainArn + "/*", - ], - effect: iam.Effect.ALLOW, - }), - new iam.PolicyStatement({ - actions: ["es:ESHttp*"], - resources: [ - openSearchDomain.domainArn, - openSearchDomain.domainArn + "/*", - ], - effect: iam.Effect.ALLOW, - }), - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "s3:GetObject", - "s3:AbortMultipartUpload", - "s3:PutObject", - "s3:PutObjectAcl", - ], - resources: [s3BucketArn, s3BucketArn + "/*"], - }), - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "dynamodb:DescribeTable", - "dynamodb:DescribeContinuousBackups", - "dynamodb:ExportTableToPointInTime", - "dynamodb:DescribeExport", - "dynamodb:DescribeStream", - "dynamodb:GetRecords", - "dynamodb:GetShardIterator", - ], - resources: [tableArn, tableArn + "/*"], - }), - ], - }), - }, - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName( - "AmazonOpenSearchIngestionFullAccess" - ), - ], - } -); - -// Define OpenSearch index mappings -const indexName = "todo"; - -const indexMapping = { - settings: { - number_of_shards: 1, - number_of_replicas: 0, - }, - mappings: { - properties: { - id: { - type: "keyword", - }, - isDone: { - type: "boolean", - }, - content: { - type: "text", - }, - priority: { - type: "text", - }, - }, - }, -}; - -// OpenSearch template definition -const openSearchTemplate = ` -version: "2" -dynamodb-pipeline: - source: - dynamodb: - acknowledgments: true - tables: - - table_arn: "${tableArn}" - stream: - start_position: "LATEST" - export: - s3_bucket: "${s3BucketName}" - s3_region: "${backend.storage.stack.region}" - s3_prefix: "${tableName}/" - aws: - sts_role_arn: "${openSearchIntegrationPipelineRole.roleArn}" - region: "${backend.data.stack.region}" - sink: - - opensearch: - hosts: - - "https://${openSearchDomain.domainEndpoint}" - index: "${indexName}" - index_type: "custom" - template_content: | - ${JSON.stringify(indexMapping)} - document_id: '\${getMetadata("primary_key")}' - action: '\${getMetadata("opensearch_action")}' - document_version: '\${getMetadata("document_version")}' - document_version_type: "external" - bulk_size: 4 - aws: - sts_role_arn: "${openSearchIntegrationPipelineRole.roleArn}" - region: "${backend.data.stack.region}" -`; - -// highlight-start -// Create a CloudWatch log group -const logGroup = new logs.LogGroup(backend.data.stack, "LogGroup", { - logGroupName: "/aws/vendedlogs/OpenSearchService/pipelines/1", - removalPolicy: RemovalPolicy.DESTROY, -}); - -// Create an OpenSearch Integration Service pipeline -const cfnPipeline = new osis.CfnPipeline( - backend.data.stack, - "OpenSearchIntegrationPipeline", - { - maxUnits: 4, - minUnits: 1, - pipelineConfigurationBody: openSearchTemplate, - pipelineName: "dynamodb-integration-2", - logPublishingOptions: { - isLoggingEnabled: true, - cloudWatchLogDestination: { - logGroup: logGroup.logGroupName, - }, - }, - } -); -//highlight-end -``` - -After deploying the resources, you can test the data ingestion process by adding an item to the `Todo` table. However, before doing that, let's verify that the pipeline has been set up correctly. - -In the AWS console, navigate to OpenSearch and then to the pipelines section. You should find your configured pipeline and review its settings to ensure they match your expectations: - -![A screenshot displaying the OpenSearch OSIS pipeline created under the DynamoDB integrations section](/images/gen2/opensearch-integration/OpenSearch_pipeline.png) - -You can also check this in the DynamoDB console by going to the Integrations section of the tables. - -![A screenshot displaying the OpenSearch OSIS pipeline created within the 'Ingestion -> Pipelines' section of the OpenSearch Console.](/images/gen2/opensearch-integration/OpenSearch_DynamoDB_integration.png) - -## Step 4: Expose new queries on OpenSearch - -### Step 4a: Add OpenSearch Datasource to backend - -First, Add the OpenSearch data source to the data backend. Add the following code to the end of the `amplify/backend.ts` file. - -```ts title="amplify/backend.ts" -// Add OpenSearch data source -const osDataSource = backend.data.addOpenSearchDataSource( - "osDataSource", - openSearchDomain -); - -``` -### Step 4b: Create Resolver and attach to query - -Let's create the search resolver. Create a new file named `amplify/data/searchTodoResolver.js` and paste the following code. For additional details please refer to [Amazon OpenSearch Service Resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-elasticsearch-resolvers-js.html) - -```ts title="amplify/data/searchTodoResolver.js" -import { util } from "@aws-appsync/utils"; - -/** - * Searches for documents by using an input term - * @param {import('@aws-appsync/utils').Context} ctx the context - * @returns {*} the request - */ -export function request(ctx) { - return { - operation: "GET", - path: "/todo/_search", - }; -} - -/** - * Returns the fetched items - * @param {import('@aws-appsync/utils').Context} ctx the context - * @returns {*} the result - */ -export function response(ctx) { - if (ctx.error) { - util.error(ctx.error.message, ctx.error.type); - } - return ctx.result.hits.hits.map((hit) => hit._source); -} -``` - -### Step 4c: Add the AppSync Resolver for the Search Query - -Update the schema and add a searchTodo query. - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - done: a.boolean(), - priority: a.enum(["low", "medium", "high"]), - }) - .authorization((allow) => [allow.publicApiKey()]), - - //highlight-start - searchTodos: a - .query() - .returns(a.ref("Todo").array()) - .authorization((allow) => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - entry: "./searchTodoResolver.js", - dataSource: "osDataSource", - }) - ), - //highlight-end - -}); -``` - -Once you've deployed the resources, you can verify the changes by checking the AppSync console. Run the 'searchTodo' query and review the results to confirm their accuracy. - -![AppSync console displaying a generated query for 'searchTodo' with the results fetched from OpenSearch on the right side.](/images/gen2/opensearch-integration/opensearch_appsync_console.png) - ---- - ---- -title: "Connect to Amazon EventBridge to send and receive events" -section: "build-a-backend/data/custom-business-logic" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-08T01:04:46.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-eventbridge-datasource/" ---- - -Amazon EventBridge is a serverless event bus that simplifies how applications communicate with each other. It acts as a central hub for events generated by various sources, including AWS services, custom applications, and third-party SaaS providers. - -EventBridge delivers this event data in real-time, allowing you to build applications that react swiftly to changes. You define rules to filter and route these events to specific destinations, known as targets. Targets can include services like AWS Lambda, Amazon SQS Queues, Amazon SNS Topics. For the purpose of this guide, we will use AWS AppSync as the target for events. - -By adopting an event-driven architecture with EventBridge, you can achieve: - -- **Loose Coupling**: Applications become independent and communicate through events, improving scalability and maintainability. - -- **Increased Resilience**: System failures are isolated as events are delivered asynchronously, ensuring overall application availability. - -- **Simplified Integration**: EventBridge provides a unified interface for integrating diverse event sources, streamlining development. - -This section will guide you through adding an event bus as a datasource to your API, defining routing rules, and configuring targets to build robust event-driven applications with AWS Amplify Gen 2 and Amazon EventBridge. - -1. Set up your API -2. Add your Amazon EventBridge event bus as a data source -3. Define custom queries and mutations -4. Configure custom business logic handler code -5. Invoke custom mutations to send events to EventBridge -6. Subscribe to mutations invoked by EventBridge -7. Invoke mutations and trigger subscriptions from EventBridge - -## Step 1 - Set up your API - -For the purpose of this guide, we will define an `OrderStatusChange` custom type that represents an order status change event. This type includes fields for the order ID, status, and message. - -In your `amplify/data/resource.ts` file, use the following code to define an `OrderStatusChange` custom type and an `OrderStatus` enum, adding them to your schema: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - .authorization(allow => [allow.publicApiKey()]), - // highlight-start - OrderStatus: a.enum(["OrderPending", "OrderShipped", "OrderDelivered"]), - OrderStatusChange: a.customType({ - orderId: a.id().required(), - status: a.ref("OrderStatus").required(), - message: a.string().required(), - }), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -> **Info:** **NOTE:** At least one query is required for a schema to be valid. Otherwise, deployments will fail a schema error. The Amplify Data schema is auto-generated with a `Todo` model and corresponding queries under the hood. You can leave the `Todo` model in the schema until you add the first custom query to the schema in the next steps. - -## Step 2 - Add your Amazon EventBridge event bus as a data source - -In your `amplify/backend.ts` file, use the following code to add the default event bus as a data source for your API: - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend"; -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; -import { aws_events } from "aws-cdk-lib"; -import { - Effect, - PolicyDocument, - PolicyStatement, - Role, - ServicePrincipal, -} from "aws-cdk-lib/aws-iam"; - -export const backend = defineBackend({ - auth, - data, -}); - -// Create a new stack for the EventBridge data source -const eventStack = backend.createStack("MyExternalDataSources"); - -// Reference or create an EventBridge EventBus -const eventBus = aws_events.EventBus.fromEventBusName( - eventStack, - "MyEventBus", - "default" -); - -// Add the EventBridge data source -// highlight-start -backend.data.addEventBridgeDataSource("MyEventBridgeDataSource", eventBus); -// highlight-end - -// Create a policy statement to allow invoking the AppSync API's mutations -const policyStatement = new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["appsync:GraphQL"], - resources: [`${backend.data.resources.graphqlApi.arn}/types/Mutation/*`], -}); - -// Create a role for the EventBus to assume -const eventBusRole = new Role(eventStack, "AppSyncInvokeRole", { - assumedBy: new ServicePrincipal("events.amazonaws.com"), - inlinePolicies: { - PolicyStatement: new PolicyDocument({ - statements: [policyStatement], - }), - }, -}); - -// Create an EventBridge rule to route events to the AppSync API -const rule = new aws_events.CfnRule(eventStack, "MyOrderRule", { - eventBusName: eventBus.eventBusName, - name: "broadcastOrderStatusChange", - eventPattern: { - source: ["amplify.orders"], - /* The shape of the event pattern must match EventBridge's event message structure. - So, this field must be spelled as "detail-type". Otherwise, events will not trigger the rule. - - https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html - */ - ["detail-type"]: ["OrderStatusChange"], - detail: { - orderId: [{ exists: true }], - status: ["PENDING", "SHIPPED", "DELIVERED"], - message: [{ exists: true }], - }, - }, - targets: [ - { - id: "orderStatusChangeReceiver", - arn: backend.data.resources.cfnResources.cfnGraphqlApi - .attrGraphQlEndpointArn, - roleArn: eventBusRole.roleArn, - appSyncParameters: { - graphQlOperation: ` - mutation PublishOrderFromEventBridge( - $orderId: String! - $status: String! - $message: String! - ) { - publishOrderFromEventBridge(orderId: $orderId, status: $status, message: $message) { - orderId - status - message - } - }`, - }, - inputTransformer: { - inputPathsMap: { - orderId: "$.detail.orderId", - status: "$.detail.status", - message: "$.detail.message", - }, - inputTemplate: JSON.stringify({ - orderId: "", - status: "", - message: "", - }), - }, - }, - ], -}); -``` - -> **Warning:** The selection set returned by the mutation must match the selection set of the subscription. If the selection set of the mutation is different from the selection set of the subscription, the subscription will not receive the event. - -In the code snippet above, the `addEventBridgeDataSource` method is used to add the default event bus as a data source to your API. This allows you to reference the event bus in your custom queries and mutations. - -The `CfnRule` construct is used to create an EventBridge rule that routes events to the AppSync API. The rule specifies the event pattern to match and the target to invoke when the event is received. In this example, the target is an AppSync mutation named `publishOrderFromEventBridge`. - -The `appSyncParameters` property specifies the mutation to invoke when the event is received. The `inputTransformer` property maps the event data to the mutation arguments. - -## Step 3 - Define custom queries and mutations - -Now that your event bus has been added as a data source, you can reference it in custom queries and mutations using the `a.handler.custom()` modifier which accepts the name of the data source and an entry point for your resolver. - -Use the following code to add `publishOrderToEventBridge` and `publishOrderFromEventBridge` custom mutations, and an `onOrderStatusChange` custom subscription to your schema: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - // ... - OrderStatus: a.enum(["OrderPending", "OrderShipped", "OrderDelivered"]), - OrderStatusChange: a.customType({ - orderId: a.id().required(), - status: a.ref("OrderStatus").required(), - message: a.string().required(), - }), - // highlight-start - publishOrderToEventBridge: a - .mutation() - .arguments({ - orderId: a.id().required(), - status: a.string().required(), - message: a.string().required(), - }) - .returns(a.ref("OrderStatusChange")) - .authorization((allow) => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - dataSource: "EventBridgeDataSource", - entry: "./publishOrderToEventBridge.js", - }) - ), - publishOrderFromEventBridge: a - .mutation() - .arguments({ - orderId: a.id().required(), - status: a.string().required(), - message: a.string().required(), - }) - .returns(a.ref("OrderStatusChange")) - .authorization((allow) => [allow.publicApiKey(), allow.guest()]) - .handler( - a.handler.custom({ - entry: "./publishOrderFromEventBridge.js", - }) - ), - onOrderFromEventBridge: a - .subscription() - .for(a.ref("publishOrderFromEventBridge")) - .authorization((allow) => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - entry: "./onOrderFromEventBridge.js", - }) - ), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - name: "MyLibrary", - authorizationModes: { - defaultAuthorizationMode: "apiKey", - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -In the code snippet above: - -- The `publishOrderToEventBridge` custom mutation uses an EventBridge data source and so it is able to publish events to the event bus from its resolver. - -- The `publishOrderFromEventBridge` custom mutation uses a None data source as a passthrough and is invoked by the EventBridge rule when an event is received that matches the rule pattern. The `allow.guest` rule uses IAM under the hood and allows the mutation to be invoked by the EventBridge rule. - -- The `onOrderFromEventBridge` custom subscription can be triggered either by EventBridge invoking the `publishOrderFromEventBridge` mutation or by a client invoking the `publishOrderToEventBridge` mutation. - -## Step 4 - Configure custom business logic handler code - -Next, create the following files in your `amplify/data` folder and use the code examples to define custom resolvers for the custom queries and mutations added to your schema from the previous step. These are AppSync JavaScript resolvers - -#### [Subscription] - -The following code defines the custom business logic handler for the `onOrderStatusChange` subscription. Since the subscription uses a None data source the `response` function is empty as the subscription does not require any additional processing. - -```js title="amplify/data/onOrderStatusChange.js" -export function request(ctx) { - return { - payload: {}, - }; -} - -export function response(ctx) { -} -``` - -#### [Publish Order to EventBridge] - -In the following code, the `request` function constructs the event payload to be published to the event bus. To match the rule pattern configured in the previous steps, the event source is set to `amplify.orders` and the `detail-type` is set to `OrderStatusChange`. The mutation arguments are passed to the event detail. - -```js title="amplify/data/publishOrderToEventBridge.js" -export function request(ctx) { - return { - operation: "PutEvents", - events: [ - { - source: "amplify.orders", - ["detail-type"]: "OrderStatusChange", - detail: { ...ctx.args }, - }, - ], - }; -} - -export function response(ctx) { - return ctx.args; -} -``` - -#### [Publish Order From EventBridge] - -The following code defines the custom business logic handler for the `publishOrderFromEventBridge` mutation. The `request` function constructs the mutation arguments from the event payload received from the event bus. The `response` function returns the mutation arguments. - -```js title="amplify/data/publishOrderFromEventBridge.js" -export function request(ctx) { - return { - payload: ctx.arguments, - }; -} - -export function response(ctx) { - return ctx.arguments; -} -``` - -## Step 5 - Invoke custom mutations to send events to EventBridge - -From your generated Data client, you can find all your custom queries and mutations under the `client.queries` and `client.mutations` APIs respectively. - -The custom mutation below will publish an order status change event to the event bus: - -```ts title="App.tsx" -await client.mutations.publishOrderToEventBridge({ - orderId: "12345", - status: "SHIPPED", - message: "Order has been shipped", -}); -``` - -## Step 6 - Subscribe to mutations invoked by EventBridge - -To subscribe to events from your event bus, you can use the `client.subscriptions` API: - -```ts title="App.tsx" -// Subscribe to the mutations triggered by the EventBridge rule -const sub = client.subscriptions.onOrderStatusChange().subscribe({ - next: (data) => { - console.log(data); - }, -}); - -//... - -// Clean up subscription -sub.unsubscribe(); -``` - -## Step 7 - Invoke a mutation and trigger a subscription from EventBridge - -You can test your custom mutation and subscriptions by using the EventBridge console to send an event which will invoke the custom mutation. You can then observe the results from the subscription being triggered: - -1. Navigate to the Amazon EventBridge console and choose "Send Events" - -![Amazon EventBridge console, page titled β€œEvent buses”. Shows a table of event buses and a highlighted button labeled "Send events."](/images/send-events.png) - -2. Fill out the form, specifying the event source to be `amplify.orders` and the `detail-type` to be `OrderStatusChange`. - -![Amazon EventBridge console, page titled "Send events". Shows a form with input fields and values of "event bus: default", "event source: amplify.orders", "detail type: OrderStatusChange", and an Event detail field with JSON data containing an "orderId", "status", and "message". ](/images/send-events-2.png) - -3. Choose "Send" and observe the subscription output in the AppSync Queries console. - -![AppSync console, page titled "Queries". Shows a running subscription named "onOrderFromEventBridge" and a result with data containing an "orderId", "status", and "message." ](/images/send-events-3.png) - -## Conclusion - -In this guide, you’ve added an Amazon EventBridge event bus as a data source to an Amplify API and defined custom queries and mutations to publish and receive events from the event bus. You’ve also configured custom business logic handler code to handle the event data and invoke the appropriate mutations. - -To clean up, you can delete your sandbox by accepting the prompt when terminating the sandbox process in your terminal. Alternatively, you can also use the AWS Amplify console to manage and delete sandbox environments. - ---- - ---- -title: "Connect to Amazon Polly for Text-To-Speech APIs" -section: "build-a-backend/data/custom-business-logic" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-14T20:52:33.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-amazon-polly/" ---- - -Amazon Polly is a text-to-speech (TTS) service offered by Amazon Web Services (AWS). It uses advanced deep learning technologies to convert written text into lifelike speech, enabling you to create applications with speech capabilities in various languages and voices. - -With Amazon Polly, you can easily add voice interactions and accessibility features to your applications. The service supports a wide range of use cases, such as providing audio content for the visually impaired, enhancing e-learning experiences, creating interactive voice response (IVR) systems, and more. - -Key features of Amazon Polly include: - -- **Multiple Voices and Languages**: Amazon Polly supports dozens of voices across various languages and dialects, giving you the flexibility to choose the most appropriate voice for your use case. - -- **High-Quality Speech**: Amazon Polly's neural and standard voices offer natural and realistic speech quality. - -- **Speech Marks and Speech Synthesis Markup Language**: Customize your speech output with Speech Synthesis Markup Language tags and obtain speech timing information with speech marks. - -- **Scalable and Cost-Effective**: Amazon Polly's pay-as-you-go pricing model makes it a cost-effective solution for adding speech capabilities to your applications. - -In this section, you'll learn how to integrate Amazon Polly into your application using AWS Amplify, enabling you to leverage its powerful text-to-speech capabilities seamlessly. - -## Step 1 - Setup the project - -Set up your project by following the instructions in the [Quickstart guide](/[platform]/start/quickstart/). - -## Step 2 - Install Polly Libraries -We'll create a new API endpoint that'll use the the AWS SDK to call the Amazon Polly service. To install the Amazon Polly SDK, run the following command in your project's root folder: - -```bash title="Terminal" showLineNumbers={false} -npm add @aws-sdk/client-polly -``` - -## Step 3 - Setup Storage - -Create a file named `amplify/storage/resource.ts` and add the following content to configure a storage resource: - -```ts title="amplify/storage/resource.ts" -import { defineStorage } from '@aws-amplify/backend'; - -export const storage = defineStorage({ - name: 'predictions_gen2' -}); - -``` - -## Step 4 - Configure IAM Roles - - To access Amazon Polly service, you need to configure the proper IAM policy for Lambda to utilize the desired feature effectively. Update the `amplify/backend.ts` file with the following code to add the necessary permissions to a lambda's Role policy. - - ```ts title= "amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend"; -import { auth } from "./auth/resource"; -import { data, convertTextToSpeech } from "./data/resource"; -import { Stack } from "aws-cdk-lib"; -import { PolicyStatement } from "aws-cdk-lib/aws-iam"; -import { storage } from "./storage/resource"; - -const backend = defineBackend({ - auth, - data, - storage, - convertTextToSpeech, -}); - -backend.convertTextToSpeech.resources.lambda.addToRolePolicy( - new PolicyStatement({ - actions: ["polly:StartSpeechSynthesisTask"], - resources: ["*"], - }) -); -``` -## Step 5 - Configure the function handler - -Define the function handler by creating a new file, `amplify/data/convertTextToSpeech.ts`. This function converts text into speech using Amazon Polly and stores the synthesized speech as an MP3 file in an S3 bucket. - -```ts title="amplify/data/convertTextToSpeech.ts" -import { Schema } from "./resource"; -import { - PollyClient, - StartSpeechSynthesisTaskCommand, -} from "@aws-sdk/client-polly"; -import { env } from "$amplify/env/convertTextToSpeech"; - -export const handler: Schema["convertTextToSpeech"]["functionHandler"] = async ( - event -) => { - const client = new PollyClient(); - const task = new StartSpeechSynthesisTaskCommand({ - OutputFormat: "mp3", - SampleRate: "8000", - Text: event.arguments.text, - TextType: "text", - VoiceId: "Amy", - OutputS3BucketName: env.PREDICTIONS_GEN_2_BUCKET_NAME, - OutputS3KeyPrefix: "public/", - }); - const result = await client.send(task); - - return ( - result.SynthesisTask?.OutputUri?.replace( - "https://s3.us-east-1.amazonaws.com/" + - env.PREDICTIONS_GEN_2_BUCKET_NAME + - "/public/", - "" - ) ?? "" - ); -}; -``` - -## Step 6 - Define the custom mutation and function - -In your `amplify/data/resource.ts` file, define the function using defineFunction and then reference the function with your mutation using a.handler.function() as a handler. - -```ts title="amplify/data/resource.ts" -import { - type ClientSchema, - a, - defineData, - defineFunction, -} from "@aws-amplify/backend"; - -export const convertTextToSpeech = defineFunction({ - entry: "./convertTextToSpeech.ts", -}); - -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - .authorization(allow => [allow.publicApiKey()]), - convertTextToSpeech: a - .mutation() - .arguments({ - text: a.string().required(), - }) - .returns(a.string().required()) - .authorization(allow => [allow.publicApiKey()]) - .handler(a.handler.function(convertTextToSpeech)), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "apiKey", - // API Key is used for allow.publicApiKey() rules - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -> **Info:** **NOTE:** At least one query is required for a schema to be valid. Otherwise, deployments will fail a schema error. The Amplify Data schema is auto-generated with a `Todo` model and corresponding queries under the hood. You can leave the `Todo` model in the schema until you add the first custom query to the schema in the next steps. - -## Step 7 - Update Storage permissions - -Customize your storage settings to manage access to various paths within your storage bucket. It's necessary to update the Storage resource to provide access to the `convertTextToSpeech` resource. Modify the file `amplify/storage/resource.ts` as shown below. - -```ts title="amplify/storage/resource.ts" -import { defineStorage } from "@aws-amplify/backend"; -import { convertTextToSpeech } from "../data/resource"; - -export const storage = defineStorage({ - name: "predictions_gen2", - access: (allow) => ({ - "public/*": [ - allow.resource(convertTextToSpeech).to(["write"]), - allow.guest.to(["read", "write"]), - ], - }), -}); -``` - -## Step 8 - Configure the frontend - -Import and load the configuration file in your app. It's recommended you add the Amplify configuration step to your app's root entry point. -``` ts title="main.tsx" -import { Amplify } from "aws-amplify"; -import outputs from "../amplify_outputs.json"; - -Amplify.configure(outputs); -``` -### Invoke the API - -Example frontend code to create an audio buffer for playback using a text input. - - -```ts title="App.tsx" -import "./App.css"; -import { generateClient } from "aws-amplify/api"; -import type { Schema } from "../amplify/data/resource"; -import { getUrl } from "aws-amplify/storage"; -import { useState } from "react"; - -const client = generateClient(); - -type PollyReturnType = Schema["convertTextToSpeech"]["returnType"]; - -function App() { - const [src, setSrc] = useState(""); - const [file, setFile] = useState(""); - return ( -
    - - - Get audio file -
    - ); -} - -export default App; -``` - - - -```ts title="app.component.ts" -import type { Schema } from '../../../amplify/data/resource'; -import { Component } from '@angular/core'; -import { generateClient } from 'aws-amplify/api'; -import { getUrl } from 'aws-amplify/storage'; - -const client = generateClient(); - -type PollyReturnType = Schema['convertTextToSpeech']['returnType']; - -@Component({ - selector: 'app-root', - template: ` -
    - - - Get audio file -
    - `, - styleUrls: ['./app.component.css'], -}) -export class App { - src: string = ''; - file: PollyReturnType = ''; - - async synthesize() { - const { data, errors } = await client.mutations.convertTextToSpeech({ - text: 'Hello World!', - }); - - if (!errors && data) { - this.file = data; - } else { - console.log(errors); - } - } - - async fetchAudio() { - const res = await getUrl({ - path: 'public/' + this.file, - }); - - this.src = res.url.toString(); - } -} -``` - - ---- - ---- -title: "Connect to Amazon Bedrock for generative AI use cases" -section: "build-a-backend/data/custom-business-logic" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-14T20:52:33.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-bedrock/" ---- - -[Amazon Bedrock](https://aws.amazon.com/bedrock/) is a fully managed service that removes the complexity of using foundation models (FMs) for generative AI development. It acts as a central hub, offering a curated selection of high-performing FMs from leading AI companies like Anthropic, AI21 Labs, Cohere, and Amazon itself. - -Amazon Bedrock streamlines generative AI development by providing: - -- **Choice and Flexibility**: Experiment and evaluate a wide range of FMs to find the perfect fit for your use case. - -- **Simplified Integration**: Access and use FMs through a single, unified API, reducing development time. - -- **Enhanced Security and Privacy**: Benefit from built-in safeguards to protect your data and prevent misuse. - -- **Responsible AI Features**: Implement guardrails to control outputs and mitigate bias. - -In the following sections, we walk through the steps to add Amazon Bedrock to your API as a data source and connect to it from your Amplify app: - -1. Add Amazon Bedrock as a data source -2. Define a custom query -3. Configure custom business logic handler code -4. Invoke a custom query to prompt a generative AI model - -## Step 1 - Add Amazon Bedrock as a data source - -To connect to Amazon Bedrock as a data source, you can choose between two methods - using a Lambda function or a custom resolver powered by AppSync JavaScript resolvers. The following steps demonstrate both methods: - -#### [Function] - -In your `amplify/backend.ts` file, replace the content with the following code to add a lambda function to your backend and grant it permission to invoke a generative AI model in Amazon Bedrock. The `generateHaikuFunction` lambda function will be defined in and exported from the `amplify/data/resource.ts` file in the next steps: - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend"; -import { auth } from "./auth/resource"; -import { data, MODEL_ID, generateHaikuFunction } from "./data/resource"; -import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam"; - -export const backend = defineBackend({ - auth, - data, - generateHaikuFunction, -}); - -backend.generateHaikuFunction.resources.lambda.addToRolePolicy( - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["bedrock:InvokeModel"], - resources: [ - `arn:aws:bedrock:*::foundation-model/${MODEL_ID}`, - ], - }) -); -``` - -#### [Custom resolver powered by AppSync JavaScript resolvers] - -In your `amplify/backend.ts` file, replace the content with the following code to add an HTTP data source for Amazon Bedrock to your API and grant it permissions to invoke a generative AI model: - -```ts title="amplify/backend.ts" -import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam"; -import { defineBackend } from "@aws-amplify/backend"; -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; - -export const backend = defineBackend({ - auth, - data, -}); - -const MODEL_ID = "anthropic.claude-3-haiku-20240307-v1:0"; - -const bedrockDataSource = backend.data.addHttpDataSource( - "BedrockDataSource", - "https://bedrock-runtime.us-east-1.amazonaws.com", - { - authorizationConfig: { - signingRegion: backend.data.stack.region, - signingServiceName: "bedrock", - }, - } -); - -bedrockDataSource.grantPrincipal.addToPrincipalPolicy( - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["bedrock:InvokeModel"], - resources: [ - `arn:aws:bedrock:${backend.data.stack.region}::foundation-model/${MODEL_ID}`, - ], - }) -); - -backend.data.resources.cfnResources.cfnGraphqlApi.environmentVariables = { - MODEL_ID -} -``` - -For the purpose of this guide, we will use Anthropic's Claude 3 Haiku to generate content. If you want to use a different model, you can find the ID for your model of choice in the Amazon Bedrock documentation's [list of model IDs](https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html) or the [Amazon Bedrock console](https://console.aws.amazon.com/bedrock/) and replace the value of `MODEL_ID`. - -> **Info:** The availability of Amazon Bedrock and its foundation models may vary by region. -> -> The policy statement in the code above assumes that your Amplify app is deployed in a region supported by Amazon Bedrock and the Claude 3 Haiku model. If you are deploying your app in a region where Amazon Bedrock is not available, update the code above accordingly. -> -> For a list of supported regions please refer to the [Amazon Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.html). - -## Step 2 - Define a custom query - -#### [Function] -Next, replace the contents of your `amplify/data/resource.ts` file with the following code. This will define and export a lambda function that was granted permission to invoke a generative AI model in Amazon Bedrock in the previous step. A custom query named `generateHaiku` is added to the schema with the `generateHaikuFunction` as the handler using the `a.handler.function()` modifier: - -```ts title="amplify/data/resource.ts" -import { - type ClientSchema, - a, - defineData, - defineFunction, -} from "@aws-amplify/backend"; - -export const MODEL_ID = "anthropic.claude-3-haiku-20240307-v1:0"; - -export const generateHaikuFunction = defineFunction({ - entry: "./generateHaiku.ts", - environment: { - MODEL_ID, - }, -}); - -const schema = a.schema({ - generateHaiku: a - .query() - .arguments({ prompt: a.string().required() }) - .returns(a.string()) - .authorization((allow) => [allow.publicApiKey()]) - .handler(a.handler.function(generateHaikuFunction)), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "apiKey", - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -#### [Custom resolver powered by AppSync JavaScript resolvers] -With Amazon Bedrock added as a data source, you can reference it in custom queries using the `a.handler.custom()` modifier which accepts the name of the data source and an entry point for your resolvers. Replace the contents of your `amplify/data/resource.ts` file with the following code to define a custom query named `generateHaiku` in the schema: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - generateHaiku: a - .query() - .arguments({ prompt: a.string().required() }) - .returns(a.string()) - .authorization((allow) => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - dataSource: "BedrockDataSource", - entry: "./generateHaiku.js", - }) - ), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "apiKey", - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -## Step 3 - Configure custom business logic handler code - -#### [Function] -Next, create a `generateHaiku.ts` file in your `amplify/data` folder and use the following code to define a custom resolver for the custom query added to your schema in the previous step: - -The following code uses the `BedrockRuntimeClient` from the `@aws-sdk/client-bedrock-runtime` package to invoke the generative AI model in Amazon Bedrock. The `handler` function takes the user prompt as an argument, invokes the model, and returns the generated haiku. - -```ts title="amplify/data/generateHaiku.ts" -import type { Schema } from "./resource"; -import { - BedrockRuntimeClient, - InvokeModelCommand, - InvokeModelCommandInput, -} from "@aws-sdk/client-bedrock-runtime"; - -// initialize bedrock runtime client -const client = new BedrockRuntimeClient(); - -export const handler: Schema["generateHaiku"]["functionHandler"] = async ( - event, - context -) => { - // User prompt - const prompt = event.arguments.prompt; - - // Invoke model - const input = { - modelId: process.env.MODEL_ID, - contentType: "application/json", - accept: "application/json", - body: JSON.stringify({ - anthropic_version: "bedrock-2023-05-31", - system: - "You are a an expert at crafting a haiku. You are able to craft a haiku out of anything and therefore answer only in haiku.", - messages: [ - { - role: "user", - content: [ - { - type: "text", - text: prompt, - }, - ], - }, - ], - max_tokens: 1000, - temperature: 0.5, - }), - } as InvokeModelCommandInput; - - const command = new InvokeModelCommand(input); - - const response = await client.send(command); - - // Parse the response and return the generated haiku - const data = JSON.parse(Buffer.from(response.body).toString()); - - return data.content[0].text; -}; -``` - -#### [Custom resolver powered by AppSync JavaScript resolvers] -Next, create a `generateHaiku.js` file in your `amplify/data` folder and use the following code to define a custom resolver for the custom query added to your schema in the previous step: - -The following code defines a `request` function that constructs the HTTP request to invoke the generative AI model in Amazon Bedrock. The `response` function parses the response and returns the generated haiku. - -```js title="amplify/data/generateHaiku.js" -export function request(ctx) { - - // Define a system prompt to give the model a persona - const system = - "You are a an expert at crafting a haiku. You are able to craft a haiku out of anything and therefore answer only in haiku."; - - const prompt = ctx.args.prompt - - // Construct the HTTP request to invoke the generative AI model - return { - resourcePath: `/model/${ctx.env.MODEL_ID}/invoke`, - method: "POST", - params: { - headers: { - "Content-Type": "application/json", - }, - body: { - anthropic_version: "bedrock-2023-05-31", - system, - messages: [ - { - role: "user", - content: [ - { - type: "text", - text: prompt, - }, - ], - }, - ], - max_tokens: 1000, - temperature: 0.5, - }, - }, - }; -} - -// Parse the response and return the generated haiku -export function response(ctx) { - const res = JSON.parse(ctx.result.body); - const haiku = res.content[0].text; - - return haiku; -} -``` - -The code above uses the [Messages API](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html), which is supported by chat models such as Anthropic's Claude 3 Haiku. - -The `system` prompt is used to give the model a persona or directives to follow, and the `messages` array can contain a history of messages. The `max_tokens` parameter controls the maximum number of tokens the model can generate, and the `temperature` parameter determines the randomness, or creativity, of the generated response. - -## Step 4 - Invoke a custom query to prompt a generative AI model - -From your generated Data client, you can find all your custom queries and mutations under the `client.queries` and `client.mutations` APIs respectively. - -The custom query below will prompt a generative AI model to create a haiku based on the given prompt. Replace the `prompt` value with your desired prompt text or user input and invoke the query as shown below: - -```ts title="App.tsx" -const { data, errors } = await client.queries.generateHaiku({ - prompt: "Frank Herbert's Dune", -}); -``` - -Here's an example of a simple UI that prompts a generative AI model to create a haiku based on user input: - - -```tsx title="App.tsx" -import type { Schema } from '@/amplify/data/resource'; -import type { FormEvent } from 'react'; -import { useState } from 'react'; -import { Amplify } from 'aws-amplify'; -import { generateClient } from 'aws-amplify/api'; -import outputs from '@/amplify_outputs.json'; - -Amplify.configure(outputs); - -const client = generateClient(); - -export default function App() { - const [prompt, setPrompt] = useState(''); - const [answer, setAnswer] = useState(null); - - const sendPrompt = async (event: FormEvent) => { - event.preventDefault(); - - const { data, errors } = await client.queries.generateHaiku({ - prompt - }); - - if (!errors) { - setAnswer(data); - setPrompt(''); - } else { - console.log(errors); - } - }; - - return ( -
    -
    -

    Haiku Generator

    -
    - setPrompt(event.target.value)} - /> -
    -
    -
    {answer}
    -
    -
    -
    - ); -} -``` - - - -```ts title="app.component.ts" -import type { Schema } from '../../../amplify/data/resource'; -import { Component } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { Amplify } from 'aws-amplify'; -import { generateClient } from 'aws-amplify/api'; -import outputs from '../../../amplify_outputs.json'; - -Amplify.configure(outputs); - -const client = generateClient(); - -@Component({ - selector: 'app-haiku', - standalone: true, - imports: [FormsModule], - template: ` -
    -
    -

    Haiku Generator

    -
    - -
    -
    -
    {{ answer }}
    -
    -
    -
    - `, -}) -export class HaikuComponent { - prompt: string = ''; - answer: string | null = null; - - async sendPrompt() { - const { data, errors } = await client.queries.generateHaiku({ - prompt: this.prompt, - }); - - if (!errors) { - this.answer = data; - this.prompt = ''; - } else { - console.log(errors); - } - } -} -``` - - -![A webpage titled "Haiku Generator" and input field. "Frank Herbert's Dune" is entered and submitted. Shortly after, a haiku is rendered to the page.](/images/haiku-generator.gif) - -## Conclusion - -In this guide, you learned how to connect to Amazon Bedrock from your Amplify app. By adding Bedrock as a data source, defining a custom query, configuring custom business logic handler code, and invoking custom queries, you can leverage the power of generative AI models in your application. - -To clean up, you can delete your sandbox by accepting the prompt when terminating the sandbox process in your terminal. Alternatively, you can also use the AWS Amplify console to manage and delete sandbox environments. - ---- - ---- -title: "Connect to Amazon Rekognition for Image Analysis APIs" -section: "build-a-backend/data/custom-business-logic" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-04-25T18:09:07.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-amazon-rekognition/" ---- - -**Amazon Rekognition** is an advanced machine learning service provided by Amazon Web Services (AWS), allowing developers to incorporate image and video analysis into their applications. It uses state-of-the-art machine learning models to analyze images and videos, providing valuable insights such as object and scene detection, text recognition, face analysis, and more. - -Key features of Amazon Rekognition include: - -- **Object and Scene Detection**: Amazon Rekognition can identify thousands of objects and scenes in images and videos, providing valuable context for your media content. - -- **Text Detection and Recognition**: The service can detect and recognize text within images and videos, making it an invaluable tool for applications requiring text extraction. - -- **Facial Analysis**: Amazon Rekognition offers accurate facial analysis, enabling you to detect, analyze, and compare faces in images and videos. - -- **Facial Recognition**: You can build applications with the capability to recognize and verify individuals using facial recognition. - -- **Content Moderation**: Amazon Rekognition can analyze images and videos to identify inappropriate or objectionable content, helping you maintain safe and compliant content. - -In this section, you will learn how to integrate Amazon Rekognition into your application using AWS Amplify, leveraging its powerful image analysis capabilities seamlessly. - -## Step 1 - Set up the project - -Set up your project by following the instructions in the [Quickstart guide](/[platform]/start/quickstart/). - -## Step 2 - Install Rekognition Libraries -Create a new API endpoint that'll use the the AWS SDK to call the Amazon Rekognition service. To install the Amazon Rekognition SDK, run the following command in your project's root folder: - -```bash title="Terminal" showLineNumbers={false} -npm add @aws-sdk/client-rekognition -``` - -## Step 3 - Setup Storage - -Create a file named `amplify/storage/resource.ts` and add the following content to configure a storage resource: - -```ts title="amplify/storage/resource.ts" -import { defineStorage } from '@aws-amplify/backend'; - -export const storage = defineStorage({ - name: 'predictions_gen2' -}); - -``` - -## Step 4 - Add your Amazon Rekognition as Datasource - -To use the Amazon Rekognition service, you need to add Amazon Rekognition as an HTTP Data Source and configure the proper IAM policy for Lambda to effectively utilize the desired feature and grant permission to access the storage. In this case, you can add the `rekognition:DetectText` and `rekognition:DetectLabels` actions to the policy. Update the `amplify/backend.ts` file as shown below. - - ```ts title= "amplify/backend.ts" -import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; -import { defineBackend } from '@aws-amplify/backend'; -import { auth } from './auth/resource'; -import { data } from './data/resource'; -import { storage } from './storage/resource'; - -const backend = defineBackend({ - auth, - data, - storage -}); - -// Set environment variables for the S3 Bucket name -backend.data.resources.cfnResources.cfnGraphqlApi.environmentVariables = { - S3_BUCKET_NAME: backend.storage.resources.bucket.bucketName, -}; - -const rekognitionDataSource = backend.data.addHttpDataSource( - "RekognitionDataSource", - `https://rekognition.${backend.data.stack.region}.amazonaws.com`, - { - authorizationConfig: { - signingRegion: backend.data.stack.region, - signingServiceName: "rekognition", - }, - } -); - -rekognitionDataSource.grantPrincipal.addToPrincipalPolicy( - new PolicyStatement({ - actions: ["rekognition:DetectText", "rekognition:DetectLabels"], - resources: ["*"], - }) -); - -backend.storage.resources.bucket.grantReadWrite( - rekognitionDataSource.grantPrincipal -); - -``` -## Step 5 - Configure the function handler - -Define the function handler by creating a new file, `amplify/data/identifyText.js`. This function analyzes the image and extracts text using the Amazon Rekognition DetectText service. - -```ts title="amplify/data/identifyText.js" - -export function request(ctx) { - return { - method: "POST", - resourcePath: "/", - params: { - body: { - Image: { - S3Object: { - Bucket: ctx.env.S3_BUCKET_NAME, - Name: ctx.arguments.path, - }, - }, - }, - headers: { - "Content-Type": "application/x-amz-json-1.1", - "X-Amz-Target": "RekognitionService.DetectText", - }, - }, - }; -} - -export function response(ctx) { - return JSON.parse(ctx.result.body) - .TextDetections.filter((item) => item.Type === "LINE") - .map((item) => item.DetectedText) - .join("\n") - .trim(); -} - -``` - -## Step 6 - Define the custom query - -After adding Amazon Rekognition as a data source, you can reference it in custom query using the `a.handler.custom()` modifier, which takes the name of the data source and an entry point for your resolvers. In your `amplify/data/resource.ts` file, specify `RekognitionDataSource` as the data source and `identifyText.js` as the entry point, as shown below. - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - identifyText: a - .query() - .arguments({ - path: a.string(), - }) - .returns(a.string()) - .authorization((allow) => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - entry: "./identifyText.js", - dataSource: "RekognitionDataSource", - }) - ), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "apiKey", - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` -## Step 7 - Update Storage permissions - -Customize your storage settings to manage access to various paths within your storage bucket. Modify the file `amplify/storage/resource.ts` as shown below. - -```ts title="amplify/storage/resource.ts" -import { defineStorage } from "@aws-amplify/backend" - -export const storage = defineStorage({ - name: "predictions_gen2", - access: allow => ({ - 'public/*': [ - allow.guest.to(['list', 'write', 'get']) - ] - }) -}) -``` - -## Step 8 - Configure the frontend - -Import and load the configuration file in your app. It's recommended you add the Amplify configuration step to your app's root entry point. - -``` ts title="main.tsx" -import { Amplify } from "aws-amplify"; -import outputs from "../amplify_outputs.json"; - -Amplify.configure(outputs); - -``` -### Invoke the Text Recognition API - -This code sets up a React app to upload an image to an S3 bucket and then use Amazon Rekognition to recognize the text in the uploaded image. - - -```ts title="App.tsx" -import { type ChangeEvent, useState } from "react"; -import { generateClient } from "aws-amplify/api"; -import { uploadData } from "aws-amplify/storage"; -import { Schema } from "@/amplify/data/resource"; -import "./App.css"; - -// Generating the client -const client = generateClient(); - -type IdentifyTextReturnType = Schema["identifyText"]["returnType"]; - -function App() { - // State to hold the recognized text - const [path, setPath] = useState(""); - const [textData, setTextData] = useState(); - - // Function to handle file upload to S3 bucket - const handleTranslate = async (event: ChangeEvent) => { - if (event.target.files) { - const file = event.target.files[0]; - - const s3Path = "public/" + file.name; - - try { - uploadData({ - path: s3Path, - data: file, - }); - - setPath(s3Path); - } catch (error) { - console.error(error); - } - } - }; - - // Function to recognize text from the uploaded image - const recognizeText = async () => { - // Identifying text in the uploaded image - const { data } = await client.queries.identifyText({ - path, // File name - }); - setTextData(data); - }; - - return ( -
    -

    Amazon Rekognition Text Recognition

    -
    - - -
    -

    Recognized Text:

    - {textData} -
    -
    -
    - ); -} - -export default App; -``` - - -```ts title="app.component.ts" -import type { Schema } from '../../../amplify/data/resource'; -import { Component } from '@angular/core'; -import { generateClient } from 'aws-amplify/api'; -import { uploadData } from 'aws-amplify/storage'; -import { CommonModule } from '@angular/common'; - -// Generating the client -const client = generateClient(); - -type IdentifyTextReturnType = Schema['identifyText']['returnType']; - -@Component({ - selector: 'app-text-recognition', - standalone: true, - imports: [CommonModule], - template: ` -
    -

    Amazon Rekognition Text Recognition

    -
    - - -
    -

    Recognized Text:

    - {{ textData }} -
    -
    -
    - `, -}) -export class TodosComponent { - // Component properties instead of React state - path: string = ''; - textData?: IdentifyTextReturnType; - - // Function to handle file upload to S3 bucket - async handleTranslate(event: Event) { - const target = event.target as HTMLInputElement; - if (target.files && target.files.length > 0) { - const file = target.files[0]; - const s3Path = 'public/' + file.name; - - try { - await uploadData({ - path: s3Path, - data: file, - }); - - this.path = s3Path; - } catch (error) { - console.error(error); - } - } - } - - // Function to recognize text from the uploaded image - async recognizeText() { - // Identifying text in the uploaded image - const { data } = await client.queries.identifyText({ - path: this.path, // File name - }); - this.textData = data; - } -} -``` - - ---- - ---- -title: "Connect to Amazon Translate for language translation APIs" -section: "build-a-backend/data/custom-business-logic" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-10-15T16:15:13.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-amazon-translate/" ---- - -Amazon Translate is a neural machine translation service provided by Amazon Web Services (AWS). It uses advanced deep learning technologies to deliver fast and high-quality language translation. With Amazon Translate, you can easily add multilingual support to your applications and services, enabling users to communicate and interact in their preferred language. - -Key features of Amazon Translate include: - -- **Accurate and Fluent Translations**: Amazon Translate produces translations that are both accurate and natural-sounding, providing a seamless experience for users. - -- **Support for Multiple Languages**: The service supports a broad range of languages, allowing you to expand your application’s reach to diverse audiences around the world. - -- **Real-Time and Batch Translation**: Amazon Translate can handle real-time translation for dynamic content and batch translation for larger volumes of text, making it suitable for various use cases. - -- **Cost-Effective and Scalable**: With its pay-as-you-go pricing model and automatic scaling, Amazon Translate is an economical and flexible solution for adding translation capabilities to your applications. - -In this section, you will learn how to integrate Amazon Translate into your application using AWS Amplify, enabling you to leverage its powerful translation capabilities effortlessly. - -## Step 1 - Set up the project - -Set up your project by following the instructions in the [Quickstart guide](/[platform]/start/quickstart/). - -## Step 2 - Install Amazon Translate libraries -To install the Amazon Translate SDK, run the following command in your project's root folder: - -```bash title="Terminal" showLineNumbers={false} -npm add @aws-sdk/client-translate -``` - -## Step 3 - Add your Amazon Translate as Datasource - - To access Amazon Translate service, you need to add Amazon Translate as an HTTP Data Source and configure the proper IAM policy for AWS Lambda to utilize the desired feature effectively. Update `amplify/backend.ts` file as shown below. - - ```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; -import { auth } from './auth/resource'; -import { data } from './data/resource'; - -const backend = defineBackend({ - auth, - data -}); - -const translateDataSource = backend.data.addHttpDataSource( - "TranslateDataSource", - `https://translate.${backend.data.stack.region}.amazonaws.com`, - { - authorizationConfig: { - signingRegion: backend.data.stack.region, - signingServiceName: "translate", - }, - } -); - -translateDataSource.grantPrincipal.addToPrincipalPolicy( - new PolicyStatement({ - actions: ["translate:TranslateText"], - resources: ["*"], - }) -); -``` -## Step 4 - Configure custom business logic handler - -Next, create the following `translate.js` file in your `amplify/data` folder and use the code below to define custom resolvers. - -```ts title="amplify/data/translate.js" - -export function request(ctx) { - return { - method: 'POST', - resourcePath: '/', - params: { - body: { - SourceLanguageCode: ctx.arguments.sourceLanguage, - TargetLanguageCode: ctx.arguments.targetLanguage, - Text: ctx.arguments.text - }, - headers: { - 'Content-Type': 'application/x-amz-json-1.1', - 'X-Amz-Target': 'AWSShineFrontendService_20170701.TranslateText' - } - }, - } -} - -export function response(ctx) { - return JSON.parse(ctx.result.body).TranslatedText -} - -``` - -## Step 5 - Define the custom query - -After adding Amazon Translate as a data source, you can reference it in a custom query using the `a.handler.custom()` modifier, which takes the name of the data source and an entry point for your resolvers. In your `amplify/data/resource.ts` file, specify `TranslateDataSource` as the data source and `translate.js` as the entry point, as shown below. - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - translate: a.query() - .arguments({ - sourceLanguage: a.string().required(), - targetLanguage: a.string().required(), - text: a.string().required() - }) - .returns(a.string()) - .authorization(allow => [allow.publicApiKey()]) - .handler(a.handler.custom({ - dataSource: "TranslateDataSource", - entry: './translate.js' - })) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); - -``` - -## Step 6 - Configure the frontend - -Import and load the configuration file in your app. It's recommended you add the Amplify configuration step to your app's root entry point. - -``` ts title="main.tsx" -import { Amplify } from "aws-amplify"; -import outputs from "../amplify_outputs.json"; - -Amplify.configure(outputs); -``` -### Invoke the API - -Sample frontend code to translate text from one language to another. - -```ts -import { generateClient } from 'aws-amplify/data'; -import { type Schema } from '../amplify/data/resource'; - -const client = generateClient(); - -const { data } = await client.queries.translate({ - sourceLanguage: "en", - targetLanguage: "es", - text: "Hello World!", -}); -``` - ---- - ---- -title: "Connect to an external HTTP endpoint" -section: "build-a-backend/data/custom-business-logic" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-06T18:52:05.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-http-datasource/" ---- - -The HTTP Datasource allows you to quickly configure HTTP resolvers within your Data API. - -This guide will demonstrate how to establish a connection to an external REST API using an HTTP data source and use Amplify Data's custom mutations and queries to interact with the REST API. - -## Step 1 - Set up your custom type - -For the purpose of this guide we will define a `Post` type and use an existing external REST API that will store records for it. In Amplify Gen 2, `customType` adds a type to the schema that is not backed by an Amplify-generated DynamoDB table. - -With the `Post` type defined, it can then be referenced as the return type when defining your custom queries and mutations. - -First, add the `Post` custom type to your schema: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - .authorization(allow => [allow.publicApiKey()]), - // highlight-start - Post: a.customType({ - title: a.string(), - content: a.string(), - author: a.string().required(), - }), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -## Step 2 - Add your REST API or HTTP API as Datasource - -To integrate the external REST API or HTTP API, you'll need to set it up as the HTTP Datasource. Add the following code in your `amplify/backend.ts` file. - -```ts title="amplify/backend.ts" - -import { defineBackend } from "@aws-amplify/backend"; -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; - -const backend = defineBackend({ - auth, - data, -}); - -const httpDataSource = backend.data.addHttpDataSource( - "HttpDataSource", - "https://www.example.com" -); - -``` -## Step 3 - Define custom queries and mutations - -Now that your REST API has been added as a data source, you can reference it in custom queries and mutations using the `a.handler.custom()` modifier which accepts the name of the data source and an entry point for your resolvers. - -Use the following code examples to add `addPost`, `getPost`, `updatePost`, and `deletePost` as custom queries and mutations to your schema: - -#### [addPost] -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Post: a.customType({ - title: a.string(), - content: a.string(), - author: a.string().required(), - }), - // highlight-start - addPost: a - .mutation() - .arguments({ - title: a.string(), - content: a.string(), - author: a.string().required(), - }) - .returns(a.ref("Post")) - .authorization(allow => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - dataSource: "HttpDataSource", - entry: "./addPost.js", - }) - ), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -#### [getPost] -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Post: a.customType({ - title: a.string(), - content: a.string(), - author: a.string().required(), - }), - // highlight-start - getPost: a - .query() - .arguments({ id: a.id().required() }) - .returns(a.ref("Post")) - .authorization(allow => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - dataSource: "HttpDataSource", - entry: "./getPost.js", - }) - ), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -#### [updatePost] -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Post: a.customType({ - title: a.string(), - content: a.string(), - author: a.string().required(), - }), - // highlight-start - updatePost: a - .mutation() - .arguments({ - id: a.id().required(), - title: a.string(), - content: a.string(), - author: a.string(), - }) - .returns(a.ref("Post")) - .authorization(allow => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - dataSource: "HttpDataSource", - entry: "./updatePost.js", - }) - ), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -#### [deletePost] -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Post: a.customType({ - title: a.string(), - content: a.string(), - author: a.string().required(), - }), - // highlight-start - deletePost: a - .mutation() - .arguments({ id: a.id().required() }) - .returns(a.ref("Post")) - .authorization(allow => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - dataSource: "HttpDataSource", - entry: "./deletePost.js", - }) - ), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -## Step 4 - Configure custom business logic handler code - -Next, create the following files in your `amplify/data` folder and use the code examples to define custom resolvers for the custom queries and mutations added to your schema from the previous step. These are AppSync JavaScript resolvers. - -#### [addPost] -```js title="amplify/data/addPost.js" -import { util } from "@aws-appsync/utils"; - -export function request(ctx) { - return { - method: "POST", - resourcePath: "/post", - params: { - headers: { - "Content-Type": "application/json", - }, - body: { - title: ctx.arguments.title, - content: ctx.arguments.content, - author: ctx.arguments.author, - }, - }, - }; -} - -export function response(ctx) { - if (ctx.error) { - return util.error(ctx.error.message, ctx.error.type); - } - if (ctx.result.statusCode == 200) { - return JSON.parse(ctx.result.body).data; - } else { - return util.appendError(ctx.result.body, "ctx.result.statusCode"); - } -} -``` - -#### [getPost] -```js title="amplify/data/getPost.js" -import { util } from "@aws-appsync/utils"; - -export function request(ctx) { - return { - method: "GET", - resourcePath: "/posts/" + ctx.arguments.id, - params: { - headers: { - "Content-Type": "application/json", - }, - }, - }; -} - -export function response(ctx) { - if (ctx.error) { - return util.error(ctx.error.message, ctx.error.type); - } - if (ctx.result.statusCode == 200) { - return JSON.parse(ctx.result.body).data; - } else { - return util.appendError(ctx.result.body, "ctx.result.statusCode"); - } -} -``` - -#### [updatePost] -```js title="amplify/data/updatePost.js" -import { util } from "@aws-appsync/utils"; - -export function request(ctx) { - return { - method: "POST", - resourcePath: "/posts/" + ctx.arguments.id, - params: { - headers: { - "Content-Type": "application/json", - }, - body: { - title: ctx.arguments.title, - content: ctx.arguments.content, - author: ctx.arguments.author, - }, - }, - }; -} - -export function response(ctx) { - if (ctx.error) { - return util.error(ctx.error.message, ctx.error.type); - } - if (ctx.result.statusCode == 200) { - return JSON.parse(ctx.result.body).data; - } else { - return util.appendError(ctx.result.body, "ctx.result.statusCode"); - } -} - -``` - -#### [deletePost] -```js title="amplify/data/deletePost.js" -import { util } from "@aws-appsync/utils"; - -export function request(ctx) { - return { - method: "DELETE", - resourcePath: "/posts/" + ctx.arguments.id, - params: { - headers: { - "Content-Type": "application/json", - }, - }, - }; -} - -export function response(ctx) { - if (ctx.error) { - return util.error(ctx.error.message, ctx.error.type); - } - if (ctx.result.statusCode == 200) { - return JSON.parse(ctx.result.body).data; - } else { - return util.appendError(ctx.result.body, "ctx.result.statusCode"); - } -} -``` - -## Step 5 - Invoke custom queries or mutations - -From your generated Data client, you can find all your custom queries and mutations under the client.queries. and client.mutations. APIs respectively. - -#### [addPost] -```ts title="App.tsx" -const { data, errors } = await client.mutations.addPost({ - title: "My Post", - content: "My Content", - author: "Chris", -}); -``` - -#### [getPost] -```ts title="App.tsx" -const { data, errors } = await client.queries.getPost({ - id: "" -}); -``` - -#### [updatePost] -```ts title="App.tsx" -const { data, errors } = await client.mutations.updatePost({ - id: "", - title: "An Updated Post", -}); -``` - -#### [deletePost] -```ts title="App.tsx" -const { data, errors } = await client.mutations.deletePost({ - id: "", -}); -``` - -## Conclusion - -In this guide, you’ve added an external REST API as a HTTP data source to an Amplify Data API and defined custom queries and mutations, handled by AppSync JS resolvers, to manipulate Post items in an external REST API using the Amplify Gen 2 Data client. - -To clean up, you can delete your sandbox by accepting the prompt when terminating the sandbox process in your terminal. Alternatively, you can also use the AWS Amplify console to manage and delete sandbox environments. - ---- - ---- -title: "Batch DynamoDB Operations" -section: "build-a-backend/data/custom-business-logic" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-13T16:03:51.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/batch-ddb-operations/" ---- - -Batch DynamoDB operations allow you to add multiple items in single mutation. - -## Step 1 - Define a custom mutation - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - // 1. Define your return type as a custom type or model - Post: a.model({ - id: a.id(), - content: a.string(), - likes: a.integer() - }), - - // 2. Define your mutation with the return type and, optionally, arguments - BatchCreatePost: a - .mutation() - // arguments that this query accepts - .arguments({ - content: a.string().array() - }) - .returns(a.ref('Post').array()) - // only allow signed-in users to call this API - .authorization(allow => [allow.authenticated()]) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema -}); -``` - -## Step 2 - Configure custom business logic handler code - -After your query or mutation is defined, you need to author your custom business logic using a [custom resolver powered by AppSync JavaScript resolver](https://docs.aws.amazon.com/appsync/latest/devguide/tutorials-js.html). - -Custom resolvers work on a "request/response" basis. You choose a data source, map your request to the data source's input parameters, and then map the data source's response back to the query/mutation's return type. Custom resolvers provide the benefit of no cold starts, less infrastructure to manage, and no additional charge for Lambda function invocations. Review [Choosing between custom resolver and function](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-overview-js.html#choosing-data-source). - -In your `amplify/data/resource.ts` file, define a custom handler using `a.handler.custom`. - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - Post: a.model({ - id: a.id(), - content: a.string(), - likes: a.integer() - }), - - BatchCreatePost: a - .mutation() - .arguments({ - contents: a.string().array() - }) - .returns(a.ref('Post').array()) - .authorization(allow => [allow.authenticated()]) - // 1. Add the custom handler - .handler( - a.handler.custom({ - dataSource: a.ref('Post'), - entry: './BatchCreatePostHandler.js', - }) - ) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema -}); -``` - -Amplify will store some values in the resolver context stash that can be accessed in the custom resolver. - -| Name | Description | -| ------------------------- | -------------------------------------------- | -| awsAppsyncApiId | The ID of the AppSync API. | -| amplifyApiEnvironmentName | The Amplify api environment name. (`NONE` in sandbox) | - -The Amplify generated DynamoDB table names can be constructed from the variables in the context stash. The table name is in the format `--`. For example, the table name for the `Post` model would be `Post-123456-dev` where `123456` is the AppSync API ID and `dev` is the Amplify API environment name. - -```ts title="amplify/data/BatchCreatePostHandler.js" -import { util } from '@aws-appsync/utils'; - -export function request(ctx) { - var now = util.time.nowISO8601(); - - return { - operation: 'BatchPutItem', - tables: { - [`Post-${ctx.stash.awsAppsyncApiId}-${ctx.stash.amplifyApiEnvironmentName}`]: ctx.args.contents.map((content) => - util.dynamodb.toMapValues({ - content, - id: util.autoId(), - createdAt: now, - updatedAt: now, - }) - ), - }, - }; -} - -export function response(ctx) { - if (ctx.error) { - util.error(ctx.error.message, ctx.error.type); - } - return ctx.result.data[`Post-${ctx.stash.awsAppsyncApiId}-${ctx.stash.amplifyApiEnvironmentName}`]; -} -``` - -## Step 3 - Invoke the custom query or mutation - -From your generated Data client, you can find all your custom queries and mutations under the `client.queries.` and `client.mutations.` APIs respectively. - -```ts -const { data, errors } = await client.mutations.BatchCreatePost({ - contents: ['Post 1', 'Post 2', 'Post 3'] -}); -``` - ---- - ---- -title: "Working with files/attachments" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-14T20:52:33.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/working-with-files/" ---- - -The Storage and GraphQL API categories can be used together to associate a file, such as an image or video, with a particular record. For example, you might create a `User` model with a profile picture, or a `Post` model with an associated image. With Amplify's GraphQL API and Storage categories, you can reference the file within the model itself to create an association. - -## Set up the project - -Set up your project by following the instructions in the [Quickstart guide](/[platform]/start/quickstart/). - -## Define the model - -Open `amplify/data/resource.ts` and add the following model as shown below: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Song: a - .model({ - id: a.id().required(), - name: a.string().required(), - coverArtPath: a.string(), - }) - .authorization((allow) => [allow.publicApiKey()]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "apiKey", - - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` -## Setup the Storage - -Next, Let's configure Storage and allow access to all authenticated(signed in) users of your application. create a file `amplify/storage/resource.ts` and add the following code,This will restrict file access to only the signed-in user. - -```ts title="amplify/storage/resource.ts" - -import { defineStorage } from "@aws-amplify/backend"; - -export const storage = defineStorage({ - name: "amplify-gen2-files", - access: (allow) => ({ - "images/*": [allow.authenticated.to(["read", "write", "delete"])], - }), -}); -``` - -Configure the storage in the `amplify/backend.ts` file as demonstrated below: - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend"; -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; -import { storage } from "./storage/resource"; - -export const backend = defineBackend({ - auth, - data, - storage, -}); -``` - -## Configuring authorization - -Your application needs authorization credentials for reading and writing to both Storage and the Data, except in the case where all data and files are intended to be publicly accessible. - -The Storage and Data categories govern data access based on their own authorization patterns, meaning that it's necessary to configure appropriate auth roles for each individual category. Although both categories share the same access credentials set up through the Auth category, they work independently from one another. For instance, adding an `allow.authenticated()` to the Data does not guard against file access in the Storage category. Likewise, adding authorization rules to the Storage category does not guard against data access in the API. - -When you configure Storage, Amplify will configure appropriate IAM policies on the bucket using a Cognito Identity Pool role. You will then have the option of adding CRUD (Create, Update, Read and Delete) based permissions as well, so that Authenticated and Guest users will be granted limited permissions within these levels. **Even after adding this configuration, all Storage access is still `guest` by default.** To guard against accidental public access, the Storage access levels must either be configured on the Storage object globally, or set within individual function calls. This guide uses the former approach, setting all Storage access to `authenticated` users. - -The ability to independently configure authorization rules for each category allows for more granular control over data access, and adds greater flexibility. For scenarios where authorization patterns must be mixed and matched, configure the access level on individual Storage function calls. For example, you may want to use `entity_id` CRUD access on an individual Storage function call for files that should only be accessible by the owner (such as personal files), `authenticated` read access to allow all logged in users to view common files (such as images in a shared photo album), and `guest` read access to allow all users to view a file (such as a public profile picture). - -For more details on how to configure Storage authorization levels, see the [Storage documentation](/[platform]/build-a-backend/storage/authorization/). For more on configuring Data authorization, see the [API documentation](/[platform]/build-a-backend/data/customize-authz/). - -## Create a record with an associated file - -You can create a record via the Amplify Data client, upload a file to Storage, and finally update the record to associate it with the uploaded file. Use the following example with the Amplify Data client and Amplify Storage library helpers, `uploadData` and `getUrl`, to create a record and associate it the file with the record. - - - -The API record's `id` is prepended to the Storage file name to ensure uniqueness. If this is excluded, multiple API records could then be associated with the same file path unintentionally. - - - - -```swift title="ContentView" -let song = Song(name: name) - -guard let imageData = artCover.pngData() else { - print("Could not get data from image.") - return -} - -// Create the song record -var createdSong = try await Amplify.API.mutate(request: .create(song)).get() -let coverArtPath = "images/\(createdSong.id)" - -// Upload the art cover image -_ = try await Amplify.Storage.uploadData(path: .fromString(coverArtPath), data: imageData).value - -// Update the song record with the image path -createdSong.coverArtPath = coverArtPath -let updatedSong = try await Amplify.API.mutate(request: .update(createdSong)).get() -``` - - - -```ts title="src/App.tsx" - -import { generateClient } from "aws-amplify/api"; -import { uploadData, getUrl } from "aws-amplify/storage"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -// Create the API record: -const response = await client.models.Song.create({ - name: `My first song`, -}); - -const song = response.data; - -if (!song) return; - -// Upload the Storage file: -const result = await uploadData({ - path: `images/${song.id}-${file.name}`, - data: file, - options: { - contentType: "image/png", // contentType is optional - }, -}).result; - -// Add the file association to the record: -const updateResponse = await client.models.Song.update({ - id: song.id, - coverArtPath: result?.path, -}); - -const updatedSong = updateResponse.data; - -setCurrentSong(updatedSong); - -// If the record has no associated file, we can return early. -if (!updatedSong.coverArtPath) return; - -// Retrieve the file's signed URL: -const signedURL = await getUrl({ path: updatedSong.coverArtPath }); -``` - - -## Add or update a file for an associated record - -To associate a file with a record, update the record with the path returned by the Storage upload. The following example uploads the file using Storage, updates the record with the file's path, then retrieves the signed URL to download the image. If an image is already associated with the record, this will update the record with the new image. - - -```swift title="ContentView" -guard var currentSong = currentSong else { - print("There is no song to associated the image with. Create a Song first.") - return -} -guard let imageData = artCover.pngData() else { - print("Could not get data from UIImage.") - return -} - -let coverArtPath = "images/\(currentSong.id)" - -// Upload the new art image -_ = try await Amplify.Storage.uploadData(path: .fromString(coverArtPath), data: imageData).value - -// Update the song record -currentSong.coverArtPath = coverArtPath -let updatedSong = try await Amplify.API.mutate(request: .update(currentSong)).get() -``` - - - - -```ts title="src/App.tsx" -import { generateClient } from "aws-amplify/api"; -import { uploadData, getUrl } from "aws-amplify/storage"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -// Upload the Storage file: -const result = await uploadData({ - path: `images/${currentSong.id}-${file.name}`, - data: file, - options: { - contentType: "image/png", // contentType is optional - }, -}).result; - -// Add the file association to the record: -const response = await client.models.Song.update({ - id: currentSong.id, - coverArtPath: result?.path, -}); - -const updatedSong = response.data; - -setCurrentSong(updatedSong); - -// If the record has no associated file, we can return early. -if (!updatedSong?.coverArtPath) return; - -// Retrieve the file's signed URL: -const signedURL = await getUrl({ path: updatedSong.coverArtPath }); -``` - - - -## Query a record and retrieve the associated file - -To retrieve the file associated with a record, first query the record, then use Storage to get the signed URL. The signed URL can then be used to download the file, display an image, etc: - -```swift title="ContentView" -// Get the song record -guard let song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { - print("Song may have been deleted, no song by id: ", currentSong.id) - return -} - -// If the record has no associated file, we can return early. -guard let coverArtPath = song.coverArtPath else { - print("Song does not contain cover art") - return -} - -// Download the art cover -print("coverArtPath: ", coverArtPath) -let imageData = try await Amplify.Storage.downloadData(path: .fromString(coverArtPath)).value - -let image = UIImage(data: imageData) -``` - - - -```ts title="src/App.tsx" -import { generateClient } from "aws-amplify/api"; -import { getUrl } from "aws-amplify/storage"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -const response = await client.models.Song.get({ - id: currentSong.id, -}); - -const song = response.data; - -// If the record has no associated file, we can return early. -if (!song?.coverArtPath) return; - -// Retrieve the signed URL: -const signedURL = await getUrl({ path: song.coverArtPath }); -``` - - -## Delete and remove files associated with API records - -There are three common deletion workflows when working with Storage files and the GraphQL API: - -1. Remove the file association, continue to persist both file and record. -2. Remove the record association and delete the file. -3. Delete both file and record. - -### Remove the file association, continue to persist both file and record - -The following example removes the file association from the record, but does not delete the file from S3, nor the record from the database. - - -```swift title="ContentView" -// Get the song record -guard var song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { - print("Song may have been deleted, no song by id: ", currentSong.id) - return -} - -guard song.coverArtPath != nil else { - print("There is no cover art path to remove image association") - return -} - -// Set the association to nil and update it -song.coverArtPath = nil - -let updatedSong = try await Amplify.API.mutate(request: .update(song)).get() -``` - - - -```ts title="src/App.tsx" - -import { generateClient } from "aws-amplify/api"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -const response = await client.models.Song.get({ - id: currentSong.id, -}); - -const song = response.data; - -// If the record has no associated file, we can return early. -if (!song?.coverArtPath) return; - -const updatedSong = await client.models.Song.update({ - id: song.id, - coverArtPath: null, -}); -``` - - -### Remove the record association and delete the file - -The following example removes the file from the record, then deletes the file from S3: - - -```swift title="ContentView" -// Get the song record -guard var song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { - print("Song may have been deleted, no song by id: ", currentSong.id) - return -} - -guard let coverArtPath = song.coverArtPath else { - print("There is no cover art path to remove image association") - return -} - -// Set the association to nil and update it -song.coverArtPath = nil -let updatedSong = try await Amplify.API.mutate(request: .update(song)).get() - -// Remove the image -try await Amplify.Storage.remove(path: .fromString(coverArtPath)) -``` - - - - -```ts title="src/App.tsx" -import { generateClient } from "aws-amplify/api"; -import { remove } from "aws-amplify/storage"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); -const response = await client.models.Song.get({ - id: currentSong.id, -}); -const song = response?.data; - -// If the record has no associated file, we can return early. -if (!song?.coverArtPath) return; - -// Remove associated file from record -const updatedSong = await client.models.Song.update({ - id: song.id, - coverArtPath: null, -}); - -// Delete the file from S3: -await remove({ path: song.coverArtPath }); - -``` - - -### Delete both file and record - - -```swift title="ContentView" -// Get the song record -guard let song = try await Amplify.API.query(request: .get(Song.self, byId: currentSong.id)).get() else { - print("Song may have been deleted, no song by id: ", currentSong.id) - return -} - -if let coverArt = song.coverArtPath { - // Delete the file from S3 - try await Amplify.Storage.remove(path: .fromString(coverArt)) -} - -// Delete the song record -_ = try await Amplify.API.mutate(request: .delete(song)).get() -``` - - - -```ts title="src/App.tsx" -import { generateClient } from "aws-amplify/api"; -import { remove } from "aws-amplify/storage"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); -const response = await client.models.Song.get({ - id: currentSong.id, -}); - -const song = response.data; - -// If the record has no associated file, we can return early. -if (!song?.coverArtPath) return; - -await remove({ path: song.coverArtPath }); - -// Delete the record from the API: -await client.models.Song.delete({ id: song.id }); - -``` - - -## Working with multiple files - -You may want to add multiple files to a single record, such as a user profile with multiple images. To do this, you can add a list of file keys to the record. The following example adds a list of file keys to a record: - -### GraphQL schema to associate a data model with multiple files - -Add the following model in `amplify/data/resource.ts" file. - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - PhotoAlbum: a - .model({ - id: a.id().required(), - name: a.string().required(), - imagePaths: a.string().array(), - }) - .authorization((allow) => [allow.publicApiKey()]), -}); -``` - -CRUD operations when working with multiple files is the same as when working with a single file, with the exception that we are now working with a list of image keys, as opposed to a single image key. - -### Create a record with multiple associated files - -First create a record via the GraphQL API, then upload the files to Storage, and finally add the associations between the record and files. - - -```swift title="ContentView" -// Create the photo album record -let album = PhotoAlbum(name: name) -var createdAlbum = try await Amplify.API.mutate(request: .create(album)).get() - -// Upload the photo album images -let imagePaths = await withTaskGroup(of: String?.self) { group in - for imageData in imagesData { - group.addTask { - let path = "images/\(album.id)-\(UUID().uuidString)" - do { - _ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value - return path - } catch { - print("Failed with error:", error) - return nil - } - } - } - - var imagePaths: [String?] = [] - for await imagePath in group { - imagePaths.append(imagePath) - } - return imagePaths.compactMap { $0 } -} - -// Update the album with the image paths -createdAlbum.imagePaths = imagePaths -let updatedAlbum = try await Amplify.API.mutate(request: .update(createdAlbum)).get() -``` - - - -```ts title="src/App.tsx" -import { generateClient } from "aws-amplify/api"; -import { uploadData, getUrl } from "aws-amplify/storage"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -// Create the API record: -const response = await client.models.PhotoAlbum.create({ - name: `My first photoAlbum`, -}); - -const photoAlbum = response.data.createPhotoAlbum; - -if (!photoAlbum) return; - -// Upload all files to Storage: -const imagePaths = await Promise.all( - Array.from(e.target.files).map(async (file) => { - const result = await uploadData({ - path: `images/${photoAlbum.id}-${file.name}`, - data: file, - options: { - contentType: "image/png", // contentType is optional - }, - }).result; - - return result.path; - }) -); - -const updatePhotoAlbumDetails = { - id: photoAlbum.id, - imagePaths: imagePaths, -}; - -// Add the file association to the record: -const updateResponse = await client.graphql({ - query: mutations.updatePhotoAlbum, - variables: { input: updatePhotoAlbumDetails }, -}); - -const updatedPhotoAlbum = updateResponse.data.updatePhotoAlbum; - -// If the record has no associated file, we can return early. -if (!updatedPhotoAlbum.imageKeys?.length) return; - -// Retrieve signed urls for all files: -const signedUrls = await Promise.all( - updatedPhotoAlbum?.imagePaths.map( - async (path) => await getUrl({ path: path! }) - ) -); -``` - - -### Add new files to an associated record - -To associate additional files with a record, update the record with the paths returned by the Storage uploads. - - -```swift title="ContentView" -// Upload the new photo album image -let path = "images/\(currentAlbum.id)-\(UUID().uuidString)" -_ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value - -// Get the latest album -guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { - print("Album may have been deleted, no album by id: ", currentAlbum.id) - return -} - -guard var imagePaths = album.imagePaths else { - print("Album does not contain images") - await setCurrentAlbum(album) - await setCurrentImages([]) - return -} - -// Add new to the existing paths -imagePaths.append(path) - -// Update the album with the image paths -album.imagePaths = imagePaths -let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() -``` - - - -```ts title="src/App.tsx" - -import { generateClient } from "aws-amplify/api"; -import { uploadData, getUrl } from "aws-amplify/storage"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -// Upload all files to Storage: -const newimagePaths = await Promise.all( - Array.from(e.target.files).map(async (file) => { - const result = await uploadData({ - path: `images/${currentPhotoAlbum.id}-${file.name}`, - data: file, - options: { - contentType: "image/png", // contentType is optional - }, - }).result; - - return result.path; - }) -); - -// Query existing record to retrieve currently associated files: -const queriedResponse = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, -}); - -const photoAlbum = queriedResponse.data; - -if (!photoAlbum?.imagePaths) return; - -// Merge existing and new file paths: -const updatedimagePaths = [...newimagePaths, ...photoAlbum.imagePaths]; - -// Update record with merged file associations: -const response = await client.models.PhotoAlbum.update({ - id: currentPhotoAlbum.id, - imagePaths: updatedimagePaths, -}); - -const updatedPhotoAlbum = response.data; - -// If the record has no associated file, we can return early. -if (!updatedPhotoAlbum?.imageKeys) return; - -// Retrieve signed urls for merged image paths: -const signedUrls = await Promise.all( - updatedPhotoAlbum?.imagePaths.map( - async (path) => await getUrl({ path: path! }) - ) -); -``` - - -### Update the file for an associated record - -Updating a file for an associated record is the same as updating a file for a single file record, with the exception that you will need to update the list of file keys. - -```swift title="ContentView" -// Upload new file to Storage: -let path = "images/\(currentAlbum.id)-\(UUID().uuidString)" - -_ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value - -// Update the album with the image keys -var album = currentAlbum - -if var imagePaths = album.imagePaths { - imagePaths.removeLast() - imagePaths.append(path) - album.imagePaths = imagePaths -} else { - album.imagePaths = [path] -} - -// Update record with updated file associations: -let updateResult = try await Amplify.API.mutate(request: .update(album)).get() -``` - - -```ts title="src/App.tsx" -import { generateClient } from "aws-amplify/api"; -import { uploadData, getUrl } from "aws-amplify/storage"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -// Upload new file to Storage: -const result = await uploadData({ - path: `images/${currentPhotoAlbum.id}-${file.name}`, - data: file, - options: { - contentType: "image/png", // contentType is optional - }, -}).result; - -const newFilePath = result.path; - -// Query existing record to retrieve currently associated files: -const queriedResponse = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, -}); - -const photoAlbum = queriedResponse.data; - -if (!photoAlbum?.imagePaths?.length) return; - -// Retrieve last image path: -const [lastImagePath] = photoAlbum.imagePaths.slice(-1); - -// Remove last file association by path -const updatedimagePaths = [ - ...photoAlbum.imagePaths.filter((path) => path !== lastImagePath), - newFilePath, -]; - -// Update record with updated file associations: -const response = await client.models.PhotoAlbum.update({ - id: currentPhotoAlbum.id, - imagePaths: updatedimagePaths, -}); - -const updatedPhotoAlbum = response.data; - -// If the record has no associated file, we can return early. -if (!updatedPhotoAlbum?.imagePaths) return; - -// Retrieve signed urls for merged image paths: -const signedUrls = await Promise.all( - updatedPhotoAlbum?.imagePaths.map( - async (path) => await getUrl({ path: path! }) - ) -); - -``` - -### Query a record and retrieve the associated files - -To retrieve the files associated with a record, first query the record, then use Storage to retrieve all of the signed URLs. - - -```swift title="ContentView" -// Query the record to get the file paths: -guard let album = try await Amplify.API.query( - request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { - print("Album may have been deleted, no album by id: ", currentAlbum.id) - return -} - -guard let imagePathsOptional = album.imagePaths else { - print("Album does not contain images") - await setCurrentAlbum(album) - await setCurrentImages([]) - return -} - -let imagePaths = imagePathsOptional.compactMap { $0 } - -// Download the photos -let images = await withTaskGroup(of: UIImage?.self) { group in - for path in imagePaths { - group.addTask { - do { - let imageData = try await Amplify.Storage.downloadData(path: .fromString(path)).value - return UIImage(data: imageData) - } catch { - print("Failed with error:", error) - return nil - } - } - } - - var images: [UIImage?] = [] - for await image in group { - images.append(image) - } - return images.compactMap { $0 } -} -``` - - -```ts title="src/App.tsx" -async function getImagesForPhotoAlbum() { -import { generateClient } from "aws-amplify/api"; -import { uploadData, getUrl } from "aws-amplify/storage"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -// Query the record to get the file paths: -const response = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, -}); - -const photoAlbum = response.data; - -// If the record has no associated files, we can return early. -if (!photoAlbum?.imagePaths) return; - -// Retrieve the signed URLs for the associated images: -const signedUrls = await Promise.all( - photoAlbum.imagePaths.map(async (imagePath) => { - if (!imagePath) return; - return await getUrl({ path: imagePath }); - }) -); -} -``` - - -### Delete and remove files associated with API records - -The workflow for deleting and removing files associated with API records is the same as when working with a single file, except that when performing a delete you will need to iterate over the list of file paths and call `Storage.remove()` for each file. - -#### Remove the file association, continue to persist both files and record - - -```swift title="ContentView" -// Get the album record -guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { - print("Album may have been deleted, no album by id: ", currentAlbum.id) - return -} - -guard let imagePaths = album.imagePaths, !imagePaths.isEmpty else { - print("There are no images to remove association") - return -} - -// Set the association to nil and update it -album.imagePaths = nil -let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() -``` - - - -```ts title="src/App.tsx" - -import { generateClient } from "aws-amplify/api"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -const response = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, -}); - -const photoAlbum = response.data; - -// If the record has no associated file, we can return early. -if (!photoAlbum?.imagePaths) return; - -const updatedPhotoAlbum = await client.models.PhotoAlbum.update({ - id: photoAlbum.id, - imagePaths: null, -}); -``` - - -#### Remove the record association and delete the files - - -```swift title="ContentView" -// Get the album record -guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { - print("Album may have been deleted, no album by id: ", currentAlbum.id) - return -} - -guard let imagePathsOptional = album.imagePaths else { - print("Album does not contain images") - await setCurrentAlbum(album) - await setCurrentImages([]) - return -} -let imagePaths = imagePathsOptional.compactMap { $0 } - -// Set the associations to nil and update it -album.imagePaths = nil -let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() - -// Remove the photos -await withTaskGroup(of: Void.self) { group in - for path in imagePaths { - group.addTask { - do { - try await Amplify.Storage.remove(path: .fromString(path)) - } catch { - print("Failed with error:", error) - } - } - } - - for await _ in group { - } -} -``` - - -```ts title="src/App.tsx" -import { generateClient } from "aws-amplify/api"; -import { remove } from "aws-amplify/storage"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -const response = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, -}); - -const photoAlbum = response.data; - -// If the record has no associated files, we can return early. -if (!photoAlbum?.imagePaths) return; - -// Remove associated files from record -const updateResponse = await client.models.PhotoAlbum.update({ - id: photoAlbum.id, - imagePaths: null, // Set the file association to `null` -}); - -const updatedPhotoAlbum = updateResponse.data; - -// Delete the files from S3: -await Promise.all( - photoAlbum?.imagePaths.map(async (imagePath) => { - if (!imagePath) return; - await remove({ path: imagePath }); - }) -); -``` - - -#### Delete record and all associated files - - -```swift title="ContentView" -// Get the album record -guard let album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { - print("Album may have been deleted, no album by id: ", currentAlbum.id) - return -} - -guard let imagePathsOptional = album.imagePaths else { - print("Album does not contain images") - - // Delete the album record - _ = try await Amplify.API.mutate(request: .delete(album)) - - await setCurrentAlbum(nil) - await setCurrentImages([]) - return -} - -let imagePaths = imagePathsOptional.compactMap { $0 } - -// Remove the photos -await withTaskGroup(of: Void.self) { group in - for path in imagePaths { - group.addTask { - do { - try await Amplify.Storage.remove(path: .fromString(path)) - } catch { - print("Failed with error:", error) - } - } - } - - for await _ in group { - } -} - -// Delete the album record -_ = try await Amplify.API.mutate(request: .delete(album)).get() -``` - - - -```ts title="src/App.tsx" - -import { generateClient } from "aws-amplify/api"; -import { remove } from "aws-amplify/storage"; -import type { Schema } from "../amplify/data/resource"; - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -const response = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, -}); - -const photoAlbum = response.data; - -if (!photoAlbum) return; - -await client.models.PhotoAlbum.delete({ - id: photoAlbum.id, -}); - -setCurrentPhotoAlbum(null); - -// If the record has no associated file, we can return early. -if (!photoAlbum?.imagePaths) return; - -await Promise.all( - photoAlbum?.imagePaths.map(async (imagePath) => { - if (!imagePath) return; - await remove({ path: imagePath }); - }) -); - -``` - - -## Data consistency when working with records and files - -The recommended access patterns in these docs attempt to remove deleted files, but favor leaving orphans over leaving records that point to non-existent files. This optimizes for read latency by ensuring clients _rarely_ attempt to fetch a non-existent file from Storage. However, any app that deletes files can inherently cause records _on-device_ to point to non-existent files. - -One example is when we [create an API record, associate the Storage file with that record, and then retrieve the file's signed URL](#create-a-record-with-an-associated-file). "Device A" calls the GraphQL API to create `API_Record_1`, and then associates that record with `First_Photo`. Before "Device A" is about to retrieve the signed URL, "Device B" might query `API_Record_1`, delete `First_Photo`, and update the record accordingly. However, "Device A" is still using the old `API_Record_1`, which is now out-of-date. Even though the shared global state is correctly in sync at every stage, the individual device ("Device A") has an out-of-date record that points to a non-existent file. Similar issues can conceivably occur for updates. Depending on your app, some of these mismatches can be minimized _even more_ with [real-time data / GraphQL subscriptions](/[platform]/build-a-backend/data/subscribe-data/). - -It is important to understand when these mismatches can occur and to add meaningful error handling around these cases. This guide does not include exhaustive error handling, real-time subscriptions, re-querying of outdated records, or attempts to retry failed operations. However, these are all important considerations for a production-level application. - -## Complete examples - - -#### [Main App] -```swift title="AmplifySwiftApp" -import SwiftUI -import Amplify -import AWSAPIPlugin -import AWSCognitoAuthPlugin -import AWSS3StoragePlugin -import Authenticator -import PhotosUI - -@main -struct WorkingWithFilesApp: App { - - init() { - do { - Amplify.Logging.logLevel = .verbose - try Amplify.add(plugin: AWSCognitoAuthPlugin()) - try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels())) - try Amplify.add(plugin: AWSS3StoragePlugin()) - try Amplify.configure(with: .amplifyOutputs) - print("Amplify configured with Auth, API, and Storage plugins") - } catch { - print("Unable to configure Amplify \(error)") - } - } - - var body: some Scene { - WindowGroup { - Authenticator { state in - TabView { - SongView() - .tabItem { - Label("Song", systemImage: "music.note") - } - - PhotoAlbumView() - .tabItem { - Label("PhotoAlbum", systemImage: "photo") - } - } - - } - } - } -} - -struct SignOutButton: View { - var body: some View { - Button("Sign out") { - Task { - await Amplify.Auth.signOut() - } - }.foregroundColor(.black) - } -} - -struct TappedButtonStyle: ButtonStyle { - func makeBody(configuration: Configuration) -> some View { - configuration.label - .padding(10) - .background(configuration.isPressed ? Color.teal.opacity(0.8) : Color.teal) - .foregroundColor(.white) - .clipShape(RoundedRectangle(cornerRadius: 10)) - } -} - -extension Color { - static let teal = Color(red: 45/255, green: 111/255, blue: 138/255) -} - -struct DimmedBackgroundView: View { - var body: some View { - Color.gray.opacity(0.5) - .ignoresSafeArea() - } -} - -struct ImagePicker: UIViewControllerRepresentable { - @Binding var selectedImage: UIImage? - @Environment(\.presentationMode) var presentationMode - - class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { - let parent: ImagePicker - - init(_ parent: ImagePicker) { - self.parent = parent - } - - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { - if let uiImage = info[.originalImage] as? UIImage { - parent.selectedImage = uiImage - } - parent.presentationMode.wrappedValue.dismiss() - } - - func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { - parent.presentationMode.wrappedValue.dismiss() - } - } - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIImagePickerController { - let imagePicker = UIImagePickerController() - imagePicker.delegate = context.coordinator - return imagePicker - } - - func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext) { - } -} - -struct MultiImagePicker: UIViewControllerRepresentable { - @Binding var selectedImages: [UIImage] - - func makeUIViewController(context: Context) -> PHPickerViewController { - var configuration = PHPickerConfiguration() - configuration.filter = .images - configuration.selectionLimit = 0 - - let picker = PHPickerViewController(configuration: configuration) - picker.delegate = context.coordinator - return picker - } - - func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { - // No need for updates in this case - } - - func makeCoordinator() -> Coordinator { - Coordinator(parent: self) - } - - class Coordinator: PHPickerViewControllerDelegate { - private let parent: MultiImagePicker - - init(parent: MultiImagePicker) { - self.parent = parent - } - - func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { - picker.dismiss(animated: true, completion: nil) - DispatchQueue.main.async { - self.parent.selectedImages = [] - } - for result in results { - if result.itemProvider.canLoadObject(ofClass: UIImage.self) { - result.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in - if let image = image as? UIImage { - DispatchQueue.main.async { - self.parent.selectedImages.append(image) - } - } - } - } - } - } - } -} -``` - -#### [Song] -```swift title="SongView" -import SwiftUI -import Amplify - -class SongViewModel: ObservableObject { - - @Published var currentSong: Song? = nil - @Published var currentImage: UIImage? = nil - @Published var isLoading: Bool = false - - // Create a song with an associated image - func createSong(name: String, artCover: UIImage) async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - let song = Song(name: name) - - guard let imageData = artCover.pngData() else { - print("Could not get data from image.") - return - } - - // Create the song record - var createdSong = try await Amplify.API.mutate(request: .create(song)).get() - let coverArtPath = "images/\(createdSong.id)" - - // Upload the art cover image - _ = try await Amplify.Storage.uploadData(path: .fromString(coverArtPath), data: imageData).value - - // Update the song record with the image path - createdSong.coverArtPath = coverArtPath - let updatedSong = try await Amplify.API.mutate(request: .update(createdSong)).get() - - await setCurrentSong(updatedSong) - } - - func getSongAndFile(currentSong: Song, imageData: Data) async throws { - // Get the song record - guard var song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { - print("Song may have been deleted, no song by id: ", currentSong.id) - return - } - - guard let coverArtPath = song.coverArtPath else { - print("There is no cover art path to retrieve image") - return - } - - // Download the art cover - let imageData = try await Amplify.Storage.downloadData(path: .fromString(coverArtPath)).value - - let image = UIImage(data: imageData) - } - - // Add or update an image for an associated record - func updateArtCover(artCover: UIImage) async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - - guard var currentSong = currentSong else { - print("There is no song to associated the image with. Create a Song first.") - return - } - - guard let imageData = artCover.pngData() else { - print("Could not get data from UIImage.") - return - } - - let coverArtPath = "images/\(currentSong.id)" - - // Upload the new art image - _ = try await Amplify.Storage.uploadData(path: .fromString(coverArtPath), data: imageData).value - - // Update the song record - currentSong.coverArtPath = coverArtPath - let updatedSong = try await Amplify.API.mutate(request: .update(currentSong)).get() - - await setCurrentSong(updatedSong) - } - - func refreshSongAndArtCover() async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - guard let currentSong = currentSong else { - print("There is no song to refresh the record and image. Create a song first.") - return - } - await setCurrentSong(nil) - await setCurrentImage(nil) - - // Get the song record - guard let song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { - print("Song may have been deleted, no song by id: ", currentSong.id) - return - } - - guard let coverArtPath = song.coverArtPath else { - print("Song does not contain cover art") - await setCurrentSong(song) - await setCurrentImage(nil) - return - } - - // Download the art cover - let imageData = try await Amplify.Storage.downloadData(path: .fromString(coverArtPath)).value - - let image = UIImage(data: imageData) - - await setCurrentSong(song) - await setCurrentImage(image) - } - - func removeImageAssociationFromSong() async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - guard let currentSong = currentSong else { - print("There is no song to remove art cover from it. Create a song first.") - return - } - - // Get the song record - guard var song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { - print("Song may have been deleted, no song by id: ", currentSong.id) - return - } - - guard song.coverArtPath != nil else { - print("There is no cover art path to remove image association") - return - } - - // Set the association to nil and update it - song.coverArtPath = nil - - let updatedSong = try await Amplify.API.mutate(request: .update(song)).get() - - await setCurrentSong(updatedSong) - } - - func removeImageAssociationAndDeleteImage() async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - guard let currentSong = currentSong else { - print("There is no song to remove art cover from it. Create a song first.") - return - } - - // Get the song record - guard var song = try await Amplify.API.query(request: .get(Song.self, byIdentifier: currentSong.id)).get() else { - print("Song may have been deleted, no song by id: ", currentSong.id) - return - } - - guard let coverArtPath = song.coverArtPath else { - print("There is no cover art path to remove image association") - return - } - - // Set the association to nil and update it - song.coverArtPath = nil - let updatedSong = try await Amplify.API.mutate(request: .update(song)).get() - - // Remove the image - try await Amplify.Storage.remove(path: .fromString(coverArtPath)) - - await setCurrentSong(updatedSong) - await setCurrentImage(nil) - } - - func deleteSongAndArtCover() async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - guard let currentSong = currentSong else { - print("There is no song to delete. Create a song first.") - return - } - - // Get the song record - guard var song = try await Amplify.API.query(request: .get(Song.self, byId: currentSong.id)).get() else { - print("Song may have been deleted, no song by id: ", currentSong.id) - return - } - - if let coverArt = song.coverArtPath { - // Remove the image - try await Amplify.Storage.remove(path: .fromString(coverArt)) - } - - // Delete the song record - _ = try await Amplify.API.mutate(request: .delete(song)).get() - - await setCurrentSong(nil) - await setCurrentImage(nil) - } - - @MainActor - func setCurrentSong(_ song: Song?) { - self.currentSong = song - } - - @MainActor - func setCurrentImage(_ image: UIImage?) { - self.currentImage = image - } - - @MainActor - func setIsLoading(_ isLoading: Bool) { - self.isLoading = isLoading - } -} - -struct SongView: View { - - @State private var isImagePickerPresented = false - @State private var songName: String = "" - - @StateObject var viewModel = SongViewModel() - - var body: some View { - NavigationView { - ZStack { - VStack { - SongInformation() - DisplayImage() - OpenImagePickerButton() - SongNameTextField() - CreateOrUpdateSongButton() - AdditionalOperations() - Spacer() - } - .padding() - .sheet(isPresented: $isImagePickerPresented) { - ImagePicker(selectedImage: $viewModel.currentImage) - } - VStack { - IsLoadingView() - } - } - .navigationBarItems(trailing: SignOutButton()) - } - } - - @ViewBuilder - func SongInformation() -> some View { - if let song = viewModel.currentSong { - Text("Song Id: \(song.id)").font(.caption) - if song.name != "" { - Text("Song Name: \(song.name)").font(.caption) - } - } - } - - @ViewBuilder - func DisplayImage() -> some View { - if let image = viewModel.currentImage { - Image(uiImage: image) - .resizable() - .aspectRatio(contentMode: .fit) - } else { - Text("No Image Selected") - .foregroundColor(.gray) - } - - } - - func OpenImagePickerButton() -> some View { - Button("Select \(viewModel.currentImage != nil ? "a new ": "" )song album cover") { - isImagePickerPresented.toggle() - }.buttonStyle(TappedButtonStyle()) - } - - @ViewBuilder - func SongNameTextField() -> some View { - TextField("\(viewModel.currentSong != nil ? "Update": "Enter") song name", text: $songName) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .multilineTextAlignment(.center) - } - - @ViewBuilder - func CreateOrUpdateSongButton() -> some View { - if viewModel.currentSong == nil, let image = viewModel.currentImage { - Button("Save") { - Task { - try? await viewModel.createSong(name: songName, - artCover: image) - } - } - .buttonStyle(TappedButtonStyle()) - .disabled(viewModel.isLoading) - } else if viewModel.currentSong != nil, let image = viewModel.currentImage { - Button("Update") { - Task { - try? await viewModel.updateArtCover(artCover: image) - } - } - .buttonStyle(TappedButtonStyle()) - .disabled(viewModel.isLoading) - } - } - - @ViewBuilder - func AdditionalOperations() -> some View { - if viewModel.currentSong != nil { - VStack { - Button("Refresh") { - Task { - try? await viewModel.refreshSongAndArtCover() - } - }.buttonStyle(TappedButtonStyle()) - Button("Remove association from song") { - Task { - try? await viewModel.removeImageAssociationFromSong() - } - }.buttonStyle(TappedButtonStyle()) - Button("Remove association and delete image") { - Task { - try? await viewModel.removeImageAssociationAndDeleteImage() - } - }.buttonStyle(TappedButtonStyle()) - Button("Delete song and art cover") { - Task { - try? await viewModel.deleteSongAndArtCover() - } - songName = "" - }.buttonStyle(TappedButtonStyle()) - }.disabled(viewModel.isLoading) - } - } - - @ViewBuilder - func IsLoadingView() -> some View { - if viewModel.isLoading { - ZStack { - DimmedBackgroundView() - ProgressView() - } - } - } -} - -struct SongView_Previews: PreviewProvider { - static var previews: some View { - SongView() - } -} -``` - -#### [Photo Album] -```swift title="PhotoAlbumView" -import SwiftUI -import Amplify -import Photos - -class PhotoAlbumViewModel: ObservableObject { - @Published var currentImages: [UIImage] = [] - @Published var currentAlbum: PhotoAlbum? = nil - @Published var isLoading: Bool = false - - // Create a record with multiple associated files - func createPhotoAlbum(name: String, photos: [UIImage]) async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - - let imagesData = photos.compactMap { $0.pngData() } - guard !imagesData.isEmpty else { - print("Could not get data from [UIImage]") - return - } - - // Create the photo album record - let album = PhotoAlbum(name: name) - var createdAlbum = try await Amplify.API.mutate(request: .create(album)).get() - - // Upload the photo album images - let imagePaths = await withTaskGroup(of: String?.self) { group in - for imageData in imagesData { - group.addTask { - let path = "images/\(album.id)-\(UUID().uuidString)" - do { - _ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value - return path - } catch { - print("Failed with error:", error) - return nil - } - } - } - - var imagePaths: [String?] = [] - for await imagePath in group { - imagePaths.append(imagePath) - } - return imagePaths.compactMap { $0 } - } - - // Update the album with the image paths - createdAlbum.imagePaths = imagePaths - let updatedAlbum = try await Amplify.API.mutate(request: .update(createdAlbum)).get() - - await setCurrentAlbum(updatedAlbum) - } - - // Create a record with a single associated file - func createPhotoAlbum(name: String, photo: UIImage) async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - - guard let imageData = photo.pngData() else { - print("Could not get data from UIImage") - return - } - - // Create the photo album record - let album = PhotoAlbum(name: name) - var createdAlbum = try await Amplify.API.mutate(request: .create(album)).get() - - // Upload the photo album image - let path = "images/\(album.id)-\(UUID().uuidString)" - _ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value - - // Update the album with the image path - createdAlbum.imagePaths = [path] - let updatedAlbum = try await Amplify.API.mutate(request: .update(createdAlbum)).get() - - await setCurrentAlbum(updatedAlbum) - } - - // Add new file to an associated record - func addAdditionalPhotos(_ photo: UIImage) async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - - guard let currentAlbum = currentAlbum else { - print("There is no album to associated the images with. Create an Album first.") - return - } - - guard let imageData = photo.pngData() else { - print("Could not get data from UIImage.") - return - } - - // Upload the new photo album image - let path = "images/\(currentAlbum.id)-\(UUID().uuidString)" - _ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value - - // Get the latest album - guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { - print("Album may have been deleted, no album by id: ", currentAlbum.id) - return - } - - guard var imagePaths = album.imagePaths else { - print("Album does not contain images") - await setCurrentAlbum(album) - await setCurrentImages([]) - return - } - - // Add new to the existing paths - imagePaths.append(path) - - // Update the album with the image paths - album.imagePaths = imagePaths - let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() - - await setCurrentAlbum(updatedAlbum) - } - - func replaceLastImage(_ photo: UIImage) async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - - guard let currentAlbum = currentAlbum else { - print("There is no album to associated the images with. Create an Album first.") - return - } - - guard let imageData = photo.pngData() else { - print("Could not get data from UIImage") - return - } - - // Upload the new photo album image - let path = "images/\(currentAlbum.id)-\(UUID().uuidString)" - _ = try await Amplify.Storage.uploadData(path: .fromString(path), data: imageData).value - - // Update the album with the image paths - var album = currentAlbum - if var imagePaths = album.imagePaths { - imagePaths.removeLast() - imagePaths.append(path) - album.imagePaths = imagePaths - } else { - album.imagePaths = [path] - } - - let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() - - await setCurrentAlbum(updatedAlbum) - } - - // Query a record and retrieve the associated files - func refreshAlbumAndPhotos() async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - guard let currentAlbum = currentAlbum else { - print("There is no album to associate the images with. Create an Album first.") - return - } - - await setCurrentAlbum(nil) - await setCurrentImages([]) - - // Get the song record - guard let album = try await Amplify.API.query( - request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { - print("Album may have been deleted, no album by id: ", currentAlbum.id) - return - } - - guard let imagePathsOptional = album.imagePaths else { - print("Album does not contain images") - await setCurrentAlbum(album) - await setCurrentImages([]) - return - } - - let imagePaths = imagePathsOptional.compactMap { $0 } - - // Download the photos - let images = await withTaskGroup(of: UIImage?.self) { group in - for path in imagePaths { - group.addTask { - do { - let imageData = try await Amplify.Storage.downloadData(path: .fromString(path)).value - return UIImage(data: imageData) - } catch { - print("Failed with error:", error) - return nil - } - } - } - - var images: [UIImage?] = [] - for await image in group { - images.append(image) - } - return images.compactMap { $0 } - } - - await setCurrentAlbum(album) - await setCurrentImages(images) - } - - // Remove the file association - func removeStorageAssociationsFromAlbum() async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - guard let currentAlbum = currentAlbum else { - print("There is no album to associated the images with. Create an Album first.") - return - } - - // Get the album record - guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { - print("Album may have been deleted, no album by id: ", currentAlbum.id) - return - } - - guard let imagePaths = album.imagePaths, !imagePaths.isEmpty else { - print("There are no images to remove association") - return - } - - // Set the association to nil and update it - album.imagePaths = nil - let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() - - await setCurrentAlbum(updatedAlbum) - } - - // Remove the record association and delete the files - func removeStorageAssociationsAndDeletePhotos() async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - - guard let currentAlbum = currentAlbum else { - print("There is no album to associated the images with. Create an Album first.") - return - } - - // Get the album record - guard var album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { - print("Album may have been deleted, no album by id: ", currentAlbum.id) - return - } - - guard let imagePathsOptional = album.imagePaths else { - print("Album does not contain images") - await setCurrentAlbum(album) - await setCurrentImages([]) - return - } - let imagePaths = imagePathsOptional.compactMap { $0 } - - // Set the associations to nil and update it - album.imagePaths = nil - let updatedAlbum = try await Amplify.API.mutate(request: .update(album)).get() - - // Remove the photos - await withTaskGroup(of: Void.self) { group in - for path in imagePaths { - group.addTask { - do { - try await Amplify.Storage.remove(path: .fromString(path)) - } catch { - print("Failed with error:", error) - } - } - } - - for await _ in group { - } - } - - await setCurrentAlbum(updatedAlbum) - await setCurrentImages([]) - } - - // Delete record and all associated files - func deleteAlbumAndPhotos() async throws { - await setIsLoading(true) - defer { - Task { - await setIsLoading(false) - } - } - - guard let currentAlbum = currentAlbum else { - print("There is no album to associated the images with. Create an Album first.") - return - } - - // Get the album record - guard let album = try await Amplify.API.query(request: .get(PhotoAlbum.self, byId: currentAlbum.id)).get() else { - print("Album may have been deleted, no album by id: ", currentAlbum.id) - return - } - - guard let imagePathsOptional = album.imagePaths else { - print("Album does not contain images") - - // Delete the album record - _ = try await Amplify.API.mutate(request: .delete(album)) - - await setCurrentAlbum(nil) - await setCurrentImages([]) - return - } - - let imagePaths = imagePathsOptional.compactMap { $0 } - - // Remove the photos - await withTaskGroup(of: Void.self) { group in - for path in imagePaths { - group.addTask { - do { - try await Amplify.Storage.remove(path: .fromString(path)) - } catch { - print("Failed with error:", error) - } - } - } - - for await _ in group { - } - } - - // Delete the album record - _ = try await Amplify.API.mutate(request: .delete(album)).get() - - await setCurrentAlbum(nil) - await setCurrentImages([]) - } - - @MainActor - func setCurrentAlbum(_ album: PhotoAlbum?) { - self.currentAlbum = album - } - - @MainActor - func setCurrentImages(_ images: [UIImage]) { - self.currentImages = images - } - - @MainActor - func setIsLoading(_ isLoading: Bool) { - self.isLoading = isLoading - } -} - -struct PhotoAlbumView: View { - @State private var isImagePickerPresented: Bool = false - @State private var albumName: String = "" - @State private var isLastImagePickerPresented = false - @State private var lastImage: UIImage? = nil - @StateObject var viewModel = PhotoAlbumViewModel() - - var body: some View { - NavigationView { - ZStack { - VStack { - AlbumInformation() - DisplayImages() - OpenImagePickerButton() - PhotoAlbumNameTextField() - CreateOrUpdateAlbumButton() - AdditionalOperations() - } - .padding() - .sheet(isPresented: $isImagePickerPresented) { - MultiImagePicker(selectedImages: $viewModel.currentImages) - } - .sheet(isPresented: $isLastImagePickerPresented) { - ImagePicker(selectedImage: $lastImage) - } - VStack { - IsLoadingView() - } - } - .navigationBarItems(trailing: SignOutButton()) - } - } - - @ViewBuilder - func AlbumInformation() -> some View { - if let album = viewModel.currentAlbum { - Text("Album Id: \(album.id)").font(.caption) - if album.name != "" { - Text("Album Name: \(album.name)").font(.caption) - } - } - } - - @ViewBuilder - func DisplayImages() -> some View { - // Display selected images - ScrollView(.horizontal) { - HStack { - ForEach($viewModel.currentImages, id: \.self) { image in - Image(uiImage: image.wrappedValue) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 100, height: 100) - } - } - } - if $viewModel.currentImages.isEmpty { - Text("No Images Selected") - .foregroundColor(.gray) - } - } - - func OpenImagePickerButton() -> some View { - // Button to open the image picker - Button("Select \(!viewModel.currentImages.isEmpty ? "new " : "")photo album images") { - isImagePickerPresented.toggle() - }.buttonStyle(TappedButtonStyle()) - } - - @ViewBuilder - func PhotoAlbumNameTextField() -> some View { - TextField("\(viewModel.currentAlbum != nil ? "Update": "Enter") album name", text: $albumName) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .multilineTextAlignment(.center) - } - - @ViewBuilder - func CreateOrUpdateAlbumButton() -> some View { - if viewModel.currentAlbum == nil, !viewModel.currentImages.isEmpty { - Button("Save") { - Task { - try? await viewModel.createPhotoAlbum(name: albumName, - photos: viewModel.currentImages) - } - } - .buttonStyle(TappedButtonStyle()) - .disabled(viewModel.isLoading) - } else if viewModel.currentAlbum != nil { - Button("Select \(lastImage != nil ? "another ": "")photo to replace last photo in the album") { - isLastImagePickerPresented.toggle() - } - .buttonStyle(TappedButtonStyle()) - .disabled(viewModel.isLoading) - - if let lastImage = lastImage { - Image(uiImage: lastImage) - .resizable() - .aspectRatio(contentMode: .fit) - Button("Replace last image in album with above") { - Task { - try? await viewModel.replaceLastImage(lastImage) - self.lastImage = nil - try? await viewModel.refreshAlbumAndPhotos() - } - } - .buttonStyle(TappedButtonStyle()) - .disabled(viewModel.isLoading) - Button("Append above image to album") { - Task { - try? await viewModel.addAdditionalPhotos(lastImage) - self.lastImage = nil - try? await viewModel.refreshAlbumAndPhotos() - } - } - .buttonStyle(TappedButtonStyle()) - .disabled(viewModel.isLoading) - } - } - } - - @ViewBuilder - func AdditionalOperations() -> some View { - if viewModel.currentAlbum != nil { - VStack { - Button("Refresh") { - Task { - try? await viewModel.refreshAlbumAndPhotos() - } - }.buttonStyle(TappedButtonStyle()) - Button("Remove associations from album") { - Task { - try? await viewModel.removeStorageAssociationsFromAlbum() - try? await viewModel.refreshAlbumAndPhotos() - } - }.buttonStyle(TappedButtonStyle()) - Button("Remove association and delete photos") { - Task { - try? await viewModel.removeStorageAssociationsAndDeletePhotos() - try? await viewModel.refreshAlbumAndPhotos() - } - }.buttonStyle(TappedButtonStyle()) - Button("Delete album and images") { - Task { - try? await viewModel.deleteAlbumAndPhotos() - } - albumName = "" - }.buttonStyle(TappedButtonStyle()) - }.disabled(viewModel.isLoading) - } - } - - @ViewBuilder - func IsLoadingView() -> some View { - if viewModel.isLoading { - ZStack { - DimmedBackgroundView() - ProgressView() - } - } - } -} - -struct PhotoAlbumView_Previews: PreviewProvider { - static var previews: some View { - PhotoAlbumView() - } -} -``` - - - - - -#### [Single File (TS)] - -```ts title="src/App.tsx" - -import "./App.css"; -import { generateClient } from "aws-amplify/api"; -import { uploadData, getUrl, remove } from "aws-amplify/storage"; -import React, { useState } from "react"; -import type { Schema } from "../amplify/data/resource"; -import "@aws-amplify/ui-react/styles.css"; -import { - type WithAuthenticatorProps, - withAuthenticator, -} from "@aws-amplify/ui-react"; -import { Amplify } from "aws-amplify"; -import outputs from "../amplify_outputs.json"; - -Amplify.configure(outputs); - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -type Song = Schema["Song"]["type"]; - -function App({ signOut, user }: WithAuthenticatorProps) { - - const [currentSong, setCurrentSong] = useState(null); - - // Used to display image for current song: - const [currentImageUrl, setCurrentImageUrl] = useState< - string | null | undefined - >(""); - - async function createSongWithImage(e: React.ChangeEvent) { - if (!e.target.files) return; - const file = e.target.files[0]; - try { - - // Create the API record: - const response = await client.models.Song.create({ - name: `My first song`, - }); - - const song = response.data; - - if (!song) return; - - // Upload the Storage file: - const result = await uploadData({ - path: `images/${song.id}-${file.name}`, - data: file, - options: { - contentType: "image/png", // contentType is optional - }, - }).result; - - // Add the file association to the record: - const updateResponse = await client.models.Song.update({ - id: song.id, - coverArtPath: result?.path, - }); - - const updatedSong = updateResponse.data; - setCurrentSong(updatedSong); - - // If the record has no associated file, we can return early. - if (!updatedSong?.coverArtPath) return; - - // Retrieve the file's signed URL: - const signedURL = await getUrl({ path: updatedSong.coverArtPath }); - - setCurrentImageUrl(signedURL.url.toString()); - } catch (error) { - console.error("Error create song / file:", error); - } - } - - // Upload image, add to song, retrieve signed URL and retrieve the image. - // Also updates image if one already exists. - async function addNewImageToSong(e: React.ChangeEvent) { - - if (!currentSong) return; - - if (!e.target.files) return; - - const file = e.target.files[0]; - - try { - // Upload the Storage file: - const result = await uploadData({ - path: `images/${currentSong.id}-${file.name}`, - data: file, - options: { - contentType: "image/png", // contentType is optional - }, - }).result; - - // Add the file association to the record: - const response = await client.models.Song.update({ - id: currentSong.id, - coverArtPath: result?.path, - }); - - const updatedSong = response.data; - - setCurrentSong(updatedSong); - - // If the record has no associated file, we can return early. - if (!updatedSong?.coverArtPath) return; - - // Retrieve the file's signed URL: - const signedURL = await getUrl({ path: updatedSong.coverArtPath }); - setCurrentImageUrl(signedURL.url.toString()); - - } catch (error) { - console.error("Error uploading image / adding image to song: ", error); - } - } - - async function getImageForCurrentSong() { - if (!currentSong) return; - - try { - // Query the record to get the file path: - const response = await client.models.Song.get({ - id: currentSong.id, - }); - - const song = response.data; - - // If the record has no associated file, we can return early. - if (!song?.coverArtPath) return; - - // Retrieve the signed URL: - const signedURL = await getUrl({ path: song.coverArtPath }); - setCurrentImageUrl(signedURL.url.toString()); - } catch (error) { - console.error("Error getting song / image:", error); - } - } - - // Remove the file association, continue to persist both file and record - async function removeImageFromSong() { - - if (!currentSong) return; - - try { - const response = await client.models.Song.get({ - id: currentSong.id, - }); - - const song = response.data; - - // If the record has no associated file, we can return early. - if (!song?.coverArtPath) return; - - const updatedSong = await client.models.Song.update({ - id: song.id, - coverArtPath: null, - }); - - // If successful, the response here will be `null`: - setCurrentSong(updatedSong.data); - - setCurrentImageUrl(updatedSong.data?.coverArtPath); - - } catch (error) { - console.error("Error removing image from song: ", error); - } - } - - // Remove the record association and delete the file - async function deleteImageForCurrentSong() { - - if (!currentSong) return; - - try { - const response = await client.models.Song.get({ - id: currentSong.id, - }); - - const song = response?.data; - - // If the record has no associated file, we can return early. - if (!song?.coverArtPath) return; - - // Remove associated file from record - const updatedSong = await client.models.Song.update({ - id: song.id, - coverArtPath: null, - }); - - // Delete the file from S3: - await remove({ path: song.coverArtPath }); - - // If successful, the response here will be `null`: - setCurrentSong(updatedSong.data); - - setCurrentImageUrl(updatedSong.data?.coverArtPath); - - } catch (error) { - console.error("Error deleting image: ", error); - } - } - - // Delete both file and record - async function deleteCurrentSongAndImage() { - - if (!currentSong) return; - try { - const response = await client.models.Song.get({ - id: currentSong.id, - }); - const song = response.data; - - // If the record has no associated file, we can return early. - if (!song?.coverArtPath) return; - - await remove({ path: song.coverArtPath }); - - // Delete the record from the API: - await client.models.Song.delete({ id: song.id }); - - clearLocalState(); - - } catch (error) { - console.error("Error deleting song: ", error); - } - } - - function clearLocalState() { - setCurrentSong(null); - setCurrentImageUrl(""); - } - - return ( - <> -

    Hello {user?.username}

    - -
    - - - - - - - -
    - - ); -} - -export default withAuthenticator(App); - -``` - -#### [Multi-File (TS)] - -```ts title="src/App.tsx" - -import "./App.css"; -import { generateClient } from "aws-amplify/api"; -import { uploadData, getUrl, remove } from "aws-amplify/storage"; -import React, { useState } from "react"; -import type { Schema } from "../amplify/data/resource"; -import "@aws-amplify/ui-react/styles.css"; -import { - type WithAuthenticatorProps, - withAuthenticator, -} from "@aws-amplify/ui-react"; -import { Amplify } from "aws-amplify"; -import outputs from "../amplify_outputs.json"; - -Amplify.configure(outputs); - -// Generating the client -const client = generateClient({ - authMode: "apiKey", -}); - -type PhotoAlbum = Schema["PhotoAlbum"]["type"]; - -function App({ signOut, user }: WithAuthenticatorProps) { - // State to hold the recognized text - const [currentPhotoAlbum, setCurrentPhotoAlbum] = useState( - null - ); - - // Used to display images for current photoAlbum: - const [currentImages, setCurrentImages] = useState< - (string | null | undefined)[] | null | undefined - >([]); - - async function createPhotoAlbumWithFirstImage( - e: React.ChangeEvent - ) { - if (!e.target.files) return; - - const file = e.target.files[0]; - - try { - // Create the API record: - const response = await client.models.PhotoAlbum.create({ - name: `My first photoAlbum`, - }); - - const photoAlbum = response.data; - - if (!photoAlbum) return; - - // Upload the Storage file: - const result = await uploadData({ - path: `images/${photoAlbum.id}-${file.name}`, - data: file, - options: { - contentType: "image/png", // contentType is optional - }, - }).result; - - const updatePhotoAlbumDetails = { - id: photoAlbum.id, - imagePaths: [result.path], - }; - - // Add the file association to the record: - const updateResponse = await client.models.PhotoAlbum.update({ - id: photoAlbum.id, - imagePaths: [result.path], - }); - - const updatedPhotoAlbum = updateResponse.data; - - setCurrentPhotoAlbum(updatedPhotoAlbum); - - // If the record has no associated file, we can return early. - if (!updatedPhotoAlbum?.imagePaths?.length) return; - - // Retrieve the file's signed URL: - const signedURL = await getUrl({ - path: updatedPhotoAlbum.imagePaths[0]!, - }); - setCurrentImages([signedURL.url.toString()]); - } catch (error) { - console.error("Error create photoAlbum / file:", error); - } - } - - async function createPhotoAlbumWithMultipleImages( - e: React.ChangeEvent - ) { - if (!e.target.files) return; - - try { - const photoAlbumDetails = { - name: `My first photoAlbum`, - }; - - // Create the API record: - const response = await client.models.PhotoAlbum.create({ - name: `My first photoAlbum`, - }); - - const photoAlbum = response.data; - - if (!photoAlbum) return; - - // Upload all files to Storage: - const imagePaths = await Promise.all( - Array.from(e.target.files).map(async (file) => { - const result = await uploadData({ - path: `images/${photoAlbum.id}-${file.name}`, - data: file, - options: { - contentType: "image/png", // contentType is optional - }, - }).result; - - return result.path; - }) - ); - - // Add the file association to the record: - const updateResponse = await client.models.PhotoAlbum.update({ - id: photoAlbum.id, - imagePaths: imagePaths, - }); - const updatedPhotoAlbum = updateResponse.data; - - setCurrentPhotoAlbum(updatedPhotoAlbum); - - // If the record has no associated file, we can return early. - if (!updatedPhotoAlbum?.imagePaths?.length) return; - - // Retrieve signed urls for all files: - const signedUrls = await Promise.all( - updatedPhotoAlbum.imagePaths.map( - async (path) => await getUrl({ path: path! }) - ) - ); - - if (!signedUrls) return; - setCurrentImages(signedUrls.map((signedUrl) => signedUrl.url.toString())); - } catch (error) { - console.error("Error create photoAlbum / file:", error); - } - } - - async function addNewImagesToPhotoAlbum( - e: React.ChangeEvent - ) { - if (!currentPhotoAlbum) return; - - if (!e.target.files) return; - - try { - // Upload all files to Storage: - const newimagePaths = await Promise.all( - Array.from(e.target.files).map(async (file) => { - const result = await uploadData({ - path: `images/${currentPhotoAlbum.id}-${file.name}`, - data: file, - options: { - contentType: "image/png", // contentType is optional - }, - }).result; - - return result.path; - }) - ); - - // Query existing record to retrieve currently associated files: - const queriedResponse = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, - }); - - const photoAlbum = queriedResponse.data; - - if (!photoAlbum?.imagePaths) return; - - // Merge existing and new file paths: - const updatedimagePaths = [...newimagePaths, ...photoAlbum.imagePaths]; - - // Update record with merged file associations: - const response = await client.models.PhotoAlbum.update({ - id: currentPhotoAlbum.id, - imagePaths: updatedimagePaths, - }); - - const updatedPhotoAlbum = response.data; - setCurrentPhotoAlbum(updatedPhotoAlbum); - - // If the record has no associated file, we can return early. - if (!updatedPhotoAlbum?.imagePaths) return; - - // Retrieve signed urls for merged image paths: - const signedUrls = await Promise.all( - updatedPhotoAlbum?.imagePaths.map( - async (path) => await getUrl({ path: path! }) - ) - ); - - if (!signedUrls) return; - - setCurrentImages(signedUrls.map((signedUrl) => signedUrl.url.toString())); - } catch (error) { - console.error( - "Error uploading image / adding image to photoAlbum: ", - error - ); - } - } - - // Replace last image associated with current photoAlbum: - async function updateLastImage(e: React.ChangeEvent) { - if (!currentPhotoAlbum) return; - - if (!e.target.files) return; - - const file = e.target.files[0]; - - try { - // Upload new file to Storage: - const result = await uploadData({ - path: `images/${currentPhotoAlbum.id}-${file.name}`, - data: file, - options: { - contentType: "image/png", // contentType is optional - }, - }).result; - - const newFilePath = result.path; - - // Query existing record to retrieve currently associated files: - const queriedResponse = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, - }); - - const photoAlbum = queriedResponse.data; - - if (!photoAlbum?.imagePaths?.length) return; - - // Retrieve last image path: - const [lastImagePath] = photoAlbum.imagePaths.slice(-1); - - // Remove last file association by path - const updatedimagePaths = [ - ...photoAlbum.imagePaths.filter((path) => path !== lastImagePath), - newFilePath, - ]; - - // Update record with updated file associations: - const response = await client.models.PhotoAlbum.update({ - id: currentPhotoAlbum.id, - imagePaths: updatedimagePaths, - }); - - const updatedPhotoAlbum = response.data; - - setCurrentPhotoAlbum(updatedPhotoAlbum); - - // If the record has no associated file, we can return early. - if (!updatedPhotoAlbum?.imagePaths) return; - - // Retrieve signed urls for merged image paths: - const signedUrls = await Promise.all( - updatedPhotoAlbum?.imagePaths.map( - async (path) => await getUrl({ path: path! }) - ) - ); - - if (!signedUrls) return; - - setCurrentImages(signedUrls.map((signedUrl) => signedUrl.url.toString())); - } catch (error) { - console.error( - "Error uploading image / adding image to photoAlbum: ", - error - ); - } - } - - async function getImagesForPhotoAlbum() { - if (!currentPhotoAlbum) { - return; - } - try { - // Query the record to get the file paths: - const response = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, - }); - const photoAlbum = response.data; - - // If the record has no associated files, we can return early. - if (!photoAlbum?.imagePaths) return; - - // Retrieve the signed URLs for the associated images: - const signedUrls = await Promise.all( - photoAlbum.imagePaths.map(async (imagePath) => { - if (!imagePath) return; - return await getUrl({ path: imagePath }); - }) - ); - - setCurrentImages( - signedUrls.map((signedUrl) => signedUrl?.url.toString()) - ); - } catch (error) { - console.error("Error getting photoAlbum / image:", error); - } - } - - // Remove the file associations, continue to persist both files and record - async function removeImagesFromPhotoAlbum() { - if (!currentPhotoAlbum) return; - - try { - const response = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, - }); - - const photoAlbum = response.data; - - // If the record has no associated file, we can return early. - if (!photoAlbum?.imagePaths) return; - - const updatedPhotoAlbum = await client.models.PhotoAlbum.update({ - id: photoAlbum.id, - imagePaths: null, - }); - - // If successful, the response here will be `null`: - setCurrentPhotoAlbum(updatedPhotoAlbum.data); - setCurrentImages(updatedPhotoAlbum.data?.imagePaths); - } catch (error) { - console.error("Error removing image from photoAlbum: ", error); - } - } - - // Remove the record association and delete the file - async function deleteImagesForCurrentPhotoAlbum() { - if (!currentPhotoAlbum) return; - - try { - const response = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, - }); - - const photoAlbum = response.data; - - // If the record has no associated files, we can return early. - if (!photoAlbum?.imagePaths) return; - - // Remove associated files from record - const updateResponse = await client.models.PhotoAlbum.update({ - id: photoAlbum.id, - imagePaths: null, // Set the file association to `null` - }); - - const updatedPhotoAlbum = updateResponse.data; - - // Delete the files from S3: - await Promise.all( - photoAlbum?.imagePaths.map(async (imagePath) => { - if (!imagePath) return; - await remove({ path: imagePath }); - }) - ); - - // If successful, the response here will be `null`: - setCurrentPhotoAlbum(updatedPhotoAlbum); - setCurrentImages(null); - } catch (error) { - console.error("Error deleting image: ", error); - } - } - - // Delete both files and record - async function deleteCurrentPhotoAlbumAndImages() { - if (!currentPhotoAlbum) return; - - try { - const response = await client.models.PhotoAlbum.get({ - id: currentPhotoAlbum.id, - }); - - const photoAlbum = response.data; - - if (!photoAlbum) return; - - await client.models.PhotoAlbum.delete({ - id: photoAlbum.id, - }); - - setCurrentPhotoAlbum(null); - - // If the record has no associated file, we can return early. - if (!photoAlbum?.imagePaths) return; - - await Promise.all( - photoAlbum?.imagePaths.map(async (imagePath) => { - if (!imagePath) return; - await remove({ path: imagePath }); - }) - ); - - clearLocalState(); - } catch (error) { - console.error("Error deleting photoAlbum: ", error); - } - } - - function clearLocalState() { - setCurrentPhotoAlbum(null); - setCurrentImages([]); - } - - return ( -
    -

    Hello {user?.username}!

    -

    - Current PhotoAlbum: {currentPhotoAlbum?.id} -

    - -
    - - - - - - - -
    - -
    - - - - - -
    - -
    - {currentImages && - currentImages.map((url, idx) => { - if (!url) return undefined; - return ( - Storage file - ); - })} -
    -
    - ); -} - -export default withAuthenticator(App); - -``` - -
    - ---- - ---- -title: "Add custom real-time subscriptions" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-07-16T16:09:37.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/custom-subscription/" ---- - -Create a custom real-time subscription for any mutation to enable PubSub use cases. - -## Define a custom subscription - -For every custom subscription, you need to set: -1. the mutation(s) that should trigger a subscription event, -2. a return type that matches the subscribed mutations' return type, -3. authorization rules. - -Optionally, you can set filter arguments to customize the server-side subscription filter rules. - -Use `a.subscription()` to define your custom subscription in your **amplify/data/resource.ts** file: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - // Message type that's used for this PubSub sample - Message: a.customType({ - content: a.string().required(), - channelName: a.string().required() - }), - - // Message publish mutation - publish: a.mutation() - .arguments({ - channelName: a.string().required(), - content: a.string().required() - }) - .returns(a.ref('Message')) - .handler(a.handler.custom({ entry: './publish.js' })) - .authorization(allow => [allow.publicApiKey()]), - - // highlight-start - // Subscribe to incoming messages - receive: a.subscription() - // subscribes to the 'publish' mutation - .for(a.ref('publish')) - // subscription handler to set custom filters - .handler(a.handler.custom({entry: './receive.js'})) - // authorization rules as to who can subscribe to the data - .authorization(allow => [allow.publicApiKey()]), - // highlight-end - - // A data model to manage channels - Channel: a.model({ - name: a.string(), - }).authorization(allow => [allow.publicApiKey()]), -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema -}); -``` - -For this example, we're building a generic PubSub capability. This requires us to convert the arguments for `publish` into the `Channel`'s format. Create a new `publish.js` file in your **amplify/data/** folder with the following contents: - -```js title="amplify/data/publish.js" -// This handler simply passes through the arguments of the mutation through as the result -export function request() { - return {} -} - -/** - * @param {import('@aws-appsync/utils').Context} ctx - */ -export function response(ctx) { - return ctx.args -} -``` - -Next, create a new `receive.js` file in your **amplify/data/** folder to define handlers for your subscription. In this case, it'll just be a simple passthrough. In the next section, we'll explore how to use this handler to construct more advanced subscription filters. - -> **Info:** **Note:** We're planning a developer experience enhancement in the near future that'll create this passthrough under the hood. - -```ts title="amplify/data/receive.js" -export function request() { - return {}; -} - -export const response = (ctx) => { - return ctx.result; -}; -``` - -## Subscribe to custom subscriptions client-side - -From your generated Data client, you can find all your custom subscriptions under `client.subscriptions`. Subscribe using the `.subscribe()` function and then use the `next` function to handle incoming events. - -```ts -import { generateClient } from 'aws-amplify/data' -import type { Schema } from '../amplify/data/resource' - -const client = generateClient() - -const sub = client.subscriptions.receive() - .subscribe({ - next: event => { - console.log(event) - } - } -) -``` - -You can try publishing an event using the custom mutation to test the real-time subscription. - -```ts -client.mutations.publish({ - channelName: "world", - content: "My first message!" -}) -``` - -Your subscription event should be received and logs the payload into your app's developer console. Unsubscribe your subscription to disconnect using the `unsubscribe()` function. - -```ts -sub.unsubscribe() -``` - -## (Optionally) Add server-side subscription filters - -> **Info:** **Note:** The custom subscription `.authorization()` modifier does not support the `owner` strategy. This differs from model subscriptions and may result in a subscription event being broadcast to a larger audience. - -You can add subscription filters by adding arguments to the custom subscriptions. - -If you want to customize the filters, modify the subscription handler. For this example, we'll allow a customer to pass in a `namePrefix` parameter that allows the end users to only receive channel events in channels that start with the `namePrefix`. - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; - -const schema = a.schema({ - Channel: a.model({ - name: a.string(), - }).authorization(allow => [allow.publicApiKey()]), - - Message: a.customType({ - content: a.string().required(), - channelName: a.string().required() - }), - - publish: a.mutation() - .arguments({ - channelName: a.string().required(), - content: a.string().required() - }) - .returns(a.ref('Message')) - .handler(a.handler.custom({ entry: './publish.js' })) - .authorization(allow => [allow.publicApiKey()]), - - receive: a.subscription() - .for(a.ref('publish')) - // highlight-next-line - .arguments({ namePrefix: a.string() }) - .handler(a.handler.custom({entry: './receive.js'})) - .authorization(allow => [allow.publicApiKey()]) -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema -}); -``` - -In your handler, you can set custom subscription filters based on arguments passed into the custom subscription. For this example, create a new **receive.js** file alongside the **amplify/data/resource.ts** file: - -```js -import { util, extensions } from "@aws-appsync/utils" - -// Subscription handlers must return a `null` payload on the request -export function request() { return { payload: null } } - -/** - * @param {import('@aws-appsync/utils').Context} ctx - */ -export function response(ctx) { - const filter = { - channelName: { - beginsWith: ctx.args.namePrefix - } - } - - extensions.setSubscriptionFilter(util.transform.toSubscriptionFilter(filter)) - - return null -} -``` - ---- - ---- -title: "Connect to existing data sources" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-06T18:10:09.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/connect-to-existing-data-sources/" ---- - - - ---- - ---- -title: "Connect your app to existing MySQL and PostgreSQL database" -section: "build-a-backend/data/connect-to-existing-data-sources" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-14T20:52:33.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/connect-to-existing-data-sources/connect-postgres-mysql-database/" ---- - -Amplify's native integration supports any MySQL or Postgres database, no matter if they're hosted on AWS within a VPC or outside of AWS with a 3rd party hosted database provider. - -You must create a connection string using the following database information to get started: - -- Database **hostname** -- Database **port** -- Database **username** -- Database **user password** -- Database **name** - -## Step 1 - Set secrets for database connection - -First, provide all the database connection information as secrets, you can use the Amplify sandbox's secret functionality to set them or go to the Amplify console to set secrets in a shared environment: - -```bash showLineNumbers={false} -npx ampx sandbox secret set SQL_CONNECTION_STRING -``` - -#### [MySQL] -**Connection string format for MySQL** -```console showLineNumbers={false} -mysql://user:password@hostname:port/db-name -``` - -#### [PostgreSQL] -**Connection string format for PostgreSQL** -```console showLineNumbers={false} -postgres://user:password@hostname:port/db-name -``` - -## Step 2 - Generate TypeScript representation of your database schema - -Run the following command to generate the Data schema with your database connection information. It'll infer an `a.model()` representation for **all database tables that have primary key specified**. - -```bash -npx ampx generate schema-from-database --connection-uri-secret SQL_CONNECTION_STRING --out amplify/data/schema.sql.ts -``` - - - -If your RDS database exists within a VPC, it must be configured to be `Publicly accessible`. This does not mean the instance needs to be accessible from all IP addresses on the Internet, but this flag is required to allow your local machine to connect via an **Inbound Rule** rather than being inside your VPC or connected to the VPC via VPN. - -**To generate the TypeScript representation of your database schema:** - -If your database is protected by a VPC, you will need to add an **Inbound Rule** for the database port from your local IP address. The `npx ampx generate schema-from-database` command connects to your database from your local workstation to read schema information. - -If you are connecting to an RDS Proxy, the machine you run the `generate schema-from-database` command must be in the same VPC as the proxy itself, or you must connect to it via VPN. Simply opening an **Inbound Rule** for the database port is not sufficient. - -**To connect to your database during runtime:** -When you specify connection information, we'll compare the hostname you supply against a list of RDS instances, clusters, and proxies in your account in the same region as your project. If we find a match, we'll automatically detect the VPC configuration for your database and provision a SQL Lambda function to connect to the database and retrieve the schema. - -The VPC security group in which your database instance, cluster, or proxy is installed must have **Inbound rules** that allow traffic from the following TCP ports: - -1. The specified database port (e.g., 3306 for MySQL databases or 5432 for Postgres databases). -2. Port 443 (HTTPS) to allow the Lambda function to connect to AWS Systems Manager to retrieve configuration parameters. - -Finally, the security group must have **Outbound rules** that allow traffic on those same ports from the security group itself. - - - - - -When creating new DynamoDB-backed data models via `a.model()`, a set of a implicit fields, such as `id`, `createdAt`, and `updatedAt` are added by default. When connecting to an existing SQL database, these fields are not implicitly added as they are not part of the underlying data source. If `createdAt` and `updatedAt` are valid columns in the underlying database table, then Amplify Data will automatically populate those fields with their respective values upon create and update mutations. - - - -
    RDS Proxy for improved connectivity - -Consider adding an RDS Proxy in front of the cluster to manage database connections. - -When using Amplify GraphQL API with a relational database like Amazon RDS, each query from your application needs to open a separate connection to the database. - -If there are a large number of queries occurring concurrently, it can exceed the connection limit on the database and result in errors like "Too many connections". To avoid this, Amplify can use an RDS Proxy when connecting your GraphQL API to a database. - -The RDS Proxy acts as an intermediary sitting in front of your database. Instead of each application query opening a direct connection to the database, they will connect through the Proxy. The Proxy helps manage and pool these connections to avoid overwhelming your database cluster. This improves the availability of your API, allowing more queries to execute concurrently without hitting connection limits. - -However, there is a tradeoff of increased latency - queries may take slightly longer as they wait for an available connection from the Proxy pool. There are also additional costs associated with using RDS Proxy. Please refer to the [pricing page for RDS Proxy](https://aws.amazon.com/rds/proxy/pricing/) to learn more. - -
    - -
    Connecting to a database with a custom SSL certificate - -Amplify creates an [AWS Lambda](https://aws.amazon.com/lambda) function using a Node.js runtime to connect your AppSync API to your SQL database. The Lambda function connects to the database using Secure Socket Layer (SSL) or Transport Layer Security (TLS) to protect data in transit. Amplify automatically uses the correct root certificate authority (CA) certificates for Amazon RDS databases, and the Node.js runtime includes root CAs from [well-known certificate providers](https://github.com/nodejs/node/issues/4175) to connect to non-RDS databases. - -However, if your database uses a custom or self-signed SSL certificate, you can upload the PEM-encoded public CA certificate of 4 KB or less to your Amplify project as a secret when you generate the database configuration, and specify that secret when generating the schema from your database: - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox secret set CUSTOM_SSL_CERT < /path/to/custom/ssl/public-ca-cert.pem -npx ampx generate schema-from-database --connection-uri-secret SQL_CONNECTION_STRING --ssl-cert-secret CUSTOM_SSL_CERT --out amplify/data/schema.sql.ts -``` - -The Lambda function will then use the specified root CA to validate connections to the database. - -When deploying your app to production, you need to [add the PEM-encoded public CA certificate as a secret](/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/#set-secrets). Make sure to add the certificate with the same secret name you used in the sandbox environment. For example, we used `CUSTOM_SSL_CERT` above. Make sure to preserve the newlines and the `------BEGIN CERTIFICATE------` and `------END CERTIFICATE------` delimiters in the value. Finally, make sure the size of the entire value does not exceed 4KB. - -
    - -This creates a new **schema.sql.ts** with a schema reflecting the types of your database. **Do not edit the schema.sql.ts file directly**. Import the schema to your **amplify/data/resource.ts** file and apply any additive changes there. This ensures that you can continuously regenerate the TypeScript schema representation of your SQL database without losing any additive changes that you apply out-of-band. - -```ts -import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; -// highlight-start -import { schema as generatedSqlSchema } from './schema.sql'; - -// Add a global authorization rule -const sqlSchema = generatedSqlSchema.authorization(allow => allow.guest()) -// highlight-end - -// Relational database sources can coexist with DynamoDB tables managed by Amplify. -const schema = a.schema({ - Todo: a.model({ - content: a.string(), - }).authorization(allow => [allow.guest()]) -}); - -// Use the a.combine() operator to stitch together the models backed by DynamoDB -// and the models backed by Postgres or MySQL databases. -// highlight-next-line -const combinedSchema = a.combine([schema, sqlSchema]); - -// Don't forget to update your client types to take into account the types from -// both schemas. -// highlight-next-line -export type Schema = ClientSchema; - -export const data = defineData({ - // Update the data definition to use the combined schema, instead of just - // your DynamoDB-backed schema - // highlight-next-line - schema: combinedSchema -}); -``` - -## Step 3 - Fine-grained authorization rules - -Use the `.setAuthorization()` modifier to set model-level and field-level authorization rules for the SQL-backed data models. Review [Customize your auth rules](/[platform]/build-a-backend/data/customize-authz) for detailed authorization rule options. - -```ts -// Add an authorization rule to the schema -// highlight-start -const sqlSchema = generatedSqlSchema.setAuthorization((models) => [ - // Model-level authorization rules - models.event.authorization((allow) => [allow.publicApiKey()]), - // Field-level authorization rules - models.event.fields.id.authorization(allow => [allow.publicApiKey(), allow.guest()]), - models.event.fields.created_at.authorization(allow => [allow.publicApiKey(), allow.guest()]) -]); -// highlight-end -``` - -## Step 4 - Deploy your Data resources using the cloud sandbox - -Finally, you can now validate your Data resources with your cloud sandbox: - -```bash -npx ampx sandbox -``` - -On the client side, you can now make create, read, update, delete, and subscribe to your SQL-backed data models: - -```ts -const { data: events } = await client.models.event.list() -``` - -## Step 5 - Configuring database connection for production - -When deploying your app to production, you need to [add the database connection string as a secret](/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/#set-secrets). Make sure to add the appropriate database connection string with the same secret name you used in the sandbox environment. For example, we used `SQL_CONNECTION_STRING` above. - - - -## Rename generated models and fields - -To improve the ergonomics of your API, you might want to rename the generate fields or types to better accommodate your use case. Use the `renameModels()` modifier to rename the auto-inferred data models. - -```ts -// Rename models or fields to be more idiomatic for frontend code -const sqlSchema = generatedSqlSchema.authorization(allow => allow.guest()) -// highlight-start - .renameModels(() => [ - //βŒ„βŒ„βŒ„βŒ„βŒ„ existing model name based on table name - ['event', 'Event'] - // ^^^^^^ renamed data model name - ]) - // highlight-end -``` - -## Add relationships between tables - -```ts -const sqlSchema = generatedSqlSchema - .authorization(allow => allow.guest()) -// highlight-start - .setRelationships((models) => [ - models.Note.relationships({ - comments: a.hasMany("Comment", "note_id"), - }), - models.Comment.relationships({ - note: a.belongsTo("Note", "note_id") - }) - ]); -// highlight-end -``` - -## Add custom queries, mutations, subscriptions auto-generated SQL data schema - -Use the `.addToSchema(...)` to add in additional queries, mutations, and subscriptions to your auto-generated SQL data schema. - -> **Info:** Note: you can't add additional data models via `a.model()`. They should be exclusively generated via `npx ampx generate schema-from-database`. - -### Use an inline SQL statement as a query or mutation handler - -```ts -// Add custom mutations or queries that execute SQL statements -const sqlSchema = generatedSqlSchema.authorization(allow => allow.guest()) -// highlight-start - .addToSchema({ - listEventsWithDecodedLatLong: a.query() - // reference custom types added to the schema - .returns(a.ref("EventWithDecodedCoord").array()) - .handler(a.handler.inlineSql( - `SELECT - id, - name, - address, - ST_X(geom) AS longitude, - ST_Y(geom) AS latitude - FROM locations;` - )) - .authorization(allow => [allow.guest()]), - - // Define custom types to provide end-to-end typed results - // for custom queries / mutations - EventWithDecodedCoord: a.customType({ - id: a.integer(), - name: a.string(), - address: a.string(), - longitude: a.float(), - latitude: a.float(), - }) - }) -// highlight-end -``` - -### Reference an existing SQL file as a query or mutation handler - -You can define the different SQL handlers in separate `.sql` files and reference them in your custom queries / mutations. - -First, configure the custom query or mutation in your **amplify/data/resource.ts** file: -```ts -const sqlSchema = generatedSqlSchema.authorization(allow => allow.guest()) - .addToSchema({ - createNewLocationWithLongLat: a.mutation() - .arguments({ - lat: a.float().required(), - long: a.float().required(), - name: a.string().required(), - address: a.string().required() - }) - .returns(a.json().array()) - .authorization(allow => allow.authenticated()) - // highlight-next-line - .handler(a.handler.sqlReference('./createNewLocationWithLongLat.sql')) - }) -``` - -Next, add a corresponding sql file to handle the request: - -#### [MySQL] -```sql title="createNewLocationWithLongLat.sql" -INSERT INTO locations (name, address, geom) -VALUES (:name, :address, ST_GEOMFROMTEXT(CONCAT('POINT (', :long, ' ', :lat, ')'), 4326)); -``` - -#### [PostgreSQL] -```sql title="createNewLocationWithLongLat.sql" -INSERT INTO locations (name, address, geom) -VALUES (:name, :address, ST_SetSRID(ST_MakePoint(:long, :lat), 4326)) -``` - -> **Info:** The return type for custom queries and mutations expecting to return row data from SQL statements must be an array of the corresponding model. This is true even for custom `get` queries, where a single row is expected. -> -> **Example** -> -> ```ts -getPostBySlug: a - .query() - .arguments({ - slug: a.string().required(), - }) - // highlight-start - .returns(a.ref("Post").array()) - // highlight-end - .handler( - a.handler.inlineSql(` - SELECT id, title, slug, content, created_at, updated_at - FROM posts - WHERE slug = :slug; - `) - ) -``` - -## How does it work? - -The Amplify uses AWS Lambda functions to enable features like querying data from your database. To work properly, these Lambda functions need access to common logic and dependencies. - -Amplify provides this shared code in the form of Lambda Layers. You can think of Lambda Layers as a package of reusable runtime code that Lambda functions can reference. - -When you deploy an Amplify API, it will create two Lambda functions: - -### SQL Lambda - -This allows you to query and write data to your database from your API. - - - **NOTE:** If the database is in a VPC, this Lambda function will be deployed - in the same VPC as the database. The usage of Amazon Virtual Private Cloud - (VPC) or VPC peering, with AWS Lambda functions will incur additional charges - as explained, this comes with an additional cost as explained on the [Amazon - Elastic Compute Cloud (EC2) on-demand pricing - page](https://aws.amazon.com/ec2/pricing/on-demand/). - - -### Updater Lambda - -This automatically keeps the SQL Lambda up-to-date by managing its Lambda Layers. - -A Lambda layer that includes all the core SQL connection logic lives within the AWS Amplify service account but is executed within your AWS account, when invoked by the SQL Lambda. This allows the Amplify service team to own the ongoing maintenance and security enhancements of the SQL connection logic. - -This allows the Amplify team to maintain and enhance the SQL Layer without needing direct access to your Lambdas. If updates to the Layer are needed, the Updater Lambda will receive a signal from Amplify and automatically update the SQL Lambda with the latest Layer. - -### Mapping of SQL data types to field types for auto-generated schema - -> **Warning:** **Note:** MySQL does not support time zone offsets in date time or timestamp fields. Instead, we will convert these values to `datetime`, without the offset. Unlike MySQL, PostgreSQL does support date time or timestamp values with an offset. - -| SQL | Mapped field types | -|--------------------|--------------| -| **String** | | -| char | a.string() | -| varchar | a.string() | -| tinytext | a.string() | -| text | a.string() | -| mediumtext | a.string() | -| longtext | a.string() | -| **Geometry** | | -| geometry | a.string() | -| point | a.string() | -| linestring | a.string() | -| geometryCollection | a.string() | -| **Numeric** | | -| smallint | a.integer() | -| mediumint | a.integer() | -| int | a.integer() | -| integer | a.integer() | -| bigint | a.integer() | -| tinyint | a.integer() | -| float | a.float() | -| double | a.float() | -| decimal | a.float() | -| dec | a.float() | -| numeric | a.float() | -| **Date and Time** | | -| date | a.date() | -| datetime | a.datetime() | -| timestamp | a.datetime() | -| time | a.time() | -| year | a.integer() | -| **Binary** | | -| binary | a.string() | -| varbinary | a.string() | -| tinyblob | a.string() | -| blob | a.string() | -| mediumblob | a.string() | -| longblob | a.string() | -| **Others** | | -| bool | a.boolean() | -| boolean | a.boolean() | -| bit | a.integer() | -| json | a.json() | -| enum | a.enum() | - -## Troubleshooting - -### Debug Mode - -To return the actual SQL error instead of a generic error from underlying API responses, an environment variable `DEBUG_MODE` can be set to `true` on the Amplify-generated SQL Lambda function. You can find this Lambda function in the AWS Lambda console with the naming convention of: `--SQLLambdaFunction`. - -### My SQL table doesn't get generated when running `npx ampx generate schema-from-database` - -This is likely because the table doesn't have a designated primary key. A primary key is required for `npx ampx generate schema-from-database` to infer the table structure and create a create, read, update, and delete API. - ---- - ---- -title: "Connect to external Amazon DynamoDB data sources" -section: "build-a-backend/data/connect-to-existing-data-sources" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-11-19T09:34:45.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/connect-to-existing-data-sources/connect-external-ddb-table/" ---- - -The `a.model()` data model allows you to define a GraphQL schema for an AWS AppSync API where models are backed by DynamoDB Tables managed by Amplify. The generated schema also provides queries and mutations to the Amplify Data client. However, you may want to connect to an external DynamoDB table and execute custom business logic against it instead. - -> **Info:** Using an external DynamoDB table as a data source may be useful if you need to leverage patterns such as single table design. - -In the following sections, we walk through the steps to add and use an external DynamoDB table as a data source for your API: - -1. Set up your Amazon DynamoDB table -2. Add your Amazon DynamoDB table as a data source -3. Define custom queries and mutations -4. Configure custom business logic handler code -5. Invoke custom queries or mutations - -## Step 1 - Set up your Amazon DynamoDB table - -For the purpose of this guide we will define a `Post` type and create an external DynamoDB table that will store records for it. In Amplify Gen 2, `customType` adds a type to the schema that is not backed by an Amplify-generated DynamoDB table. - -With the `Post` type defined, it can then be referenced as the return type when defining your custom queries and mutations. - -First, add the `Post` custom type to your schema: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Todo: a - .model({ - content: a.string(), - }) - .authorization(allow => [allow.publicApiKey()]), - // highlight-start - Post: a.customType({ - id: a.id().required(), - author: a.string().required(), - title: a.string(), - content: a.string(), - url: a.string(), - ups: a.integer(), - downs: a.integer(), - version: a.integer(), - }), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -> **Info:** **NOTE:** To comply with the GraphQL spec, at least one query is required for a schema to be valid. Otherwise, deployments will fail with a schema error. The Amplify Data schema is auto-generated with a `Todo` model and corresponding queries under the hood. You can leave the `Todo` model in the schema until you add the first custom query to the schema in the next steps. - -Once the deployment successfully completes, you need to create a DynamoDB table that will serve as your external data source. You can create this table using the AWS Console, AWS CLI, or AWS CDK. For this example, we'll create it using the DynamoDB console: - -1. Navigate to the [DynamoDB console](https://console.aws.amazon.com/dynamodb/). - -2. Choose **Create table**. - -3. For **Table name**, enter `PostTable`. - -4. For **Partition key**, enter `id` and select **String** as the type. - -5. Leave **Sort key** empty (we don't need one for this example). - -6. Keep the default settings for the remaining options and choose **Create table**. - -Alternatively, you can create the table using the AWS CLI: - -```bash -aws dynamodb create-table \ - --table-name PostTable \ - --attribute-definitions \ - AttributeName=id,AttributeType=S \ - --key-schema \ - AttributeName=id,KeyType=HASH \ - --provisioned-throughput \ - ReadCapacityUnits=5,WriteCapacityUnits=5 -``` - -You now have a new DynamoDB table named `PostTable` that exists independently of your Amplify-generated resources. You will use this table as the data source for your custom queries and mutations. - -## Step 2 - Add your Amazon DynamoDB table as a data source - -In your `amplify/backend.ts` file, add your DynamoDB table as a data source for your API: - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend"; -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; -import { aws_dynamodb } from "aws-cdk-lib"; - -export const backend = defineBackend({ - auth, - data, -}); - -// highlight-start -const externalDataSourcesStack = backend.createStack("MyExternalDataSources"); - -const externalTable = aws_dynamodb.Table.fromTableName( - externalDataSourcesStack, - "MyExternalPostTable", - "PostTable" -); - -backend.data.addDynamoDbDataSource( - "ExternalPostTableDataSource", - externalTable -); -// highlight-end -``` - -## Step 3 - Define custom queries and mutations - -Now that your DynamoDB table has been added as a data source, you can reference it in custom queries and mutations using the `a.handler.custom()` modifier which accepts the name of the data source and an entry point for your resolvers. - -Use the following code examples to add `addPost`, `getPost`, `updatePost`, and `deletePost` as custom queries and mutations to your schema: - -#### [addPost] -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Post: a.customType({ - author: a.string().required(), - title: a.string(), - content: a.string(), - url: a.string(), - ups: a.integer(), - downs: a.integer(), - version: a.integer(), - }), - // highlight-start - addPost: a - .mutation() - .arguments({ - id: a.id(), - author: a.string().required(), - title: a.string(), - content: a.string(), - url: a.string(), - }) - .returns(a.ref("Post")) - .authorization(allow => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - dataSource: "ExternalPostTableDataSource", - entry: "./addPost.js", - }) - ), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -#### [getPost] -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Post: a.customType({ - author: a.string().required(), - title: a.string(), - content: a.string(), - url: a.string(), - ups: a.integer(), - downs: a.integer(), - version: a.integer(), - }), - // highlight-start - getPost: a - .query() - .arguments({ id: a.id().required() }) - .returns(a.ref("Post")) - .authorization(allow => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - dataSource: "ExternalPostTableDataSource", - entry: "./getPost.js", - }) - ), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -#### [updatePost] -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Post: a.customType({ - author: a.string().required(), - title: a.string(), - content: a.string(), - url: a.string(), - ups: a.integer(), - downs: a.integer(), - version: a.integer(), - }), - // highlight-start - updatePost: a - .mutation() - .arguments({ - id: a.id().required(), - author: a.string(), - title: a.string(), - content: a.string(), - url: a.string(), - expectedVersion: a.integer().required(), - }) - .returns(a.ref("Post")) - .authorization(allow => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - dataSource: "ExternalPostTableDataSource", - entry: "./updatePost.js", - }) - ), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -#### [deletePost] -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; - -const schema = a.schema({ - Post: a.customType({ - author: a.string().required(), - title: a.string(), - content: a.string(), - url: a.string(), - ups: a.integer(), - downs: a.integer(), - version: a.integer(), - }), - // highlight-start - deletePost: a - .mutation() - .arguments({ id: a.id().required(), expectedVersion: a.integer() }) - .returns(a.ref("Post")) - .authorization(allow => [allow.publicApiKey()]) - .handler( - a.handler.custom({ - dataSource: "ExternalPostTableDataSource", - entry: "./deletePost.js", - }) - ), - // highlight-end -}); - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'apiKey', - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); -``` - -## Step 4 - Configure custom business logic handler code - -Next, create the following files in your `amplify/data` folder and use the code examples to define custom resolvers for the custom queries and mutations added to your schema from the previous step. These are AppSync JavaScript resolvers - -#### [addPost] -```js title="amplify/data/addPost.js" -import { util } from "@aws-appsync/utils"; -import * as ddb from "@aws-appsync/utils/dynamodb"; - -export function request(ctx) { - const item = { ...ctx.arguments, ups: 1, downs: 0, version: 1 }; - const key = { id: ctx.args.id ?? util.autoId() }; - return ddb.put({ key, item }); -} - -export function response(ctx) { - return ctx.result; -} -``` - -#### [getPost] -```js title="amplify/data/getPost.js" -import * as ddb from "@aws-appsync/utils/dynamodb"; - -export function request(ctx) { - return ddb.get({ key: { id: ctx.args.id } }); -} - -export const response = (ctx) => ctx.result; -``` - -#### [updatePost] -```js title="amplify/data/updatePost.js" -import { util } from "@aws-appsync/utils"; -import * as ddb from "@aws-appsync/utils/dynamodb"; - -export function request(ctx) { - const { id, expectedVersion, ...rest } = ctx.args; - const values = Object.entries(rest).reduce((obj, [key, value]) => { - obj[key] = value ?? ddb.operations.remove(); - return obj; - }, {}); - - return ddb.update({ - key: { id }, - condition: { version: { eq: expectedVersion } }, - update: { ...values, version: ddb.operations.increment(1) }, - }); -} - -export function response(ctx) { - const { error, result } = ctx; - if (error) { - util.appendError(error.message, error.type); - } - return result; -} -``` - -#### [deletePost] -```js title="amplify/data/deletePost.js" -import { util } from "@aws-appsync/utils"; -import * as ddb from "@aws-appsync/utils/dynamodb"; - -export function request(ctx) { - let condition = null; - if (ctx.args.expectedVersion) { - condition = { - or: [ - { id: { attributeExists: false } }, - { version: { eq: ctx.args.expectedVersion } }, - ], - }; - } - return ddb.remove({ key: { id: ctx.args.id }, condition }); -} - -export function response(ctx) { - const { error, result } = ctx; - if (error) { - util.appendError(error.message, error.type); - } - return result; -} -``` - -## Step 5 - Invoke custom queries or mutations - -From your generated Data client, you can find all your custom queries and mutations under the client.queries. and client.mutations. APIs respectively. - -#### [addPost] -```ts title="App.tsx" -const { data, errors } = await client.mutations.addPost({ - title: "My Post", - content: "My Content", - author: "Chris", -}); -``` - -#### [getPost] -```ts title="App.tsx" -const { data, errors } = await client.queries.getPost({ - id: "" -}); -``` - -#### [updatePost] -```ts title="App.tsx" -const { data, errors } = await client.mutations.updatePost({ - id: "", - title: "An Updated Post", - expectedVersion: 1, -}); -``` - -#### [deletePost] -```ts title="App.tsx" -const { data, errors } = await client.mutations.deletePost({ - id: "", -}); -``` - -## Conclusion - -In this guide, you’ve added an external DynamoDB table as a data source to an Amplify GraphQL API and defined custom queries and mutations, handled by AppSync JS resolvers, to manipulate Post items in an external DynamoDB table using the Amplify Gen 2 Data client. - -To clean up, you can delete your sandbox by accepting the prompt when terminating the sandbox process in your terminal. Alternatively, you can also use the AWS Amplify console to manage and delete sandbox environments. - -To delete your external DynamoDB table, you can navigate to the AppSync console and click on the name of the table in the data sources list. This takes you to the DynamoDB console where you can delete the table. - -## All DynamoDB operations & example resolvers - -### GetItem - -[Reference](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-getitem) - The `GetItem` request lets you tell the AWS AppSync DynamoDB function to make a `GetItem` request to DynamoDB, and enables you to specify: - -- The key of the item in DynamoDB -- Whether to use a consistent read or not - -**Example:** - -```js -export function request(ctx) { - const { foo, bar } = ctx.args; - return { - operation: 'GetItem', - key: util.dynamodb.toMapValues({ foo, bar }), - consistentRead: true - }; -} -``` - -### PutItem - -[PutItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-putitem) - The `PutItem` request mapping document lets you tell the AWS AppSync DynamoDB function to make a `PutItem` request to DynamoDB, and enables you to specify the following: - -- The key of the item in DynamoDB -- The full contents of the item (composed of key and attributeValues) -- Conditions for the operation to succeed - -**Example:** - -```js -import { util } from '@aws-appsync/utils'; - -export function request(ctx) { - const { foo, bar, ...values } = ctx.args; - return { - operation: 'PutItem', - key: util.dynamodb.toMapValues({ foo, bar }), - attributeValues: util.dynamodb.toMapValues(values) - }; -} -``` - -### UpdateItem - -[UpdateItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-updateitem) - The `UpdateItem` request enables you to tell the AWS AppSync DynamoDB function to make a `UpdateItem` request to DynamoDB and allows you to specify the following: - -- The key of the item in DynamoDB -- An update expression describing how to update the item in DynamoDB -- Conditions for the operation to succeed - -**Example:** - -```js -import { util } from '@aws-appsync/utils'; - -export function request(ctx) { - const { id } = ctx.args; - return { - operation: 'UpdateItem', - key: util.dynamodb.toMapValues({ id }), - update: { - expression: 'ADD #voteField :plusOne, version :plusOne', - expressionNames: { '#voteField': 'upvotes' }, - expressionValues: { ':plusOne': { N: 1 } } - } - }; -} -``` - -### DeleteItem - -[DeleteItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-deleteitem) - The `DeleteItem` request lets you tell the AWS AppSync DynamoDB function to make a `DeleteItem` request to DynamoDB, and enables you to specify the following: - -- The key of the item in DynamoDB -- Conditions for the operation to succeed - -**Example:** - -```js -import { util } from '@aws-appsync/utils'; - -export function request(ctx) { - return { - operation: 'DeleteItem', - key: util.dynamodb.toMapValues({ id: ctx.args.id }) - }; -} -``` - -### Query - -[Query](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-query) - The Query request object lets you tell the AWS AppSync DynamoDB resolver to make a Query request to DynamoDB, and enables you to specify the following: - -- Key expression -- Which index to use -- Any additional filter -- How many items to return -- Whether to use consistent reads -- query direction (forward or backward) -- Pagination token - -**Example:** - -```js -import { util } from '@aws-appsync/utils'; - -export function request(ctx) { - const { owner } = ctx.args; - return { - operation: 'Query', - query: { - expression: 'ownerId = :ownerId', - expressionValues: util.dynamodb.toMapValues({ ':ownerId': owner }) - }, - index: 'owner-index' - }; -} -``` -### Scan - -[Scan](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-scan) - The `Scan` request lets you tell the AWS AppSync DynamoDB function to make a `Scan` request to DynamoDB, and enables you to specify the following: - -- A filter to exclude results -- Which index to use -- How many items to return -- Whether to use consistent reads -- Pagination token -- Parallel scans - -**Example:** - -```js -export function request(ctx) { - return { operation: 'Scan' }; -} -``` -### Sync - -[Sync](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-sync) - The `Sync` request object lets you retrieve all the results from a DynamoDB table and then receive only the data altered since your last query (the delta updates). `Sync` requests can only be made to versioned DynamoDB data sources. You can specify the following: - -- A filter to exclude results - -- How many items to return - -- Pagination Token - -- When your last Sync operation was started - -**Example:** - -```js -export function request(ctx) { - const { nextToken, lastSync } = ctx.args; - return { operation: 'Sync', limit: 100, nextToken, lastSync }; -} -``` - -### BatchGetItem - -[BatchGetItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-batch-get-item) - The `BatchGetItem` request object lets you tell the AWS AppSync DynamoDB function to make a `BatchGetItem` request to DynamoDB to retrieve multiple items, potentially across multiple tables. For this request object, you must specify the following: - -- The table names where to retrieve the items from - -- The keys of the items to retrieve from each table - -The DynamoDB `BatchGetItem` limits apply and **no condition expression** can be provided. - -**Example:** -```js -import { util } from '@aws-appsync/utils'; - -export function request(ctx) { - const { authorId, postId } = ctx.args; - return { - operation: 'BatchGetItem', - tables: { - authors: [util.dynamodb.toMapValues({ authorId })], - posts: [util.dynamodb.toMapValues({ authorId, postId })], - }, - }; -} -``` - -### BatchDeleteItem - -[BatchDeleteItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-batch-delete-item) - The BatchDeleteItem request object lets you tell the AWS AppSync DynamoDB function to make a BatchWriteItem request to DynamoDB to delete multiple items, potentially across multiple tables. For this request object, you must specify the following: - -- The table names where to delete the items from - -- The keys of the items to delete from each table - -The DynamoDB `BatchWriteItem` limits apply and **no condition expression** can be provided. - -**Example:** - -```js -import { util } from '@aws-appsync/utils'; - -export function request(ctx) { - const { authorId, postId } = ctx.args; - return { - operation: 'BatchDeleteItem', - tables: { - authors: [util.dynamodb.toMapValues({ authorId })], - posts: [util.dynamodb.toMapValues({ authorId, postId })], - }, - }; -} -``` - -### BatchPutItem - -[BatchPutItem](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-batch-put-item) - The `BatchPutItem` request object lets you tell the AWS AppSync DynamoDB function to make a `BatchWriteItem` request to DynamoDB to put multiple items, potentially across multiple tables. For this request object, you must specify the following: - -- The table names where to put the items in - -- The full items to put in each table - -The DynamoDB `BatchWriteItem` limits apply and **no condition expression** can be provided. - -**Example:** - -```js -import { util } from '@aws-appsync/utils'; - -export function request(ctx) { - const { authorId, postId, name, title } = ctx.args; - return { - operation: 'BatchPutItem', - tables: { - authors: [util.dynamodb.toMapValues({ authorId, name })], - posts: [util.dynamodb.toMapValues({ authorId, postId, title })], - }, - }; -} -``` - -### TransactGetItems - -[TransactGetItems](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-transact-get-items) - The `TransactGetItems` request object lets you to tell the AWS AppSync DynamoDB function to make a `TransactGetItems` request to DynamoDB to retrieve multiple items, potentially across multiple tables. For this request object, you must specify the following: - -- The table name of each request item where to retrieve the item from - -- The key of each request item to retrieve from each table - -The DynamoDB `TransactGetItems` limits apply and **no condition expression** can be provided. - -**Example:** - -```js -import { util } from '@aws-appsync/utils'; - -export function request(ctx) { - const { authorId, postId } = ctx.args; - return { - operation: 'TransactGetItems', - transactItems: [ - { - table: 'posts', - key: util.dynamodb.toMapValues({ postId }), - }, - { - table: 'authors', - key: util.dynamodb.toMapValues({ authorId }), - }, - ], - }; -} -``` - -### TransactWriteItems - -[TransactWriteItems](https://docs.aws.amazon.com/appsync/latest/devguide/js-resolver-reference-dynamodb.html#js-aws-appsync-resolver-reference-dynamodb-transact-write-items) - The `TransactWriteItems` request object lets you tell the AWS AppSync DynamoDB function to make a `TransactWriteItems` request to DynamoDB to write multiple items, potentially to multiple tables. For this request object, you must specify the following: - -- The destination table name of each request item - -- The operation of each request item to perform. There are four types of operations that are supported: `PutItem`, `UpdateItem`, `DeleteItem`, and `ConditionCheck` - -- The key of each request item to write - -The DynamoDB `TransactWriteItems` limits apply. - -**Example:** - -```js -import { util } from '@aws-appsync/utils'; - -export function request(ctx) { - const { authorId, postId, title, description, oldTitle, authorName } = ctx.args; - return { - operation: 'TransactWriteItems', - transactItems: [ - { - table: 'posts', - operation: 'PutItem', - key: util.dynamodb.toMapValues({ postId }), - attributeValues: util.dynamodb.toMapValues({ title, description }), - condition: util.transform.toDynamoDBConditionExpression({ - title: { eq: oldTitle }, - }), - }, - { - table: 'authors', - operation: 'UpdateItem', - key: util.dynamodb.toMapValues({ authorId }), - update: { - expression: 'SET authorName = :name', - expressionValues: util.dynamodb.toMapValues({ ':name': authorName }), - }, - }, - ], - }; -} -``` - ---- - ---- -title: "Connect to data from Server-side Runtimes" -section: "build-a-backend/data" -platforms: ["android", "angular", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-06-19T16:13:00.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/connect-from-server-runtime/" ---- - - - ---- - ---- -title: "Next.js server runtime" -section: "build-a-backend/data/connect-from-server-runtime" -platforms: ["android", "angular", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-06-19T16:13:00.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/connect-from-server-runtime/nextjs-server-runtime/" ---- - -This guide walks through how you can connect to Amplify Data from Next.js Server-side Runtimes (SSR). For Next.js applications, Amplify provides first-class support for the [App Router (React Server Components, Route Handlers, and Server Actions)](https://nextjs.org/docs/app), the [Pages Router (Components, API Routes)](https://nextjs.org/docs/pages), and [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware). - -Before you begin, you will need: - -- [A Next.js application created](/[platform]/start/quickstart/) -- [Installed and configured Amplify libraries for Next.js](/nextjs/build-a-backend/server-side-rendering/) -- [Deployed Amplify Data resources](/[platform]/build-a-backend/data/set-up-data/), or directly using [AWS AppSync](https://aws.amazon.com/appsync/) - -## Connect to Amplify Data from a Next.js server runtime - -Connecting to Amplify Data will include choosing the correct data client for Next.js server runtimes, generating the data client, and then calling the API. - -### Step 1 - Choose the correct Data client for Next.js server runtimes - -Amplify offers two specialized data clients for Next.js server runtimes (from `@aws-amplify/adapter-nextjs/data`) that you should use depending whether you retrieve the user tokens using [`cookies`](https://nextjs.org/docs/app/api-reference/functions/cookies) or [`NextRequest`](https://nextjs.org/docs/app/api-reference/functions/next-request) and [`NextResponse`](https://nextjs.org/docs/app/api-reference/functions/next-response): - -- `generateServerClientUsingCookies()` πŸͺ generates a data client with the Next.js `cookies` function from `next/headers`. Each API request dynamically refetches the cookies at runtime. -- `generateServerClientUsingReqRes()` 🌐 generates a data client requiring `NextRequest` and `NextResponse` provided to an `runWithAmplifyServerContext` function to prevent token contamination. - -Choose the correct data client based on your Next.js Router (App or Pages) and then the use case: - -#### [App Router] - -| Use case | Required Data client | -| ---------------------- | --------------------------------------- | -| React Server Component | `generateServerClientUsingCookies()` πŸͺ | -| Server Actions | `generateServerClientUsingCookies()` πŸͺ | -| Route Handler | `generateServerClientUsingCookies()` πŸͺ | -| Middleware | `generateServerClientUsingReqRes()` 🌐 | - -#### [Pages Router] - -**Pages Router** - -| Use case | Required Data client | -| -------------------------- | -------------------------------------- | -| Server-side component code | `generateServerClientUsingReqRes()` 🌐 | -| API Route | `generateServerClientUsingReqRes()` 🌐 | -| Middleware | `generateServerClientUsingReqRes()` 🌐 | - -### Step 2 - Generate the Data client for Next.js server runtimes - -#### [generateServerClientUsingCookies() πŸͺ] - -To generate a Data client for the Next.js server runtime using cookies, you need to provide both your Amplify configuration and the cookies function from Next.js. - -```ts -import { type Schema } from '@/amplify/data/resource'; -import { generateServerClientUsingCookies } from '@aws-amplify/adapter-nextjs/data'; -import outputs from '@/amplify_outputs.json'; -import { cookies } from 'next/headers'; - -export const cookieBasedClient = generateServerClientUsingCookies({ - config: outputs, - cookies, -}); -``` - - - -We recommend you generate Amplify Data's server client in a utility file. Then, import the generated client in your Next.js React Server Components, Server Actions, or Route Handlers. - - - -#### [generateServerClientUsingReqRes() 🌐] - -To generate a data client for the Next.js server runtime using `NextRequest` and `NextResponse`, you only need to provide your Amplify configuration. When making the individual API requests, you will need to pass the config to the [`runWithAmplifyServerContext`](/[platform]/build-a-backend/server-side-rendering) function to pass in the cookies from request and response variables. - -```ts -import { type Schema } from '@/amplify/data/resource'; -import { createServerRunner } from '@aws-amplify/adapter-nextjs'; -import { generateServerClientUsingReqRes } from '@aws-amplify/adapter-nextjs/data'; -import outputs from '@/amplify_outputs.json'; - -export const { runWithAmplifyServerContext } = createServerRunner({ - config: outputs, -}); - -export const reqResBasedClient = generateServerClientUsingReqRes({ - config: outputs, -}); -``` - - - -We recommend you generate the server Data client in a utility file. Then, import the generated client in your Next.js Middleware, component's server runtime code, and API Routes. - - - -### Step 3 - Call API using generated server Data clients - -You can make any available query or mutation request with the generated server data clients; however, note that subscriptions are not available within server runtimes. - -#### [generateServerClientUsingCookies() πŸͺ] - -Import the cookie-based server Data client in your Next.js React Server Component code and make your API requests. - -```ts -import { type Schema } from '@/amplify/data/resource'; -import { generateServerClientUsingCookies } from '@aws-amplify/adapter-nextjs/data'; -import outputs from '@/amplify_outputs.json'; -import { cookies } from 'next/headers'; - -export const cookieBasedClient = generateServerClientUsingCookies({ - config: outputs, - cookies, -}); - -const fetchTodos = async () => { - const { data: todos, errors } = await cookieBasedClient.models.Todo.list(); - - if (!errors) { - return todos; - } -}; -``` - -#### [generateServerClientUsingReqRes() 🌐] - -Import the NextRequest/NextResponse-based server Data client in your Next.js server runtime code and make your API requests within the `runWithAmplifyServerContext` function. Review [Server-side Rendering](/[platform]/build-a-backend/server-side-rendering) to learn more about creating an Amplify server context. - -For example, in a Next.js Pages Router API route, use the `req` and `res` parameters from the `handler` function with `runWithAmplifyServerContext`: - -```ts -import { type Schema } from '@/amplify/data/resource'; -import type { NextApiRequest, NextApiResponse } from 'next'; -import { - runWithAmplifyServerContext, - reqResBasedClient, -} from '@/utils/amplifyServerUtils'; - -type ResponseData = { - todos: Schema['Todo']['type'][]; -}; - -export default async function handler( - request: NextApiRequest, - response: NextApiResponse -) { - const todos = await runWithAmplifyServerContext({ - nextServerContext: { request, response }, - operation: async (contextSpec) => { - const { data: todos } = await reqResBasedClient.models.Todo.list( - contextSpec - ); - return todos; - }, - }); - - response.status(200).json({ todos }); -} -``` - ---- - ---- -title: "Nuxt.js server runtime" -section: "build-a-backend/data/connect-from-server-runtime" -platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] -gen: 2 -last-updated: "2024-06-06T19:49:33.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/connect-from-server-runtime/nuxtjs-server-runtime/" ---- - -This guide walks through how you can connect to Amplify Data from Nuxt.js Server-side Runtime (SSR). For Nuxt.js applications, Amplify provides first-class support for [Routing (Pages)](https://nuxt.com/docs/getting-started/routing) , [API Routes](https://nuxt.com/docs/guide/directory-structure/server#server-routes) , and [Middleware](https://nuxt.com/docs/guide/directory-structure/server#server-middleware). - -Before you begin, you will need: - -- [A Nuxt.js application created](https://nuxt.com/docs/getting-started/installation) -- [Deployed Amplify Data resources](/[platform]/build-a-backend/data/set-up-data/), or directly using [AWS AppSync](https://aws.amazon.com/appsync/) - -## Connect to Amplify Data from a Nuxt.js server runtime - -Connecting to Amplify Data will include setting up the AmplifyAPIs Plugin with the `runWithAmplifyServerContext` adapter, using the `useNuxtApp()` composable, setting up the Amplify server context utility and then using the `runAmplifyApi` function to call the API in an isolated server context. - -### Step 1 - Set up the AmplifyAPIs Plugin - -Nuxt 3 offers universal rendering by default, where your data fetching logic may be executed on both the client and server sides. Amplify offers APIs that are capable of running within a server context to support use cases such as server-side rendering (SSR) and static site generation (SSG), though Amplify's client-side APIs and server-side APIs of Amplify are slightly different. You can set up an AmplifyAPIs plugin to make your data fetching logic run smoothly across the client and server. To learn more about how to use Amplify categories APIs in server side rendering, refer to this [documentation](/[platform]/build-a-backend/server-side-rendering/). - -1. Create a `plugins` directory under the root of your Nuxt project. -2. Create two files `01.amplify-apis.client.ts` and `01.amplify-apis.server.ts` under the `plugins` directory. - -In these files, you will register both client-specific and server-specific Amplify APIs that you will use in your Nuxt project as a plugin. You can then access these APIs via the `useNuxtApp` composable. - - - -**NOTE:** The leading number in the files name indicate the plugin loading order, for more details see https://nuxt.com/docs/guide/directory-structure/plugins#registration-order. The `.client` and `.server` indicate the runtime that the logic contained in the file will run on, client or server. For details see: https://nuxt.com/docs/guide/directory-structure/plugins - - - -Modify the `01.amplify-apis.client.ts` file, with the following code: - - - -```ts title="nuxt-amplify-gen2/plugins/01.amplify-apis.client.ts" -import { - fetchAuthSession, - fetchUserAttributes, - signIn, - signOut, - getCurrentUser, -} from "aws-amplify/auth"; -import { generateClient } from "aws-amplify/data"; -import outputs from "../amplify_outputs.json"; -import type { Schema } from "@/amplify/data/resource"; -import { Amplify } from "aws-amplify"; - -// configure the Amplify client library -if (process.client) { - Amplify.configure(outputs, { ssr: true }); -} - -// generate your data client using the Schema from your backend -const client = generateClient(); - -export default defineNuxtPlugin({ - name: "AmplifyAPIs", - enforce: "pre", - setup() { - return { - provide: { - // You can call the API by via the composable `useNuxtApp()`. For example: - // `useNuxtApp().$Amplify.Auth.fetchAuthSession()` - Amplify: { - Auth: { - fetchAuthSession, - fetchUserAttributes, - getCurrentUser, - signIn, - signOut, - }, - GraphQL: { - client, - }, - }, - }, - }; - }, -}); -``` - - -Make sure you call `Amplify.configure` as early as possible in your application’s life-cycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. Review the [Library Not Configured Troubleshooting guide](/[platform]/build-a-backend/troubleshooting/library-not-configured/) for possible causes of this issue. - - - - - -Next, modify the `01.amplify-apis.server.ts` file, with the following code: - - - -```ts title="nuxt-amplify-gen2/plugins/01.amplify-apis.server.ts" -import type { CookieRef } from "nuxt/app"; -import { - createKeyValueStorageFromCookieStorageAdapter, - createUserPoolsTokenProvider, - createAWSCredentialsAndIdentityIdProvider, - runWithAmplifyServerContext, -} from "aws-amplify/adapter-core"; -import { parseAmplifyConfig } from "aws-amplify/utils"; -import { - fetchAuthSession, - fetchUserAttributes, - getCurrentUser, -} from "aws-amplify/auth/server"; -import { generateClient } from "aws-amplify/data/server"; -import type { - LibraryOptions, - FetchAuthSessionOptions, -} from "@aws-amplify/core"; -import type { - GraphQLOptionsV6, - GraphQLResponseV6, -} from "@aws-amplify/api-graphql"; - -import outputs from "../amplify_outputs.json"; - -// parse the content of `amplify_outputs.json` into the shape of ResourceConfig -const amplifyConfig = parseAmplifyConfig(outputs); - -// create the Amplify used token cookies names array -const userPoolClientId = amplifyConfig.Auth!.Cognito.userPoolClientId; -const lastAuthUserCookieName = `CognitoIdentityServiceProvider.${userPoolClientId}.LastAuthUser`; - -// create a GraphQL client that can be used in a server context -const gqlServerClient = generateClient({ config: amplifyConfig }); - -const getAmplifyAuthKeys = (lastAuthUser: string) => - ["idToken", "accessToken", "refreshToken", "clockDrift"] - .map( - (key) => - `CognitoIdentityServiceProvider.${userPoolClientId}.${lastAuthUser}.${key}` - ) - .concat(lastAuthUserCookieName); - -// define the plugin -export default defineNuxtPlugin({ - name: "AmplifyAPIs", - enforce: "pre", - setup() { - // The Nuxt composable `useCookie` is capable of sending cookies to the - // client via the `SetCookie` header. If the `expires` option is left empty, - // it sets a cookie as a session cookie. If you need to persist the cookie - // on the client side after your end user closes your Web app, you need to - // specify an `expires` value. - // - // We use 30 days here as an example (the default Cognito refreshToken - // expiration time). - const expires = new Date(); - expires.setDate(expires.getDate() + 30); - - // Get the last auth user cookie value - // - // We use `sameSite: 'lax'` in this example, which allows the cookie to be - // sent to your Nuxt server when your end user gets redirected to your Web - // app from a different domain. You should choose an appropriate value for - // your own use cases. - const lastAuthUserCookie = useCookie(lastAuthUserCookieName, { - sameSite: "lax", - expires, - secure: true, - }); - - // Get all Amplify auth token cookie names - const authKeys = lastAuthUserCookie.value - ? getAmplifyAuthKeys(lastAuthUserCookie.value) - : []; - - // Create a key-value map of cookie name => cookie ref - // - // Using the composable `useCookie` here in the plugin setup prevents - // cross-request pollution. - const amplifyCookies = authKeys - .map((name) => ({ - name, - cookieRef: useCookie(name, { sameSite: "lax", expires, secure: true }), - })) - .reduce>>( - (result, current) => ({ - ...result, - [current.name]: current.cookieRef, - }), - {} - ); - - // Create a key value storage based on the cookies - // - // This key value storage is responsible for providing Amplify Auth tokens to - // the APIs that you are calling. - // - // If you implement the `set` method, when Amplify needed to refresh the Auth - // tokens on the server side, the new tokens would be sent back to the client - // side via `SetCookie` header in the response. Otherwise the refresh tokens - // would not be propagate to the client side, and Amplify would refresh - // the tokens when needed on the client side. - // - // In addition, if you decide not to implement the `set` method, you don't - // need to pass any `CookieOptions` to the `useCookie` composable. - const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter({ - get(name) { - const cookieRef = amplifyCookies[name]; - - if (cookieRef && cookieRef.value) { - return { name, value: cookieRef.value }; - } - - return undefined; - }, - getAll() { - return Object.entries(amplifyCookies).map(([name, cookieRef]) => { - return { name, value: cookieRef.value ?? undefined }; - }); - }, - set(name, value) { - const cookieRef = amplifyCookies[name]; - if (cookieRef) { - cookieRef.value = value; - } - }, - delete(name) { - const cookieRef = amplifyCookies[name]; - - if (cookieRef) { - cookieRef.value = null; - } - }, - }); - - // Create a token provider - const tokenProvider = createUserPoolsTokenProvider( - amplifyConfig.Auth!, - keyValueStorage - ); - - // Create a credentials provider - const credentialsProvider = createAWSCredentialsAndIdentityIdProvider( - amplifyConfig.Auth!, - keyValueStorage - ); - - // Create the libraryOptions object - const libraryOptions: LibraryOptions = { - Auth: { - tokenProvider, - credentialsProvider, - }, - }; - - return { - provide: { - // You can add the Amplify APIs that you will use on the server side of - // your Nuxt app here. You must only use the APIs exported from the - // `aws-amplify//server` subpaths. - // - // You can call the API by via the composable `useNuxtApp()`. For example: - // `useNuxtApp().$Amplify.Auth.fetchAuthSession()` - // - // Recall that Amplify server APIs are required to be called in a isolated - // server context that is created by the `runWithAmplifyServerContext` - // function. - Amplify: { - Auth: { - fetchAuthSession: (options: FetchAuthSessionOptions) => - runWithAmplifyServerContext( - amplifyConfig, - libraryOptions, - (contextSpec) => fetchAuthSession(contextSpec, options) - ), - fetchUserAttributes: () => - runWithAmplifyServerContext( - amplifyConfig, - libraryOptions, - (contextSpec) => fetchUserAttributes(contextSpec) - ), - getCurrentUser: () => - runWithAmplifyServerContext( - amplifyConfig, - libraryOptions, - (contextSpec) => getCurrentUser(contextSpec) - ), - }, - GraphQL: { - client: { - // Follow this typing to ensure the`graphql` API return type can - // be inferred correctly according to your queries and mutations - graphql: < - FALLBACK_TYPES = unknown, - TYPED_GQL_STRING extends string = string - >( - options: GraphQLOptionsV6, - additionalHeaders?: Record - ) => - runWithAmplifyServerContext< - GraphQLResponseV6 - >(amplifyConfig, libraryOptions, (contextSpec) => - gqlServerClient.graphql( - contextSpec, - options, - additionalHeaders - ) - ), - }, - }, - }, - }, - }; - }, -}); -``` - - -### Step 2 - Use the `useNuxtApp()` composable - -Using the GraphQL API in `~/app.vue`: - -```ts title="nuxt-amplify-gen2/app.vue" - - - -``` - -The `app.vue` file can be rendered on both the client and server sides by default. The `useNuxtApp().$Amplify` composable will pick up the correct implementation of `01.amplify-apis.client.ts` and `01.amplify-apis.server.ts` to use, depending on the runtime. - -> **Warning:** Only a subset of Amplify APIs are usable on the server side, and as the libraries evolve, `amplify-apis.client` and `amplify-apis.server` may diverge further. You can guard your API calls to ensure an API is available in the context where you use it. E.g., you can use `if (process.client)` to ensure that a client-only API isn't executed on the server. - -### Step 3 - Set up Amplify for API Routes - -Following the specification of Nuxt, your API route handlers will live under `~/server`, which is a separate environment from other parts of your Nuxt app; hence, the plugins created in the previous step are not usable here, and extra work is required. - -#### Setup Amplify Server Context Utility - -1. Create a `utils` directory under the `server` directory of your Nuxt project. -2. Create an `amplifyUtils.ts` file under the `utils` directory. - -In this file, you will create a helper function to call Amplify APIs that are capable of running on the server side with context isolation. Modify the `amplifyUtils.ts` file, with the following code: - - - -```ts title="nuxt-amplify-gen2/server/utils/amplifyUtils.ts" -import type { H3Event, EventHandlerRequest } from "h3"; -import { - createKeyValueStorageFromCookieStorageAdapter, - createUserPoolsTokenProvider, - createAWSCredentialsAndIdentityIdProvider, - runWithAmplifyServerContext, - AmplifyServer, - CookieStorage, -} from "aws-amplify/adapter-core"; -import { parseAmplifyConfig } from "aws-amplify/utils"; - -import type { LibraryOptions } from "@aws-amplify/core"; -import outputs from "~/amplify_outputs.json"; - -const amplifyConfig = parseAmplifyConfig(outputs); - -const createCookieStorageAdapter = ( - event: H3Event -): CookieStorage.Adapter => { - // `parseCookies`, `setCookie` and `deleteCookie` are Nuxt provided functions - const readOnlyCookies = parseCookies(event); - - return { - get(name) { - if (readOnlyCookies[name]) { - return { name, value: readOnlyCookies[name] }; - } - }, - set(name, value, options) { - setCookie(event, name, value, options); - }, - delete(name) { - deleteCookie(event, name); - }, - getAll() { - return Object.entries(readOnlyCookies).map(([name, value]) => { - return { name, value }; - }); - }, - }; -}; - -const createLibraryOptions = ( - event: H3Event -): LibraryOptions => { - const cookieStorage = createCookieStorageAdapter(event); - const keyValueStorage = - createKeyValueStorageFromCookieStorageAdapter(cookieStorage); - const tokenProvider = createUserPoolsTokenProvider( - amplifyConfig.Auth!, - keyValueStorage - ); - const credentialsProvider = createAWSCredentialsAndIdentityIdProvider( - amplifyConfig.Auth!, - keyValueStorage - ); - - return { - Auth: { - tokenProvider, - credentialsProvider, - }, - }; -}; - -export const runAmplifyApi = ( - // we need the event object to create a context accordingly - event: H3Event, - operation: ( - contextSpec: AmplifyServer.ContextSpec - ) => Result | Promise -) => { - return runWithAmplifyServerContext( - amplifyConfig, - createLibraryOptions(event), - operation - ); -}; -``` - - - -Now, you can use the `runAmplifyApi` function to call Amplify APIs in an isolated server context. Create a new API route `/api/current-user` in the `server` directory and modify the `current-user.ts` file, with the following code: - -```ts title="nuxt-amplify-gen2/server/api/current-user.ts" -import { getCurrentUser } from "aws-amplify/auth/server"; -import { runAmplifyApi } from "~/server/utils/amplifyUtils"; - -export default defineEventHandler(async (event) => { - const user = await runAmplifyApi(event, (contextSpec) => - getCurrentUser(contextSpec) - ); - - return user; -}); -``` - -You can then fetch data from this API route, for example: `fetch('http://localhost:3000/api/current-user')` - ---- - ---- -title: "Optimistic UI" -section: "build-a-backend/data" -platforms: ["javascript", "swift", "angular", "nextjs", "react", "vue"] -gen: 2 -last-updated: "2025-02-12T16:04:46.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/optimistic-ui/" ---- - - -Amplify Data can be used with [TanStack Query](https://tanstack.com/query/latest/docs/react/overview) to implement optimistic UI, allowing CRUD operations to be rendered immediately on the UI before the request roundtrip has completed. Using Amplify Data with TanStack additionally makes it easy to render loading and error states, and allows you to rollback changes on the UI when API calls are unsuccessful. - -In the following examples we'll create a list view that optimistically renders newly created items, and a detail view that optimistically renders updates and deletes. - - - -For more details on TanStack Query, including requirements, supported browsers, and advanced usage, see the [TanStack Query documentation](https://tanstack.com/query/latest/docs/react/overview). -For complete guidance on how to implement optimistic updates with TanStack Query, see the [TanStack Query Optimistic UI Documentation](https://tanstack.com/query/latest/docs/react/guides/optimistic-updates). -For more on Amplify Data, see the [API documentation](/[platform]/build-a-backend/data/set-up-data/). - - - -To get started, run the following command in an existing Amplify project with a React frontend: - -```bash title="Terminal" showLineNumbers={false} -npm add @tanstack/react-query && \ -npm add --save-dev @tanstack/react-query-devtools -``` - -Modify your Data schema to use this "Real Estate Property" example: - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - RealEstateProperty: a.model({ - name: a.string().required(), - address: a.string(), - }).authorization(allow => [allow.guest()]) -}) - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'iam', - }, -}); -``` - -Save the file and run `npx ampx sandbox` to deploy the changes to your backend cloud sandbox. For the purposes of this guide, we'll build a Real Estate Property listing application. - -Next, at the root of your project, add the required TanStack Query imports, and create a client: - -```ts title="src/main.tsx" -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.tsx' -import './index.css' -import { Amplify } from 'aws-amplify' -import outputs from '../amplify_outputs.json' -// highlight-start -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; -// highlight-end - -Amplify.configure(outputs) - -const queryClient = new QueryClient() - -ReactDOM.createRoot(document.getElementById('root')!).render( - - // highlight-start - - - - - // highlight-end - , -) -``` - - - -TanStack Query Devtools are not required, but are a useful resource for debugging and understanding how TanStack works under the hood. By default, React Query Devtools are only included in bundles when `process.env.NODE_ENV === 'development'`, meaning that no additional configuration is required to exclude them from a production build. -For more information on the TanStack Query Devtools, visit the [TanStack Query Devtools docs](https://tanstack.com/query/latest/docs/react/devtools) - - - - - -For the complete working example, including required imports and React component state management, see the [Complete Example](#complete-example) below. - - - -## How to use TanStack Query query keys with the Amplify Data API - -TanStack Query manages query caching based on the query keys you specify. A query key must be an array. The array can contain a single string or multiple strings and nested objects. The query key must be serializable, and unique to the query's data. - -When using TanStack to render optimistic UI with Amplify Data, you must use different query keys depending on the API operation. When retrieving a list of items, a single string is used (e.g. `queryKey: ["realEstateProperties"]`). This query key is also used to optimistically render a newly created item. When updating or deleting an item, the query key must also include the unique identifier for the record being deleted or updated (e.g. `queryKey: ["realEstateProperties", newRealEstateProperty.id]`). - -For more detailed information on query keys, see the [TanStack Query documentation](https://tanstack.com/query/v4/docs/react/guides/query-keys). - -## Optimistically rendering a list of records - -To optimistically render a list of items returned from the Amplify Data API, use the TanStack `useQuery` hook, passing in the Data API query as the `queryFn` parameter. The following example creates a query to retrieve all records from the API. We'll use `realEstateProperties` as the query key, which will be the same key we use to optimistically render a newly created item. - -```ts title="src/App.tsx" -// highlight-start -import type { Schema } from '../amplify/data/resource' -import { generateClient } from 'aws-amplify/data' -import { useQuery } from '@tanstack/react-query' - -const client = generateClient(); -// highlight-end - -function App() { - // highlight-start - const { - data: realEstateProperties, - isLoading, - isSuccess, - isError: isErrorQuery, - } = useQuery({ - queryKey: ["realEstateProperties"], - queryFn: async () => { - const response = await client.models.RealEstateProperty.list(); - - const allRealEstateProperties = response.data; - - if (!allRealEstateProperties) return null; - - return allRealEstateProperties; - }, - }); - // highlight-end - // return ... -} -``` - -## Optimistically rendering a newly created record - -To optimistically render a newly created record returned from the Amplify Data API, use the TanStack `useMutation` hook, passing in the Amplify Data API mutation as the `mutationFn` parameter. We'll use the same query key used by the `useQuery` hook (`realEstateProperties`) as the query key to optimistically render a newly created item. -We'll use the `onMutate` function to update the cache directly, as well as the `onError` function to rollback changes when a request fails. - -```ts -import { generateClient } from 'aws-amplify/api' -import type { Schema } from '../amplify/data/resource' -// highlight-next-line -import { useQueryClient, useMutation } from '@tanstack/react-query' - -const client = generateClient() - -function App() { - // highlight-next-line - const queryClient = useQueryClient(); - - // highlight-start - const createMutation = useMutation({ - mutationFn: async (input: { name: string, address: string }) => { - const { data: newRealEstateProperty } = await client.models.RealEstateProperty.create(input) - return newRealEstateProperty; - }, - // When mutate is called: - onMutate: async (newRealEstateProperty) => { - // Cancel any outgoing refetches - // (so they don't overwrite our optimistic update) - await queryClient.cancelQueries({ queryKey: ["realEstateProperties"] }); - - // Snapshot the previous value - const previousRealEstateProperties = queryClient.getQueryData([ - "realEstateProperties", - ]); - - // Optimistically update to the new value - if (previousRealEstateProperties) { - queryClient.setQueryData(["realEstateProperties"], (old: Schema["RealEstateProperty"]["type"][]) => [ - ...old, - newRealEstateProperty, - ]); - } - - // Return a context object with the snapshotted value - return { previousRealEstateProperties }; - }, - // If the mutation fails, - // use the context returned from onMutate to rollback - onError: (err, newRealEstateProperty, context) => { - console.error("Error saving record:", err, newRealEstateProperty); - if (context?.previousRealEstateProperties) { - queryClient.setQueryData( - ["realEstateProperties"], - context.previousRealEstateProperties - ); - } - }, - // Always refetch after error or success: - onSettled: () => { - queryClient.invalidateQueries({ queryKey: ["realEstateProperties"] }); - }, - }); - // highlight-end - // return ... -} -``` - -## Querying a single item with TanStack Query - -To optimistically render updates on a single item, we'll first retrieve the item from the API. We'll use the `useQuery` hook, passing in the `get` query as the `queryFn` parameter. For the query key, we'll use a combination of `realEstateProperties` and the record's unique identifier. - -```ts -import { generateClient } from 'aws-amplify/data' -import type { Schema } from '../amplify/data/resource' -import { useQuery } from '@tanstack/react-query' - -const client = generateClient() - -function App() { - const currentRealEstatePropertyId = "SOME_ID" - // highlight-start - const { - data: realEstateProperty, - isLoading, - isSuccess, - isError: isErrorQuery, - } = useQuery({ - queryKey: ["realEstateProperties", currentRealEstatePropertyId], - queryFn: async () => { - if (!currentRealEstatePropertyId) { return } - - const { data: property } = await client.models.RealEstateProperty.get({ - id: currentRealEstatePropertyId, - }); - return property; - }, - }); - // highlight-end -} -``` - -## Optimistically render updates for a record - -To optimistically render Amplify Data updates for a single record, use the TanStack `useMutation` hook, passing in the update mutation as the `mutationFn` parameter. We'll use the same query key combination used by the single record `useQuery` hook (`realEstateProperties` and the record's `id`) as the query key to optimistically render the updates. -We'll use the `onMutate` function to update the cache directly, as well as the `onError` function to rollback changes when a request fails. - - - -When directly interacting with the cache via the `onMutate` function, the `newRealEstateProperty` parameter will only include fields that are being updated. When calling `setQueryData`, include the previous values for all fields in addition to the newly updated fields to avoid only rendering optimistic values for updated fields on the UI. - - - -```ts title="src/App.tsx" -import { generateClient } from 'aws-amplify/data' -import type { Schema } from '../amplify/data/resource' -import { useQueryClient, useMutation } from "@tanstack/react-query"; - -const client = generateClient() - -function App() { - // highlight-next-line - const queryClient = useQueryClient(); - - // highlight-start - const updateMutation = useMutation({ - mutationFn: async (realEstatePropertyDetails: { id: string, name?: string, address?: string }) => { - const { data: updatedProperty } = await client.models.RealEstateProperty.update(realEstatePropertyDetails); - - return updatedProperty; - }, - // When mutate is called: - onMutate: async (newRealEstateProperty: { id: string, name?: string, address?: string }) => { - // Cancel any outgoing refetches - // (so they don't overwrite our optimistic update) - await queryClient.cancelQueries({ - queryKey: ["realEstateProperties", newRealEstateProperty.id], - }); - - await queryClient.cancelQueries({ - queryKey: ["realEstateProperties"], - }); - - // Snapshot the previous value - const previousRealEstateProperty = queryClient.getQueryData([ - "realEstateProperties", - newRealEstateProperty.id, - ]); - - // Optimistically update to the new value - if (previousRealEstateProperty) { - queryClient.setQueryData( - ["realEstateProperties", newRealEstateProperty.id], - /** - * `newRealEstateProperty` will at first only include updated values for - * the record. To avoid only rendering optimistic values for updated - * fields on the UI, include the previous values for all fields: - */ - { ...previousRealEstateProperty, ...newRealEstateProperty } - ); - } - - // Return a context with the previous and new realEstateProperty - return { previousRealEstateProperty, newRealEstateProperty }; - }, - // If the mutation fails, use the context we returned above - onError: (err, newRealEstateProperty, context) => { - console.error("Error updating record:", err, newRealEstateProperty); - if (context?.previousRealEstateProperty) { - queryClient.setQueryData( - ["realEstateProperties", context.newRealEstateProperty.id], - context.previousRealEstateProperty - ); - } - }, - // Always refetch after error or success: - onSettled: (newRealEstateProperty) => { - if (newRealEstateProperty) { - queryClient.invalidateQueries({ - queryKey: ["realEstateProperties", newRealEstateProperty.id], - }); - queryClient.invalidateQueries({ - queryKey: ["realEstateProperties"], - }); - } - }, - }); - // highlight-end -} -``` - -## Optimistically render deleting a record - -To optimistically render a deletion of a single record, use the TanStack `useMutation` hook, passing in the delete mutation as the `mutationFn` parameter. We'll use the same query key combination used by the single record `useQuery` hook (`realEstateProperties` and the record's `id`) as the query key to optimistically render the updates. -We'll use the `onMutate` function to update the cache directly, as well as the `onError` function to rollback changes when a delete fails. - -```ts title="src/App.tsx" -import { generateClient } from 'aws-amplify/data' -import type { Schema } from '../amplify/data/resource' -import { useQueryClient, useMutation } from '@tanstack/react-query' - -const client = generateClient() - -function App() { - // highlight-next-line - const queryClient = useQueryClient(); - - // highlight-start - const deleteMutation = useMutation({ - mutationFn: async (realEstatePropertyDetails: { id: string }) => { - const { data: deletedProperty } = await client.models.RealEstateProperty.delete(realEstatePropertyDetails); - return deletedProperty; - }, - // When mutate is called: - onMutate: async (newRealEstateProperty) => { - // Cancel any outgoing refetches - // (so they don't overwrite our optimistic update) - await queryClient.cancelQueries({ - queryKey: ["realEstateProperties", newRealEstateProperty.id], - }); - - await queryClient.cancelQueries({ - queryKey: ["realEstateProperties"], - }); - - // Snapshot the previous value - const previousRealEstateProperty = queryClient.getQueryData([ - "realEstateProperties", - newRealEstateProperty.id, - ]); - - // Optimistically update to the new value - if (previousRealEstateProperty) { - queryClient.setQueryData( - ["realEstateProperties", newRealEstateProperty.id], - newRealEstateProperty - ); - } - - // Return a context with the previous and new realEstateProperty - return { previousRealEstateProperty, newRealEstateProperty }; - }, - // If the mutation fails, use the context we returned above - onError: (err, newRealEstateProperty, context) => { - console.error("Error deleting record:", err, newRealEstateProperty); - if (context?.previousRealEstateProperty) { - queryClient.setQueryData( - ["realEstateProperties", context.newRealEstateProperty.id], - context.previousRealEstateProperty - ); - } - }, - // Always refetch after error or success: - onSettled: (newRealEstateProperty) => { - if (newRealEstateProperty) { - queryClient.invalidateQueries({ - queryKey: ["realEstateProperties", newRealEstateProperty.id], - }); - queryClient.invalidateQueries({ - queryKey: ["realEstateProperties"], - }); - } - }, - }); - // highlight-end -} -``` - -## Loading and error states for optimistically rendered data - -Both `useQuery` and `useMutation` return `isLoading` and `isError` states that indicate the current state of the query or mutation. You can use these states to render loading and error indicators. - -In addition to operation-specific loading states, TanStack Query provides a [`useIsFetching` hook](https://www.tanstack.com/query/v4/docs/react/guides/background-fetching-indicators#displaying-global-background-fetching-loading-state). For the purposes of this demo, we show a global loading indicator in the [Complete Example](#complete-example) when *any* queries are fetching (including in the background) in order to help visualize what TanStack is doing in the background: - -```js -function GlobalLoadingIndicator() { - const isFetching = useIsFetching(); - return isFetching ?
    : null; -} -``` - -For more details on advanced usage of TanStack Query hooks, see the [TanStack documentation](https://tanstack.com/query/latest/docs/react/guides/mutations). - -The following example demonstrates how to use the state returned by TanStack to render a loading indicator while a mutation is in progress, and an error message if the mutation fails. For additional examples, see the [Complete Example](#complete-example) below. - -```ts -<> - {updateMutation.isError && - updateMutation.error instanceof Error ? ( -
    An error occurred: {updateMutation.error.message}
    - ) : null} - - {updateMutation.isSuccess ? ( -
    Real Estate Property updated!
    - ) : null} - - - -``` - -## Complete example - -```tsx title="src/main.tsx" -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.tsx' -import './index.css' -import { Amplify } from 'aws-amplify' -import outputs from '../amplify_outputs.json' -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; - -Amplify.configure(outputs) - -export const queryClient = new QueryClient() - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - , -) -``` - -```tsx title="src/App.tsx" -import { generateClient } from 'aws-amplify/data' -import type { Schema } from '../amplify/data/resource' -import './App.css' -import { useIsFetching, useMutation, useQuery } from '@tanstack/react-query' -import { queryClient } from './main' -import { useState } from 'react' - -const client = generateClient({ - authMode: 'iam' -}) - -function GlobalLoadingIndicator() { - const isFetching = useIsFetching(); - - return isFetching ?
    : null; -} - -function App() { - const [currentRealEstatePropertyId, setCurrentRealEstatePropertyId] = - useState(null); - - const { - data: realEstateProperties, - isLoading, - isSuccess, - isError: isErrorQuery, - } = useQuery({ - queryKey: ["realEstateProperties"], - queryFn: async () => { - const response = await client.models.RealEstateProperty.list(); - - const allRealEstateProperties = response.data; - - if (!allRealEstateProperties) return null; - - return allRealEstateProperties; - }, - }); - - const createMutation = useMutation({ - mutationFn: async (input: { name: string, address: string }) => { - const { data: newRealEstateProperty } = await client.models.RealEstateProperty.create(input) - return newRealEstateProperty; - }, - // When mutate is called: - onMutate: async (newRealEstateProperty) => { - // Cancel any outgoing refetches - // (so they don't overwrite our optimistic update) - await queryClient.cancelQueries({ queryKey: ["realEstateProperties"] }); - - // Snapshot the previous value - const previousRealEstateProperties = queryClient.getQueryData([ - "realEstateProperties", - ]); - - // Optimistically update to the new value - if (previousRealEstateProperties) { - queryClient.setQueryData(["realEstateProperties"], (old: Schema["RealEstateProperty"]["type"][]) => [ - ...old, - newRealEstateProperty, - ]); - } - - // Return a context object with the snapshotted value - return { previousRealEstateProperties }; - }, - // If the mutation fails, - // use the context returned from onMutate to rollback - onError: (err, newRealEstateProperty, context) => { - console.error("Error saving record:", err, newRealEstateProperty); - if (context?.previousRealEstateProperties) { - queryClient.setQueryData( - ["realEstateProperties"], - context.previousRealEstateProperties - ); - } - }, - // Always refetch after error or success: - onSettled: () => { - queryClient.invalidateQueries({ queryKey: ["realEstateProperties"] }); - }, - }); - - function RealEstatePropertyDetailView() { - - const { - data: realEstateProperty, - isLoading, - isSuccess, - isError: isErrorQuery, - } = useQuery({ - queryKey: ["realEstateProperties", currentRealEstatePropertyId], - queryFn: async () => { - if (!currentRealEstatePropertyId) { return } - - const { data: property } = await client.models.RealEstateProperty.get({ id: currentRealEstatePropertyId }); - return property - }, - }); - - const updateMutation = useMutation({ - mutationFn: async (realEstatePropertyDetails: { id: string, name?: string, address?: string }) => { - const { data: updatedProperty } = await client.models.RealEstateProperty.update(realEstatePropertyDetails); - - return updatedProperty; - }, - // When mutate is called: - onMutate: async (newRealEstateProperty: { id: string, name?: string, address?: string }) => { - // Cancel any outgoing refetches - // (so they don't overwrite our optimistic update) - await queryClient.cancelQueries({ - queryKey: ["realEstateProperties", newRealEstateProperty.id], - }); - - await queryClient.cancelQueries({ - queryKey: ["realEstateProperties"], - }); - - // Snapshot the previous value - const previousRealEstateProperty = queryClient.getQueryData([ - "realEstateProperties", - newRealEstateProperty.id, - ]); - - // Optimistically update to the new value - if (previousRealEstateProperty) { - queryClient.setQueryData( - ["realEstateProperties", newRealEstateProperty.id], - /** - * `newRealEstateProperty` will at first only include updated values for - * the record. To avoid only rendering optimistic values for updated - * fields on the UI, include the previous values for all fields: - */ - { ...previousRealEstateProperty, ...newRealEstateProperty } - ); - } - - // Return a context with the previous and new realEstateProperty - return { previousRealEstateProperty, newRealEstateProperty }; - }, - // If the mutation fails, use the context we returned above - onError: (err, newRealEstateProperty, context) => { - console.error("Error updating record:", err, newRealEstateProperty); - if (context?.previousRealEstateProperty) { - queryClient.setQueryData( - ["realEstateProperties", context.newRealEstateProperty.id], - context.previousRealEstateProperty - ); - } - }, - // Always refetch after error or success: - onSettled: (newRealEstateProperty) => { - if (newRealEstateProperty) { - queryClient.invalidateQueries({ - queryKey: ["realEstateProperties", newRealEstateProperty.id], - }); - queryClient.invalidateQueries({ - queryKey: ["realEstateProperties"], - }); - } - }, - }); - - const deleteMutation = useMutation({ - mutationFn: async (realEstatePropertyDetails: { id: string }) => { - const { data: deletedProperty } = await client.models.RealEstateProperty.delete(realEstatePropertyDetails); - return deletedProperty; - }, - // When mutate is called: - onMutate: async (newRealEstateProperty) => { - // Cancel any outgoing refetches - // (so they don't overwrite our optimistic update) - await queryClient.cancelQueries({ - queryKey: ["realEstateProperties", newRealEstateProperty.id], - }); - - await queryClient.cancelQueries({ - queryKey: ["realEstateProperties"], - }); - - // Snapshot the previous value - const previousRealEstateProperty = queryClient.getQueryData([ - "realEstateProperties", - newRealEstateProperty.id, - ]); - - // Optimistically update to the new value - if (previousRealEstateProperty) { - queryClient.setQueryData( - ["realEstateProperties", newRealEstateProperty.id], - newRealEstateProperty - ); - } - - // Return a context with the previous and new realEstateProperty - return { previousRealEstateProperty, newRealEstateProperty }; - }, - // If the mutation fails, use the context we returned above - onError: (err, newRealEstateProperty, context) => { - console.error("Error deleting record:", err, newRealEstateProperty); - if (context?.previousRealEstateProperty) { - queryClient.setQueryData( - ["realEstateProperties", context.newRealEstateProperty.id], - context.previousRealEstateProperty - ); - } - }, - // Always refetch after error or success: - onSettled: (newRealEstateProperty) => { - if (newRealEstateProperty) { - queryClient.invalidateQueries({ - queryKey: ["realEstateProperties", newRealEstateProperty.id], - }); - queryClient.invalidateQueries({ - queryKey: ["realEstateProperties"], - }); - } - }, - }); - - return ( -
    -

    Real Estate Property Detail View

    - {isErrorQuery &&
    {"Problem loading Real Estate Property"}
    } - {isLoading && ( -
    - {"Loading Real Estate Property..."} -
    - )} - {isSuccess && ( -
    -

    {`Name: ${realEstateProperty?.name}`}

    -

    {`Address: ${realEstateProperty?.address}`}

    -
    - )} - {realEstateProperty && ( -
    -
    - {updateMutation.isPending ? ( - "Updating Real Estate Property..." - ) : ( - <> - {updateMutation.isError && - updateMutation.error instanceof Error ? ( -
    An error occurred: {updateMutation.error.message}
    - ) : null} - - {updateMutation.isSuccess ? ( -
    Real Estate Property updated!
    - ) : null} - - - - - )} -
    - -
    - {deleteMutation.isPending ? ( - "Deleting Real Estate Property..." - ) : ( - <> - {deleteMutation.isError && - deleteMutation.error instanceof Error ? ( -
    An error occurred: {deleteMutation.error.message}
    - ) : null} - - {deleteMutation.isSuccess ? ( -
    Real Estate Property deleted!
    - ) : null} - - - - )} -
    -
    - )} - -
    - ); - - } - return ( -
    - {!currentRealEstatePropertyId && ( -
    -

    Real Estate Properties:

    -
    - {createMutation.isPending ? ( - "Adding Real Estate Property..." - ) : ( - <> - {createMutation.isError && - createMutation.error instanceof Error ? ( -
    An error occurred: {createMutation.error.message}
    - ) : null} - - {createMutation.isSuccess ? ( -
    Real Estate Property added!
    - ) : null} - - - - )} -
    -
      - {isLoading && ( -
      - {"Loading Real Estate Properties..."} -
      - )} - {isErrorQuery && ( -
      {"Problem loading Real Estate Properties"}
      - )} - {isSuccess && - realEstateProperties?.map((realEstateProperty, idx) => { - if (!realEstateProperty) return null; - return ( -
    • -

      {realEstateProperty.name}

      - -
    • - ); - })} -
    -
    - )} - {currentRealEstatePropertyId && } - -
    - ); - -} - -export default App - -const styles = { - appContainer: { - display: "flex", - flexDirection: "column", - alignItems: "center", - }, - detailViewButton: { marginLeft: "1rem" }, - detailViewContainer: { border: "1px solid black", padding: "3rem" }, - globalLoadingIndicator: { - position: "fixed", - top: 0, - left: 0, - width: "100%", - height: "100%", - border: "4px solid blue", - pointerEvents: "none", - }, - listItem: { - display: "flex", - justifyContent: "space-between", - border: "1px dotted grey", - padding: ".5rem", - margin: ".1rem", - }, - loadingIndicator: { - border: "1px solid black", - padding: "1rem", - margin: "1rem", - }, - propertiesList: { - display: "flex", - flexDirection: "column", - alignItems: "center", - justifyContent: "start", - width: "50%", - border: "1px solid black", - padding: "1rem", - listStyleType: "none", - }, -} as const; -``` - - - -Implementing optimistic UI with Amplify Data allows CRUD operations to be rendered immediately on the UI before the request roundtrip has completed, and allows you to rollback changes on the UI when API calls are unsuccessful. - -In the following example, we'll create a list view that optimistically renders newly created items, updates and deletes. Modify your Data schema to use this "Real Estate Property" example: - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - RealEstateProperty: a.model({ - name: a.string().required(), - address: a.string(), - }).authorization(allow => [allow.guest()]) -}) - -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: 'iam', - }, -}); -``` - -Save the file and run `npx ampx sandbox` to deploy the changes to your backend cloud sandbox. For the purposes of this guide, we'll build a Real Estate Property listing application. - -Once the backend has been provisioned, run `npx ampx generate graphql-client-code --format modelgen --model-target swift --out /AmplifyModels` to generate the Swift model types for the app. - -Next, add the Amplify(`https://github.com/aws-amplify/amplify-swift.git`) package to your Xcode project and select the following modules to import when prompted: - -- AWSAPIPlugin -- AWSCognitoAuthPlugin -- AWSS3StoragePlugin -- Amplify - - - -For the complete working example see the [Complete Example](#complete-example) below. - - - -## How to use a Swift Actor to perform optimistic UI updates - -A Swift actor serializes access to its underlying properties. In this example, the actor will hold a list of items that will be published to the UI through a Combine publisher whenever the list is accessed. On a high level, the methods on the actor will perform the following: - -- create a new model, add it to the list, remove the newly added item from the list if the API request is unsuccessful -- update the existing model in the list, revert the update on the list if the API request is unsuccessful -- delete the existing model from the list, add the item back into the list if the API request is unsuccessful - -By providing these methods through an actor object, the underlying list will be accessed serially so that the entire operation can be rolled back if needed. - -To create an actor object that allows optimistic UI updates, create a new file and add the following code. - -```swift -import Amplify -import SwiftUI -import Combine - -actor RealEstatePropertyList { - - private var properties: [RealEstateProperty?] = [] { - didSet { - subject.send(properties.compactMap { $0 }) - } - } - - private let subject = PassthroughSubject<[RealEstateProperty], Never>() - var publisher: AnyPublisher<[RealEstateProperty], Never> { - subject.eraseToAnyPublisher() - } - - func listProperties() async throws { - let result = try await Amplify.API.query(request: .list(RealEstateProperty.self)) - guard case .success(let propertyList) = result else { - print("Failed with error: ", result) - return - } - properties = propertyList.elements - } -} -``` - -Calling the `listProperties()` method will perform a query with Amplify Data API and store the results in the `properties` property. When this property is set, the list is sent back to the subscribers. In your UI, create a view model and subscribe to updates: - -```swift -class RealEstatePropertyContainerViewModel: ObservableObject { - @Published var properties: [RealEstateProperty] = [] - var sink: AnyCancellable? - - var propertyList = RealEstatePropertyList() - init() { - Task { - sink = await propertyList.publisher - .receive(on: DispatchQueue.main) - .sink { properties in - print("Updating property list") - self.properties = properties - } - } - } - - func loadList() { - Task { - try? await propertyList.listProperties() - } - } -} - -struct RealEstatePropertyContainerView: View { - @StateObject var vm = RealEstatePropertyContainerViewModel() - @State private var propertyName: String = "" - - var body: some View { - Text("Hello") - } -} -``` - -## Optimistically rendering a newly created record - -To optimistically render a newly created record returned from the Amplify Data API, add a method to the `actor RealEstatePropertyList`: - -```swift -func createProperty(name: String, address: String? = nil) { - let property = RealEstateProperty(name: name, address: address) - // Optimistically send the newly created property, for the UI to render. - properties.append(property) - - Task { - do { - // Create the property record - let result = try await Amplify.API.mutate(request: .create(property)) - guard case .failure(let graphQLResponse) = result else { - return - } - print("Failed with error: ", graphQLResponse) - // Remove the newly created property - if let index = properties.firstIndex(where: { $0?.id == property.id }) { - properties.remove(at: index) - } - } catch { - print("Failed with error: ", error) - // Remove the newly created property - if let index = properties.firstIndex(where: { $0?.id == property.id }) { - properties.remove(at: index) - } - } - } -} -``` - -## Optimistically rendering a record update - -To optimistically render updates on a single item, use the code snippet like below: - -```swift -func updateProperty(_ property: RealEstateProperty) async { - guard let index = properties.firstIndex(where: { $0?.id == property.id }) else { - print("No property to update") - return - } - - // Optimistically update the property, for the UI to render. - let rollbackProperty = properties[index] - properties[index] = property - - do { - // Update the property record - let result = try await Amplify.API.mutate(request: .update(property)) - guard case .failure(let graphQLResponse) = result else { - return - } - print("Failed with error: ", graphQLResponse) - properties[index] = rollbackProperty - } catch { - print("Failed with error: ", error) - properties[index] = rollbackProperty - } -} -``` - -## Optimistically render deleting a record - -To optimistically render a Amplify Data API delete, use the code snippet like below: - -```swift -func deleteProperty(_ property: RealEstateProperty) async { - guard let index = properties.firstIndex(where: { $0?.id == property.id }) else { - print("No property to remove") - return - } - - // Optimistically remove the property, for the UI to render. - let rollbackProperty = properties[index] - properties[index] = nil - - do { - // Delete the property record - let result = try await Amplify.API.mutate(request: .delete(property)) - switch result { - case .success: - // Finalize the removal - properties.remove(at: index) - case .failure(let graphQLResponse): - print("Failed with error: ", graphQLResponse) - // Undo the removal - properties[index] = rollbackProperty - } - - } catch { - print("Failed with error: ", error) - // Undo the removal - properties[index] = rollbackProperty - } -} -``` - -## Complete example - -#### [Main] - -```swift -import SwiftUI -import Amplify -import AWSAPIPlugin - -@main -struct OptimisticUIApp: App { - - init() { - do { - Amplify.Logging.logLevel = .verbose - try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels())) - try Amplify.configure(with: .amplifyOutputs) - print("Amplify configured with API, Storage, and Auth plugins!") - } catch { - print("Failed to initialize Amplify with \(error)") - } - } - - var body: some Scene { - WindowGroup { - RealEstatePropertyContainerView() - } - } -} - -// Extend the model to Identifiable to make it compatible with SwiftUI's `ForEach`. -extension RealEstateProperty: Identifiable { } - -struct TappedButtonStyle: ButtonStyle { - func makeBody(configuration: Configuration) -> some View { - configuration.label - .padding(10) - .background(configuration.isPressed ? Color.teal.opacity(0.8) : Color.teal) - .foregroundColor(.white) - .clipShape(RoundedRectangle(cornerRadius: 10)) - } -} -``` - -#### [Actor] - -```swift -actor RealEstatePropertyList { - - private var properties: [RealEstateProperty?] = [] { - didSet { - subject.send(properties.compactMap { $0 }) - } - } - - private let subject = PassthroughSubject<[RealEstateProperty], Never>() - var publisher: AnyPublisher<[RealEstateProperty], Never> { - subject.eraseToAnyPublisher() - } - - func listProperties() async throws { - let result = try await Amplify.API.query(request: .list(RealEstateProperty.self)) - guard case .success(let propertyList) = result else { - print("Failed with error: ", result) - return - } - properties = propertyList.elements - } - - func createProperty(name: String, address: String? = nil) { - let property = RealEstateProperty(name: name, address: address) - // Optimistically send the newly created property, for the UI to render. - properties.append(property) - - Task { - do { - // Create the property record - let result = try await Amplify.API.mutate(request: .create(property)) - guard case .failure(let graphQLResponse) = result else { - return - } - print("Failed with error: ", graphQLResponse) - // Remove the newly created property - if let index = properties.firstIndex(where: { $0?.id == property.id }) { - properties.remove(at: index) - } - } catch { - print("Failed with error: ", error) - // Remove the newly created property - if let index = properties.firstIndex(where: { $0?.id == property.id }) { - properties.remove(at: index) - } - } - } - } - - func updateProperty(_ property: RealEstateProperty) async { - guard let index = properties.firstIndex(where: { $0?.id == property.id }) else { - print("No property to update") - return - } - - // Optimistically update the property, for the UI to render. - let rollbackProperty = properties[index] - properties[index] = property - - do { - // Update the property record - let result = try await Amplify.API.mutate(request: .update(property)) - guard case .failure(let graphQLResponse) = result else { - return - } - print("Failed with error: ", graphQLResponse) - properties[index] = rollbackProperty - } catch { - print("Failed with error: ", error) - properties[index] = rollbackProperty - } - } - - func deleteProperty(_ property: RealEstateProperty) async { - guard let index = properties.firstIndex(where: { $0?.id == property.id }) else { - print("No property to remove") - return - } - - // Optimistically remove the property, for the UI to render. - let rollbackProperty = properties[index] - properties[index] = nil - - do { - // Delete the property record - let result = try await Amplify.API.mutate(request: .delete(property)) - switch result { - case .success: - // Finalize the removal - properties.remove(at: index) - case .failure(let graphQLResponse): - print("Failed with error: ", graphQLResponse) - // Undo the removal - properties[index] = rollbackProperty - } - - } catch { - print("Failed with error: ", error) - // Undo the removal - properties[index] = rollbackProperty - } - } -} - -``` - -#### [View] - -```swift -class RealEstatePropertyContainerViewModel: ObservableObject { - @Published var properties: [RealEstateProperty] = [] - var sink: AnyCancellable? - - var propertyList = RealEstatePropertyList() - init() { - Task { - sink = await propertyList.publisher - .receive(on: DispatchQueue.main) - .sink { properties in - print("Updating property list") - self.properties = properties - } - } - } - - func loadList() { - Task { - try? await propertyList.listProperties() - } - } - func createPropertyButtonTapped(name: String) { - Task { - await propertyList.createProperty(name: name) - } - } - - func updatePropertyButtonTapped(_ property: RealEstateProperty) { - Task { - await propertyList.updateProperty(property) - } - } - - func deletePropertyButtonTapped(_ property: RealEstateProperty) { - Task { - await propertyList.deleteProperty(property) - } - } -} - -struct RealEstatePropertyContainerView: View { - @StateObject var viewModel = RealEstatePropertyContainerViewModel() - @State private var propertyName: String = "" - - var body: some View { - VStack { - ScrollView { - LazyVStack(alignment: .leading) { - ForEach($viewModel.properties) { $property in - HStack { - TextField("Update property name", text: $property.name) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .multilineTextAlignment(.center) - Button("Update") { - viewModel.updatePropertyButtonTapped(property) - } - Button { - viewModel.deletePropertyButtonTapped(property) - } label: { - Image(systemName: "xmark") - .foregroundColor(.red) - } - - }.padding(.horizontal) - } - } - }.refreshable { - viewModel.loadList() - } - TextField("New property name", text: $propertyName) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .multilineTextAlignment(.center) - - Button("Save") { - viewModel.createPropertyButtonTapped(name: propertyName) - self.propertyName = "" - } - .buttonStyle(TappedButtonStyle()) - }.task { - viewModel.loadList() - } - } -} - -struct RealEstatePropertyContainerView_Previews: PreviewProvider { - static var previews: some View { - RealEstatePropertyContainerView() - } -} -``` - - - ---- - ---- -title: "Connect to AWS AppSync Events" -section: "build-a-backend/data" -platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue", "android", "swift"] -gen: 2 -last-updated: "2025-10-03T18:57:52.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/connect-event-api/" ---- - - -This guide walks through how you can connect to AWS AppSync Events using the Amplify library. - - - -This guide walks through how you can connect to AWS AppSync Events, with or without Amplify. - - -AWS AppSync Events lets you create secure and performant serverless WebSocket APIs that can broadcast real-time event data to millions of subscribers, without you having to manage connections or resource scaling. With this feature, you can build multi-user features such as a collaborative document editors, chat apps, and live polling systems. - -Learn more about AWS AppSync Events by visiting the [Developer Guide](https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html). - - -## Install the AWS AppSync Events library - -#### [Without Amplify] - -Add the `aws-sdk-appsync-events` dependency to your app/build.gradle.kts file. - -```kotlin title="app/build.gradle.kts" -dependencies { - // highlight-start - // Adds the AWS AppSync Events library without adding any Amplify dependencies - implementation("com.amazonaws:aws-sdk-appsync-events:ANDROID_APPSYNC_SDK_VERSION") - // highlight-end -} -``` - -#### [With Amplify] - -Add the `aws-sdk-appsync-events` and `aws-sdk-appsync-amplify` dependencies to your app/build.gradle.kts file. - -```kotlin title="app/build.gradle.kts" -dependencies { - // highlight-start - // Adds the AWS AppSync Events library - implementation("com.amazonaws:aws-sdk-appsync-events:ANDROID_APPSYNC_SDK_VERSION") - // Contains AppSync Authorizers which connect to Amplify Auth - implementation("com.amazonaws:aws-sdk-appsync-amplify:ANDROID_APPSYNC_SDK_VERSION") - // highlight-end -} -``` - -### Providing AppSync Authorizers - -#### [Without Amplify] - -The AWS AppSync Events library imports a number of Authorizer classes to match the various authorization strategies that may be used for your Events API. You should choose the appropriate Authorizer type for your authorization strategy. - -* `apiKey` authorization, **APIKeyAuthorizer** -* `identityPool` authorization, **IAMAuthorizer** -* `userPool` authorization, **AuthTokenAuthorizer** - -You can create as many Events clients as necessary if you require multiple authorization types. - -#### API KEY - -An `ApiKeyAuthorizer` can be used with a hardcoded API key or by fetching the key from some source.: - -```kotlin -// highlight-start -// Use a hard-coded API key -val authorizer = ApiKeyAuthorizer("[API_KEY]") -//highlight-end -// or -// highlight-start -// Fetch the API key from some source. This function may be called many times, -// so it should implement appropriate caching internally. -val authorizer = ApiKeyAuthorizer { fetchApiKey() } -//highlight-end -``` - -#### AMAZON COGNITO USER POOLS - -When working directly with AppSync, you must implement the token fetching yourself. - -```kotlin -// highlight-start -// Use your own token fetching. This function may be called many times, -// so it should implement appropriate caching internally. -val authorizer = AuthTokenAuthorizer { - fetchLatestAuthToken() -} -//highlight-end -``` - -#### AWS IAM - -When working directly with AppSync, you must implement the request signing yourself. - -```kotlin -// highlight-start -// Provide an implementation of the signing function. This function should implement the -// AWS Sig-v4 signing logic and return the authorization headers containing the token and signature. -val authorizer = IamAuthorizer { appSyncRequest -> signRequestAndReturnHeaders(appSyncRequest) } -// highlight-end -``` - -#### [With Amplify] - -The AWS AppSync Events library imports a number of Authorizer classes to match the various authorization strategies that may be used for your Events API. You should choose the appropriate Authorizer type for your authorization strategy. - -* `apiKey` authorization, **APIKeyAuthorizer** -* `identityPool` authorization, **AmplifyIAMAuthorizer** -* `userPool` authorization, **AmplifyUserPoolAuthorizer** - -You can create as many Events clients as necessary if you require multiple authorization types. - -#### API KEY - -An `ApiKeyAuthorizer` can provide a hardcoded API key, or fetch the API key from some source: - -```kotlin -// highlight-start -// Use a hard-coded API key -val authorizer = ApiKeyAuthorizer("[API_KEY]") -//highlight-end -// or -// highlight-start -// Fetch the API key from some source. This function may be called many times, -// so it should implement appropriate caching internally. -val authorizer = ApiKeyAuthorizer { fetchApiKey() } -//highlight-end -``` - -#### AMAZON COGNITO USER POOLS - -The `AmplifyUserPoolAuthorizer` uses your configured Amplify instance to fetch AWS Cognito UserPool tokens and attach to the request. - -```kotlin -// highlight-start -// Using the provided Amplify UserPool Authorizer -val authorizer = AmplifyUserPoolAuthorizer() -//highlight-end -``` - -#### AWS IAM - -The `AmplifyIAMAuthorizer` uses your configured Amplify instance to sign a request with the IAM SigV4 protocol. - -```kotlin -// highlight-start -// Using the provided Amplify IAM Authorizer -val authorizer = AmplifyIamAuthorizer("{REGION}") -//highlight-end -``` - - - - -## Install the AWS AppSync Events library - -#### [Without Amplify] - -- Add `AWS AppSync Events Library for Swift` into your project using Swift Package Manager. - -- Enter its Github URL (https://github.com/aws-amplify/aws-appsync-events-swift), select `Up to Next Major Version` and click `Add Package`. - -- Select the following product and add it to your target: - - `AWSAppSyncEvents` - -#### [With Amplify] - -- Add `AWS AppSync Events Library for Swift` into your project using Swift Package Manager. - - Enter its Github URL (https://github.com/aws-amplify/aws-appsync-events-swift), select `Up to Next Major Version` and click `Add Package`. - - Select the following product and add it to your target: - - `AWSAppSyncEvents` - -- Add `Amplify Library for Swift` into your project using Swift Package Manager. - - Enter its Github URL (https://github.com/aws-amplify/amplify-swift), select `Up to Next Major Version` and click `Add Package`. - - Select the following product and add it to your target: - - `Amplify` - - `AWSCognitoAuthPlugin` - -### Providing AppSync Authorizers - -#### [Without Amplify] - -The AWS AppSync Events library imports a number of Authorizer classes to match the various authorization strategies that may be used for your Events API. You should choose the appropriate Authorizer type for your authorization strategy. - -* API KEY authorization, **APIKeyAuthorizer** -* AWS IAM authorization, **IAMAuthorizer** -* AMAZON COGNITO USER POOLS authorization, **AuthTokenAuthorizer** - -You can create as many `Events` clients as necessary if you require multiple authorization types. - -#### API KEY - -An `APIKeyAuthorizer` can be used with a hardcoded API key or by fetching the key from some source. - -```swift -// highlight-start -// Use a hard-coded API key -let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") -//highlight-end -// or -// highlight-start -// Fetch the API key from some source. This function may be called many times, -// so it should implement appropriate caching internally. -let authorizer = APIKeyAuthorizer(fetchAPIKey: { - // fetch your api key - }) -//highlight-end -``` - -#### AMAZON COGNITO USER POOLS - -When working directly with AppSync, you must implement the token fetching yourself. - -```swift -// highlight-start -// Use your own token fetching. This function may be called many times, -// so it should implement appropriate caching internally. -let authorizer = AuthTokenAuthorizer(fetchLatestAuthToken: { - // fetch your auth token -}) -//highlight-end -``` - -#### AWS IAM - -When working directly with AppSync, you must implement the request signing yourself. - -```swift -// highlight-start -// Provide an implementation of the signing function. This function should implement the -// AWS Sig-v4 signing logic and return the authorization headers containing the token and signature. -let authorizer = IAMAuthorizer(signRequest: { - // implement your `URLRequest` signing logic -}) -// highlight-end -``` - -#### [With Amplify] - -The AWS AppSync Events library imports a number of Authorizer classes to match the various authorization strategies that may be used for your Events API. You should choose the appropriate Authorizer type for your authorization strategy. - -* API KEY authorization, **APIKeyAuthorizer** -* AWS IAM authorization, **IAMAuthorizer** -* AMAZON COGNITO USER POOLS authorization, **AuthTokenAuthorizer** - -You can create as many `Events` clients as necessary if you require multiple authorization types. - -#### API KEY - -An `APIKeyAuthorizer` can be used with a hardcoded API key or by fetching the key from some source. - -```swift -// highlight-start -// Use a hard-coded API key -let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") -//highlight-end -// or -// highlight-start -// Fetch the API key from some source. This function may be called many times, -// so it should implement appropriate caching internally. -let authorizer = APIKeyAuthorizer(fetchAPIKey: { - // fetch your api key - }) -//highlight-end -``` - -#### AMAZON COGNITO USER POOLS - -If you are using Amplify Auth, you can create a method that retrieves the Cognito access token. - -```swift -import Amplify - -func getUserPoolAccessToken() async throws -> String { - let authSession = try await Amplify.Auth.fetchAuthSession() - if let result = (authSession as? AuthCognitoTokensProvider)?.getCognitoTokens() { - switch result { - case .success(let tokens): - return tokens.accessToken - case .failure(let error): - throw error - } - } - throw AuthError.unknown("Did not receive a valid response from fetchAuthSession for get token.") -} -``` - -Then create the `AuthTokenAuthorizer` with this method. - -```swift -let authorizer = AuthTokenAuthorizer(fetchLatestAuthToken: getUserPoolAccessToken) -``` - -#### AWS IAM - -If you are using Amplify Auth, you can initialize `IAMAuthorizer` with a helper method from `AWSCognitoAuthPlugin` like below: - -```swift -let authorizer = IAMAuthorizer( - signRequest: AWSCognitoAuthPlugin.createAppSyncSigner(region: "region") -) -``` - - - -## Connect to an Event API without an existing Amplify backend - -Before you begin, you will need: - -- An Event API created via the AWS Console -- Take note of: HTTP endpoint, region, API Key - - -Thats it! Skip to [Client Library Usage Guide](#client-library-usage-guide). - - - -```tsx title="src/App.tsx" -import type { EventsChannel } from 'aws-amplify/data'; -import { useState, useEffect, useRef } from 'react'; -import { Amplify } from 'aws-amplify'; -import { events } from 'aws-amplify/data'; - -Amplify.configure({ - API: { - Events: { - endpoint: - 'https://abcdefghijklmnopqrstuvwxyz.appsync-api.us-east-1.amazonaws.com/event', - region: 'us-east-1', - defaultAuthMode: 'apiKey', - apiKey: 'da2-abcdefghijklmnopqrstuvwxyz', - }, - }, -}); - -export default function App() { - const [myEvents, setMyEvents] = useState([]); - - const sub = useRef>(null); - - useEffect(() => { - let channel: EventsChannel; - - const connectAndSubscribe = async () => { - channel = await events.connect('default/channel'); - - if (!sub.current) { - sub.current = channel.subscribe({ - next: (data) => { - console.log('received', data); - setMyEvents((prev) => [data, ...prev]); - }, - error: (err) => console.error('error', err), - }); - } - }; - - connectAndSubscribe(); - - return () => { - sub.current?.unsubscribe(); - sub.current = null; - return channel?.close(); - }; - }, []); - - async function publishEvent() { - // Publish via HTTP POST - await events.post('default/channel', { some: 'data' }); - - // Alternatively, publish events through the WebSocket channel - const channel = await events.connect('default/channel'); - await channel.publish({ some: 'data' }); - } - - return ( - <> - -
      - {myEvents.map((data, idx) => ( -
    • {JSON.stringify(data.event, null, 2)}
    • - ))} -
    - - ); -} -``` - - -## Add an Event API to an existing Amplify backend - -This guide walks through how you can add an Event API to an existing Amplify backend. We'll be using Cognito User Pools for authenticating with Event API from our frontend application. Any signed in user will be able to subscribe to the Event API and publish events. - -Before you begin, you will need: - -- An existing Amplify backend (see [Quickstart](/[platform]/start/quickstart/)) -- Latest versions of `@aws-amplify/backend` and `@aws-amplify/backend-cli` (`npm add @aws-amplify/backend@latest @aws-amplify/backend-cli@latest`) - -### Update Backend Definition - -First, we'll add a new Event API to our backend definition. - - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { auth } from './auth/resource'; -// highlight-start -// import CDK resources: -import { - CfnApi, - CfnChannelNamespace, - AuthorizationType, -} from 'aws-cdk-lib/aws-appsync'; -import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; -// highlight-end - -const backend = defineBackend({ - auth, -}); - -// highlight-start -// create a new stack for our Event API resources: -const customResources = backend.createStack('custom-resources'); - -// add a new Event API to the stack: -const cfnEventAPI = new CfnApi(customResources, 'CfnEventAPI', { - name: 'my-event-api', - eventConfig: { - authProviders: [ - { - authType: AuthorizationType.USER_POOL, - cognitoConfig: { - awsRegion: customResources.region, - // configure Event API to use the Cognito User Pool provisioned by Amplify: - userPoolId: backend.auth.resources.userPool.userPoolId, - }, - }, - ], - // configure the User Pool as the auth provider for Connect, Publish, and Subscribe operations: - connectionAuthModes: [{ authType: AuthorizationType.USER_POOL }], - defaultPublishAuthModes: [{ authType: AuthorizationType.USER_POOL }], - defaultSubscribeAuthModes: [{ authType: AuthorizationType.USER_POOL }], - }, -}); - -// create a default namespace for our Event API: -const namespace = new CfnChannelNamespace( - customResources, - 'CfnEventAPINamespace', - { - apiId: cfnEventAPI.attrApiId, - name: 'default', - } -); - -// attach a policy to the authenticated user role in our User Pool to grant access to the Event API: -backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy( - new Policy(customResources, 'AppSyncEventPolicy', { - statements: [ - new PolicyStatement({ - actions: [ - 'appsync:EventConnect', - 'appsync:EventSubscribe', - 'appsync:EventPublish', - ], - resources: [`${cfnEventAPI.attrApiArn}/*`, `${cfnEventAPI.attrApiArn}`], - }), - ], - }) -); - -// finally, add the Event API configuration to amplify_outputs: -backend.addOutput({ - custom: { - events: { - url: `https://${cfnEventAPI.getAtt('Dns.Http').toString()}/event`, - aws_region: customResources.region, - default_authorization_type: AuthorizationType.USER_POOL, - }, - }, -}); -// highlight-end -``` - - - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { auth } from './auth/resource'; -// highlight-start -// import CDK resources: -import { - CfnApi, - CfnChannelNamespace, - AuthorizationType, -} from 'aws-cdk-lib/aws-appsync'; -import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; -// highlight-end - -const backend = defineBackend({ - auth, -}); - -// highlight-start -// create a new stack for our Event API resources: -const customResources = backend.createStack('custom-resources'); - -// add a new Event API to the stack: -const cfnEventAPI = new CfnApi(customResources, 'CfnEventAPI', { - name: 'my-event-api', - eventConfig: { - authProviders: [ - { - authType: AuthorizationType.USER_POOL, - cognitoConfig: { - awsRegion: customResources.region, - // configure Event API to use the Cognito User Pool provisioned by Amplify: - userPoolId: backend.auth.resources.userPool.userPoolId, - }, - }, - ], - // configure the User Pool as the auth provider for Connect, Publish, and Subscribe operations: - connectionAuthModes: [{ authType: AuthorizationType.USER_POOL }], - defaultPublishAuthModes: [{ authType: AuthorizationType.USER_POOL }], - defaultSubscribeAuthModes: [{ authType: AuthorizationType.USER_POOL }], - }, -}); - -// create a default namespace for our Event API: -const namespace = new CfnChannelNamespace( - customResources, - 'CfnEventAPINamespace', - { - apiId: cfnEventAPI.attrApiId, - name: 'default', - } -); - -// attach a policy to the authenticated user role in our User Pool to grant access to the Event API: -backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy( - new Policy(customResources, 'AppSyncEventPolicy', { - statements: [ - new PolicyStatement({ - actions: [ - 'appsync:EventConnect', - 'appsync:EventSubscribe', - 'appsync:EventPublish', - ], - resources: [`${cfnEventAPI.attrApiArn}/*`, `${cfnEventAPI.attrApiArn}`], - }), - ], - }) -); -// highlight-end -``` - - -### Deploy Backend - -To test your changes, deploy your Amplify Sandbox. - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox -``` - - -### Connect your frontend application - -After the sandbox deploys, connect your frontend application to the Event API. We'll be using the [Amplify Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator) to sign in to our Cognito User Pool. - -If you don't already have the Authenticator installed, you can install it by running `npm add @aws-amplify/ui-react`. - -```tsx title="src/App.tsx" -import { useEffect, useState } from 'react'; -import { Amplify } from 'aws-amplify'; -import { events, type EventsChannel } from 'aws-amplify/data'; -import { Authenticator } from '@aws-amplify/ui-react'; -import '@aws-amplify/ui-react/styles.css'; -import outputs from '../amplify_outputs.json'; - -Amplify.configure(outputs); - -export default function App() { - const [myEvents, setMyEvents] = useState[]>([]); - - useEffect(() => { - let channel: EventsChannel; - - const connectAndSubscribe = async () => { - channel = await events.connect('default/channel'); - - channel.subscribe({ - next: (data) => { - console.log('received', data); - setMyEvents((prev) => [data, ...prev]); - }, - error: (err) => console.error('error', err), - }); - }; - - connectAndSubscribe(); - - return () => channel && channel.close(); - }, []); - - async function publishEvent() { - // Publish via HTTP POST - await events.post('default/channel', { some: 'data' }); - - // Alternatively, publish events through the WebSocket channel - const channel = await events.connect('default/channel'); - await channel.publish({ some: 'data' }); - } - - return ( - - {({ signOut, user }) => ( - <> -
    -

    Welcome, {user.username}

    - -
    -
    - -
      - {myEvents.map((data) => ( -
    • {JSON.stringify(data.event)}
    • - ))} -
    -
    - - )} -
    - ); -} -``` - - - -## Client Library Usage Guide - - - -### Create the Events class - -You can find your endpoint in the AWS AppSync Events console. It should start with `https` and end with `/event`. - -```kotlin -val endpoint = "https://abcdefghijklmnopqrstuvwxyz.appsync-api.us-east-1.amazonaws.com/event" -val events = Events(endpoint) -``` - -### Using the REST Client - -An `EventsRestClient` can be created to publish event(s) over REST. It accepts a publish authorizer that will be used by default for any publish calls within the client. - -#### Creating the REST Client - -``` kotlin -val events: Events // Your configured Events -val restClient = events.createRestClient( - publishAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") -) -``` - -Additionally, you can pass custom options to the Rest Client. Current capabilities include modifying the `OkHttp.Builder` the `EventsRestClient` uses, and enabling client library logs. -See [Collecting Client Library Logs](#collecting-client-library-logs) for the AndroidLogger class. - -``` kotlin -val restClient = events.createRestClient( - publishAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz"), - options = Events.Options.Rest( - loggerProvider = { namespace -> AndroidLogger(namespace, logLevel) }, - okHttpConfigurationProvider = { okHttpBuilder -> - // update OkHttp.Builder used by EventsRestClient - } - ) -) -``` - -#### Publish a Single Event - -```kotlin -val restClient: EventsRestClient // Your configured EventsRestClient -// kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] -val jsonEvent = JsonObject(mapOf("some" to JsonPrimitive("data"))) -val publishResult = restClient.publish( - channelName = "default/channel", - event = jsonEvent -) -when(publishResult) { - is PublishResult.Response -> { - val successfulEvents = publishResult.successfulEvents // inspect successful events - val failedEvents = publishResult.failedEvents // inspect failed events - } - is PublishResult.Failure -> { - val error = result.error // publish failure, inspect error - } -} -``` - -#### Publish multiple events - -You can publish up to 5 events at a time. - -```kotlin -val restClient: EventsRestClient // Your configured EventsRestClient -// List of kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] -val jsonEvents = listOf( - JsonObject(mapOf("some" to JsonPrimitive("data1"))), - JsonObject(mapOf("some" to JsonPrimitive("data2"))) -) -val publishResult = restClient.publish( - channelName = "default/channel", - events = jsonEvents -) -when(publishResult) { - is PublishResult.Response -> { - val successfulEvents = publishResult.successfulEvents // inspect successful events - val failedEvents = publishResult.failedEvents // inspect failed events - } - is PublishResult.Failure -> { - val error = result.error // publish failure, inspect error - } -} -``` - -#### Publish with a different authorizer - -```kotlin -restClient.publish( - channelName = "default/channel", - event = JsonObject(mapOf("some" to JsonPrimitive("data"))), - authorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") -) -``` - -### Using the WebSocket Client - -An `EventsWebSocketClient` can be created to publish and subscribe to channels. The WebSocket connection is managed by the library and connects on the first subscribe or publish operation. Once connected, the WebSocket will remain open. You should explicitly disconnect the client when you no longer need to subscribe or publish to channels. - -#### Creating the WebSocket Client - -```kotlin -val events: Events // Your configured Events -val apiKeyAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") -val webSocketClient = events.createWebSocketClient( - connectAuthorizer = apiKeyAuthorizer, // used to connect the websocket - subscribeAuthorizer = apiKeyAuthorizer, // used for subscribe calls - publishAuthorizer = apiKeyAuthorizer // used for publish calls -) -``` - -Additionally, you can pass custom options to the WebSocket Client. Current capabilities include modifying the `OkHttp.Builder` the `EventsWebSocketClient` uses, and enabling client library logs. -See [Collecting Client Library Logs](#collecting-client-library-logs) for the AndroidLogger class. - -```kotlin -val events: Events // Your configured Events -val apiKeyAuthorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") -val webSocketClient = events.createWebSocketClient( - connectAuthorizer = apiKeyAuthorizer, // used to connect the websocket - subscribeAuthorizer = apiKeyAuthorizer, // used for subscribe calls - publishAuthorizer = apiKeyAuthorizer // used for publish calls, - options = Events.Options.WebSocket( - loggerProvider = { namespace -> AndroidLogger(namespace, logLevel) }, - okHttpConfigurationProvider = { okHttpBuilder -> - // update OkHttpBuilder used by EventsWebSocketClient - } - ) -) -``` - -#### Publish a Single Event - -```kotlin -val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient -// kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] -val jsonEvent = JsonObject(mapOf("some" to JsonPrimitive("data"))) -val publishResult = webSocketClient.publish( - channelName = "default/channel", - event = jsonEvent -) -when(publishResult) { - is PublishResult.Response -> { - val successfulEvents = publishResult.successfulEvents // inspect successful events - val failedEvents = publishResult.failedEvents // inspect failed events - } - is PublishResult.Failure -> { - val error = result.error // publish failure, inspect error - } -} -``` - -#### Publish multiple Events - -You can publish up to 5 events at a time. - -```kotlin -val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient -// List of kotlinx.serialization.json.[JsonElement, JsonPrimitive, JsonArray, JsonObject] -val jsonEvents = listOf( - JsonObject(mapOf("some" to JsonPrimitive("data1"))), - JsonObject(mapOf("some" to JsonPrimitive("data2"))) -) -val publishResult = webSocketClient.publish( - channelName = "default/channel", - events = jsonEvents -) -when(publishResult) { - is PublishResult.Response -> { - val successfulEvents = publishResult.successfulEvents // inspect successful events - val failedEvents = publishResult.failedEvents // inspect failed events - } - is PublishResult.Failure -> { - val error = result.error // publish failure, inspect error - } -} -``` - -#### Publish with a different authorizer - -```kotlin -val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient -val jsonEvent = JsonObject(mapOf("some" to JsonPrimitive("data"))) -val publishResult = webSocketClient.publish( - channelName = "default/channel", - event = jsonEvent, - authorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") -) -when(publishResult) { - is PublishResult.Response -> { - val successfulEvents = publishResult.successfulEvents // inspect successful events - val failedEvents = publishResult.failedEvents // inspect failed events - } - is PublishResult.Failure -> { - val error = result.error // publish failure, inspect error - } -} -``` - -#### Subscribing to a channel. - -When subscribing to a channel, you can subscribe to a specific namespace/channel (ex: "default/channel"), or you can specify a wildcard (`*`) at the end of a channel path to receive events published to all channels that match (ex: "default/*"). - -Choosing the proper Coroutine Scope for the subscription Flow is critical. You should choose a scope to live for the lifetime in which you need the subscription. For example, if you want a subscription to be tied to a screen, you should consider using `viewModelScope` from an AndroidX `ViewModel`. The subscription Flow would persist configuration changes and be cancelled when `onCleared()` is triggered on the ViewModel. -When the Flow's Coroutine Scope is cancelled, the library will unsubscribe from the channel. - -```kotlin -coroutineScope.launch { - // subscribe returns a cold Flow. The subscription is established once a terminal operator is invoked. - val subscription: Flow = webSocketClient.subscribe("default/channel").onCompletion { - // Subscription has been unsubscribed - }.catch { throwable -> - // Subscription encountered an error and has been unsubscribed - // See throwable for cause - } - - // collect starts the subscription - subscription.collect { eventsMessage -> - // Returns a JsonElement type. - // Use Kotlin Serialization to convert into your preferred data structure. - val jsonData = eventsMessage.data - } -} -``` - -#### Subscribing to a channel with a different authorizer. - -```kotlin -coroutineScope.launch { - // subscribe returns a cold Flow. The subscription is established once a terminal operator is invoked. - val subscription: Flow = webSocketClient.subscribe( - channelName = "default/channel". - authorizer = ApiKeyAuthorizer("da2-abcdefghijklmnopqrstuvwxyz") - ).onCompletion { - // Subscription has been unsubscribed - }.catch { throwable -> - // Subscription encountered an error and has been unsubscribed - // See throwable for cause - } - - // collect starts the subscription - subscription.collect { eventsMessage -> - // Returns a JsonElement type. - val jsonData = eventsMessage.data - // Use Kotlin Serialization to convert into your preferred data structure. - } -} -``` - -#### Disconnecting the WebSocket - -When you are done using the WebSocket and do not intend to call publish/subscribe on the client, you should disconnect the WebSocket. This will unsubscribe all channels. - -```kotlin -val webSocketClient: EventsWebSocketClient // Your configured EventsWebSocketClient -// set flushEvents to true if you want to wait for any pending publish operations to post to the WebSocket -// set flushEvents to false to immediately disconnect, discarding any pending posts to the WebSocket -webSocketClient.disconnect(flushEvents = true) // or false to immediately disconnect -``` - - - -### Create the Events class - -You can find your endpoint in the AWS AppSync Events console. It should start with `https` and end with `/event`. - -```swift -let eventsEndpoint = "https://abcdefghijklmnopqrstuvwxyz.appsync-api.us-east-1.amazonaws.com/event" -let events = Events(endpointURL: eventsEndpoint) -``` - -### Using the REST Client - -An `EventsRestClient` can be created to publish event(s) over REST. It accepts a publish authorizer that will be used by default for any publish calls within the client. - -#### Creating the REST Client - -```swift -let events = Events(endpointURL: eventsEndpoint) -let restClient = events.createRestClient( - publishAuthorizer: APIKeyAuthorizer(apiKey: "apiKey") -) -``` - -Additionally, you can pass custom options to the Rest Client. Current capabilities include passing a custom `URLSessionConfiguration` object, a prepend `URLRequestInterceptor` and enabling client library logs. -See [Collecting Client Library Logs](#collecting-client-library-logs) and `RestOptions` class for more details. - -```swift -let restClient = events.createRestClient( - publishAuthorizer: apiKeyAuthorizer, - options: .init( - urlSessionConfiguration: urlSessionConfiguration, // your instance of `URLSessionConfiguration` - logger: AppSyncEventsLogger(), // your implementation of `EventsLogger` - interceptor: AppSyncEventsURLRequestInterceptor() // your implementation of `URLRequestInterceptor` - ) -) -``` - -#### Publish a single event - -```swift -let defaultChannel = "default/channel" -let event = JSONValue(stringLiteral: "123") -do { - let result = try await restClient.publish( - channelName: defaultChannel, - event: event - ) - print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") -} catch { - print("Publish failure with error: \(error)") -} -``` - -#### Publish multiple events - -You can publish up to 5 events at a time. - -```swift -let defaultChannel = "default/channel" -let eventsList = [ - JSONValue(stringLiteral: "123"), - JSONValue(booleanLiteral: true), - JSONValue(floatLiteral: 1.25), - JSONValue(integerLiteral: 37), - JSONValue(dictionaryLiteral: ("key", "value")) -] - -do { - let result = try await restClient.publish( - channelName: defaultChannel, - events: eventsList - ) - print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") -} catch { - print("Publish failure with error: \(error)") -} -``` - -#### Publish with a different authorizer - -```swift -let defaultChannel = "default/channel" -let event = JSONValue(stringLiteral: "123") -let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") -do { - let result = try await restClient.publish( - channelName: defaultChannel, - event: event, - authorizer: apiKeyAuthorizer - ) - print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") -} catch { - print("Publish failure with error: \(error)") -} -``` - -### Using the WebSocket Client - -An `EventsWebSocketClient` can be created to publish and subscribe to channels. The WebSocket connection is managed by the library and connects on the first subscribe or publish operation. Once connected, the WebSocket will remain open. You should explicitly disconnect the client when you no longer need to subscribe or publish to channels. - -#### Creating the WebSocket Client - -```swift -let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") -let webSocketClient = events.createWebSocketClient( - connectAuthorizer: apiKeyAuthorizer, - publishAuthorizer: apiKeyAuthorizer, - subscribeAuthorizer: apiKeyAuthorizer -) -``` - -Additionally, you can pass custom options to the WebSocket Client. Current capabilities include passing a custom `URLSessionConfiguration` object, a prepend `URLRequestInterceptor` and enabling client library logs. -See [Collecting Client Library Logs](#collecting-client-library-logs) and `WebSocketOptions` class for more details. - -```swift -let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") -let webSocketClient = events.createWebSocketClient( - connectAuthorizer: apiKeyAuthorizer, - publishAuthorizer: apiKeyAuthorizer, - subscribeAuthorizer: apiKeyAuthorizer, - options: .init( - urlSessionConfiguration: urlSessionConfiguration, // your instance of `URLSessionConfiguration` - logger: AppSyncEventsLogger(), // your implementation of `EventsLogger` - interceptor: AppSyncEventsURLRequestInterceptor() // your implementation of `URLRequestInterceptor` - ) -) -``` - -#### Publish a Single Event - -```swift -let defaultChannel = "default/channel" -let event = JSONValue(stringLiteral: "123") -do { - let result = try await websocketClient.publish( - channelName: defaultChannel, - event: event - ) - print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") -} catch { - print("Publish failure with error: \(error)") -} -``` - -#### Publish multiple Events - -You can publish up to 5 events at a time. - -```swift -let defaultChannel = "default/channel" -let eventsList = [ - JSONValue(stringLiteral: "123"), - JSONValue(booleanLiteral: true), - JSONValue(floatLiteral: 1.25), - JSONValue(integerLiteral: 37), - JSONValue(dictionaryLiteral: ("key", "value")) -] - -do { - let result = try await websocketClient.publish( - channelName: defaultChannel, - events: eventsList - ) - print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") -} catch { - print("Publish failure with error: \(error)") -} -``` - -#### Publish with a different authorizer - -```swift -let defaultChannel = "default/channel" -let event = JSONValue(stringLiteral: "123") -let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") -do { - let result = try await websocketClient.publish( - channelName: defaultChannel, - event: event, - authorizer: apiKeyAuthorizer - ) - print("Publish success with result:\n status: \(result.status) \n, successful events: \(result.successfulEvents) \n, failed events: \(result.failedEvents)") -} catch { - print("Publish failure with error: \(error)") -} -``` - -#### Subscribing to a channel - -When subscribing to a channel, you can subscribe to a specific namespace/channel (e.g. `default/channel`), or you can specify a wildcard (`*`) at the end of a channel path to receive events published to all channels that match (e.g. `default/*`). - -```swift -let defaultChannel = "default/channel" -let subscription = try websocketClient.subscribe(channelName: defaultChannel) -let task = Task { - for try await message in subscription { - print("Subscription received message: \(message))" - } -} -``` -To unsubscribe from the channel, you can cancel the enclosing task for the `AsyncThrowingStream`. - -```swift -task.cancel() -``` - -#### Subscribing to a channel with a different authorizer - -```swift -let defaultChannel = "default/channel" -let apiKeyAuthorizer = APIKeyAuthorizer(apiKey: "apiKey") -let subscription = try websocketClient.subscribe( - channelName: defaultChannel, - authorizer: apiKeyAuthorizer -) -let task = Task { - for try await message in subscription { - print("Subscription received message: \(message))" - } -} -``` - -#### Disconnecting the WebSocket - -When you are done using the WebSocket and do not intend to call publish/subscribe on the client, you should disconnect the WebSocket. This will unsubscribe all channels. - -```swift -// set flushEvents to true if you want to wait for any pending publish operations to post to the WebSocket -// set flushEvents to false to immediately disconnect, discarding any pending posts to the WebSocket -try await webSocketClient.disconnect(flushEvents: true) // or false to immediately disconnect -``` - - - -### Collecting Client Library Logs - - - -In the Rest Client and WebSocket Client examples, we demonstrated logging to a custom logger. Here is an example of a custom logger that writes logs to Android's Logcat. You are free to implement your own `Logger` type. - -```kotlin -class AndroidLogger( - private val namespace: String, - override val thresholdLevel: LogLevel -) : Logger { - - override fun error(message: String) { - if (!thresholdLevel.above(LogLevel.ERROR)) { - Log.e(namespace, message) - } - } - - override fun error(message: String, error: Throwable?) { - if (!thresholdLevel.above(LogLevel.ERROR)) { - Log.e(namespace, message, error) - } - } - - override fun warn(message: String) { - if (!thresholdLevel.above(LogLevel.WARN)) { - Log.w(namespace, message) - } - } - - override fun warn(message: String, issue: Throwable?) { - if (!thresholdLevel.above(LogLevel.WARN)) { - Log.w(namespace, message, issue) - } - } - - override fun info(message: String) { - if (!thresholdLevel.above(LogLevel.INFO)) { - Log.i(namespace, message) - } - } - - override fun debug(message: String) { - if (!thresholdLevel.above(LogLevel.DEBUG)) { - Log.d(namespace, message) - } - } - - override fun verbose(message: String) { - if (!thresholdLevel.above(LogLevel.VERBOSE)) { - Log.v(namespace, message) - } - } -} -``` - - - -In the Rest Client and WebSocket Client examples, we demonstrated logging to a custom logger. Here is an example of a custom logger that writes logs to Xcode console. You are free to implement your own `EventsLogger` type. - -```swift -import os -import Foundation -import AWSAppSyncEvents - -public final class AppSyncEventsLogger: EventsLogger { - static let lock: NSLocking = NSLock() - - static var _logLevel = LogLevel.error - - public init() { } - - public var logLevel: LogLevel { - get { - AppSyncEventsLogger.lock.lock() - defer { - AppSyncEventsLogger.lock.unlock() - } - - return AppSyncEventsLogger._logLevel - } - set { - AppSyncEventsLogger.lock.lock() - defer { - AppSyncEventsLogger.lock.unlock() - } - - AppSyncEventsLogger._logLevel = newValue - } - } - - public func error(_ log: @autoclosure () -> String) { - os_log("%@", type: .error, log()) - } - - public func error(_ error: @autoclosure () -> Error) { - os_log("%@", type: .error, error().localizedDescription) - } - - public func warn(_ log: @autoclosure () -> String) { - guard logLevel.rawValue >= LogLevel.warn.rawValue else { - return - } - - os_log("%@", type: .info, log()) - } - - public func info(_ log: @autoclosure () -> String) { - guard logLevel.rawValue >= LogLevel.info.rawValue else { - return - } - - os_log("%@", type: .info, log()) - } - - public func debug(_ log: @autoclosure () -> String) { - guard logLevel.rawValue >= LogLevel.debug.rawValue else { - return - } - - os_log("%@", type: .debug, log()) - } - - public func verbose(_ log: @autoclosure () -> String) { - guard logLevel.rawValue >= LogLevel.verbose.rawValue else { - return - } - - os_log("%@", type: .debug, log()) - } -} - -``` - - ---- - ---- -title: "Modify Amplify-generated AWS resources" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-10-15T16:14:40.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/override-resources/" ---- - -Amplify GraphQL API uses a variety of auto-generated, underlying AWS services and resources. You can customize these underlying resources to optimize the deployed stack for your specific use case. - -In your Amplify app, you can access every underlying resource using CDK ["L2"](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_using) or ["L1"](https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_l1_using) constructs. Access the generated resources as L2 constructs via the `.resources` property on the returned stack or access the generated resources as L1 constructs using the `.resources.cfnResources` property. - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { data } from './data/resource'; - -const backend = defineBackend({ - data -}); - -const { cfnResources } = backend.data.resources; - -for (const table of Object.values(cfnResources.amplifyDynamoDbTables)) { - table.pointInTimeRecoveryEnabled = true; -} -``` - -## Customize Amplify-generated AppSync GraphQL API resources - -Apply all the customizations on `backend.data.resources.graphqlApi` or `backend.data.resources.cfnResources.cfnGraphqlApi`. For example, to enable X-Ray tracing for the AppSync GraphQL API: - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { data } from './data/resource'; - -const backend = defineBackend({ - data -}); - -const { cfnResources } = backend.data.resources; - -cfnResources.cfnGraphqlApi.xrayEnabled = true; -``` - -## Customize Amplify-generated resources for data models - -Pass in the model type name into `backend.data.resources.amplifyDynamoDbTables["MODEL_NAME"]` to modify the resources generated for that particular model type. For example, to enable time-to-live on the Todo `@model` type's DynamoDB table: - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { data } from './data/resource'; - -const backend = defineBackend({ - data -}); - -const { cfnResources } = backend.data.resources; - -cfnResources.amplifyDynamoDbTables["Todo"].timeToLiveAttribute = { - attributeName: "ttl", - enabled: true, -}; -``` - -### Example - Configure billing mode on a DynamoDB table - -Set the [DynamoDB billing mode](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-billingmode) for the DynamoDB table as either "PROVISIONED" or "PAY_PER_REQUEST". - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { BillingMode } from "aws-cdk-lib/aws-dynamodb"; -import { data } from './data/resource'; - -const backend = defineBackend({ - data -}); - -const { cfnResources } = backend.data.resources; - -cfnResources.amplifyDynamoDbTables['Todo'].billingMode = BillingMode.PAY_PER_REQUEST; -``` - -### Example - Configure provisioned throughput for a DynamoDB table - -Override the default [ProvisionedThroughput](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-provisionedthroughput) provisioned for each model table and its Global Secondary Indexes (GSI). This override is only valid if the "DynamoDBBillingMode" is set to "PROVISIONED". - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { data } from './data/resource'; - -const backend = defineBackend({ - data -}); - -const { cfnResources } = backend.data.resources; - -cfnResources.amplifyDynamoDbTables["Todo"].provisionedThroughput = { - readCapacityUnits: 5, - writeCapacityUnits: 5, -}; -``` - -### Example - Enable point-in-time recovery for a DynamoDB table - -Enable/disable [DynamoDB point-in-time recovery](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-pointintimerecoveryspecification.html) for each model table. - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { data } from './data/resource'; - -const backend = defineBackend({ - data -}); - -const { cfnResources } = backend.data.resources; - -cfnResources.amplifyDynamoDbTables['Todo'].pointInTimeRecoveryEnabled = true; -``` - ---- - ---- -title: "Manage Data with Amplify console" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-08-06T19:20:15.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/manage-with-amplify-console/" ---- - -The **Data manager** page in the Amplify Console offers a user-friendly interface for managing the backend GraphQL API data of an application. It enables real-time creation and updates of application data, eliminating the need to build separate admin views. - -If you have not yet created a **data** resource, visit the [Data setup guide](/[platform]/build-a-backend/data/set-up-data/). - -## Access Data manager - -After you've deployed your data resource, you can access the manager on Amplify console. - -1. Log in to the [Amplify console](https://console.aws.amazon.com/amplify/home) and choose your app. -2. Select the branch you would like to access. -3. Select **Data** from the left navigation bar. -4. Then, select **Data manager**. - -### To create a record - -1. On the **Data manager** page, select a table from the **Select table** dropdown. For this example, we are using a *Todo* table. -2. Select **Create Todo**. -3. In the **Add Todo** pane, specify your custom values for the fields in the table. For example, enter *my first todo* for the *Content* field and toggle the *Is done* field. -4. Select **Submit**. - -### To update a record - -1. On the **Data manager** page, select a table from the **Select table** dropdown. -2. From the list of records, select a record you want to update. -3. In the **Edit Todo** pane, make any changes to your record, and then select **Submit**. - -### To delete a record(s) - -1. On the **Data manager** page, select a table from the **Select table** dropdown. -2. From the list of records, select the checkbox to the left of the record(s) you want to delete. -3. Select the **Actions** dropdown, and then select **delete item(s)** . - -### To Seed records - -1. On the **Data manager** page, select a table from the **Select table** dropdown. -2. Select the **Actions** dropdown and then select **Auto-generate data**. -3. In the **Auto-generate data** pane, specify how many rows of data you want to generate and constraints for the generated data. -4. Then select **Generate data** - -You can generate up to 100 records at a time. - -> **Warning:** Seed data cannot be generated for tables that have the following field types: AWSPhone, Enum, Custom Type, or Relationship - -### To download records - -1. On the **Data manager** page, select a table from the **Select table** dropdown. -2. Select the **Actions** dropdown. -3. Here you have two options for downloading data. - - Choose **Download selected items (.csv)** to download only the selected rows of data. - - Choose **Download all items (.csv)** to download all rows of records on the currently selected table. -4. Once you have selected a download option, your data should immediately start downloading as a CSV file. - ---- - ---- -title: "AWS AppSync Apollo Extensions" -section: "build-a-backend/data" -platforms: ["android", "swift"] -gen: 2 -last-updated: "2025-01-29T16:47:20.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/aws-appsync-apollo-extensions/" ---- - -AWS AppSync Apollo Extensions provide a seamless way to connect to your AWS AppSync backend using Apollo client, an open-source GraphQL client. - -To learn more about Apollo, see https://www.apollographql.com/docs/ios/. - - - -To learn more about Apollo, see https://www.apollographql.com/docs/kotlin. - - -## Features - -AWS AppSync Apollo Extensions provide AWS AppSync authorizers to be used with the Apollo client to make it simple to apply the correct authorization payloads to your GraphQL operations. - - -Additionally, we publish an optional Amplify extension that allows Amplify to provide auth tokens and signing logic for the corresponding Authorizers. - - - -Additionally, the included Amplify components allow Amplify to provide auth tokens and signing logic for the corresponding Authorizers. - - -## Install the AWS AppSync Apollo Extensions library - - - -#### [With Amplify] - -Add the `apollo-appsync-amplify` dependency to your app/build.gradle.kts file. - -```kotlin title="app/build.gradle.kts" -dependencies { - // highlight-start - // Connect Apollo to AppSync, delegating some implementation details to Amplify - implementation("com.amplifyframework:apollo-appsync-amplify:1.0.0") - // highlight-end -} -``` - -#### [Without Amplify] - -Add the `apollo-appsync` dependency to your app/build.gradle.kts file. - -```kotlin title="app/build.gradle.kts" -dependencies { - // highlight-start - // Connect Apollo to AppSync without using Amplify - implementation("com.amplifyframework:apollo-appsync:1.0.0") - // highlight-end -} -``` - - - - -Add AWS AppSync Apollo Extensions into your project using Swift Package Manager. - -Enter its GitHub URL (`https://github.com/aws-amplify/aws-appsync-apollo-extensions-swift`), select **Up to Next Major Version** and click **Add Package** - -* Select the following libraries: - * **AWSAppSyncApolloExtensions** - - -## Connecting to AWS AppSync with Apollo client - - -### Creating the ApolloClient - -#### [With Amplify] -Before you begin, you will need an Amplify Data backend deploy. To get started, see [Set up Data](/[platform]/build-a-backend/data/set-up-data/). - -Once you have deployed your backend and created the `amplify_outputs.json` file, you can use Amplify library to read and retrieve your configuration values with the following steps: - -```kotlin -// Use apiKey auth mode, reading configuration from AmplifyOutputs -val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) -val apolloClient = ApolloClient.Builder() - .appSync(connector.endpoint, connector.apiKeyAuthorizer()) - .build() -``` - -#### [Without Amplify] -You can create your Apollo client by using our provided AWS AppSync endpoint and authorizer classes. - -```kotlin -val endpoint = AppSyncEndpoint("") -// Continue Reading to see more authorizer examples -val authorizer = ApiKeyAuthorizer("[API_KEY]") -val apolloClient = ApolloClient.Builder() - .appSync(endpoint, authorizer) - .build() -``` - -### Providing AppSync Authorizers - -#### [With Amplify] - -The AWS AppSync Apollo Extensions library provides a number of Authorizer classes to match the various authorization strategies that may be in use in your schema. You should choose the appropriate Authorizer type for your authorization strategy. To read more about the strategies and their corresponding auth modes, see [Available authorization strategies](/[platform]/build-a-backend/data/customize-authz/#available-authorization-strategies). - -Some common ones are - -* `publicAPIkey` strategy, `apiKey` authMode, **APIKeyAuthorizer** -* `guest` strategy, `identityPool` authMode, **IAMAuthorizer** -* `owner` strategy, `userPool` authMode, **AuthTokenAuthorizer** - -If you define multiple authorization strategies within your schema, you will have to create separate Apollo client instances for each Authorizer that you want to use in your app. - -#### API_KEY - -An `ApiKeyAuthorizer` can read the API key from `amplify_outputs.json`, provide a hardcoded API key, or fetch the API key from some source: - -```kotlin -// highlight-start -// Using ApolloAmplifyConnector to read API key from amplify_outputs.json -val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) -val authorizer = connector.apiKeyAuthorizer() -//highlight-end -// or -// highlight-start -// Use a hard-coded API key -val authorizer = ApiKeyAuthorizer("[API_KEY]") -//highlight-end -// or -// highlight-start -// Fetch the API key from some source. This function may be called many times, -// so it should implement appropriate caching internally. -val authorizer = ApiKeyAuthorizer { fetchApiKey() } -//highlight-end -``` - -#### AMAZON_COGNITO_USER_POOLS - -You can use `AmplifyApolloConnector` to get an `AuthTokenAuthorizer` instance that supplies the token for the current logged-in Amplify user, or implement the token fetching yourself. - -```kotlin -// highlight-start -// Using ApolloAmplifyConnector to get the authorizer that connects to your -// Amplify instance -val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) -val authorizer = connector.authTokenAuthorizer() -//highlight-end -// or -// highlight-start -// Using the ApolloAmplifyConnector companion function -val authorizer = AuthTokenAuthorizer { - ApolloAmplifyConnector.fetchLatestCognitoAuthToken() -} -//highlight-end -// or -// highlight-start -// Use your own token fetching. This function may be called many times, -// so it should implement appropriate caching internally. -val authorizer = AuthTokenAuthorizer { - fetchLatestAuthToken() -} -//highlight-end -``` - -You can provide your own custom `fetchLatestAuthToken` provider for **AWS_LAMBDA** and **OPENID_CONNECT** auth modes. - -#### AWS_IAM - -You can use the `ApolloAmplifyConnector` to delegate token fetching and request -signing to Amplify. - -```kotlin -// highlight-start -// Using ApolloAmplifyConnector to get the authorizer that connects to your -// Amplify instance -val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs)) -val authorizer = connector.iamAuthorizer() -//highlight-end -// or -// highlight-start -// Using the ApolloAmplifyConnector companion function -val authorizer = IamAuthorizer { - ApolloAmplifyConnector.signAppSyncRequest(it, "us-east-1") -} -//highlight-end -``` - -#### [Without Amplify] - -AWS AppSync supports the following [authorization modes](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html). Use the corresponding Authorizer that matches the chosen authorization type. - -Some common ones are - -* API Key Authorization -> **APIKeyAuthorizer** -* IAM Authorization -> **IAMAuthorizer** -* Cognito User Pools -> **AuthTokenAuthorizer** - -If you apply multiple authorization directives in your schema, you will have to create separate Apollo client instances for each Authorizer that you want to use in your app. - -#### API_KEY - -An `ApiKeyAuthorizer` can be used with a hardcoded API key or by fetching the key from some source.: - -```kotlin -// highlight-start -// Use a hard-coded API key -val authorizer = ApiKeyAuthorizer("[API_KEY]") -//highlight-end -// or -// highlight-start -// Fetch the API key from some source. This function may be called many times, -// so it should implement appropriate caching internally. -val authorizer = ApiKeyAuthorizer { fetchApiKey() } -//highlight-end -``` - -#### AMAZON_COGNITO_USER_POOLS - -When working directly with AppSync, you must implement the token fetching yourself. - -```kotlin -// highlight-start -// Use your own token fetching. This function may be called many times, -// so it should implement appropriate caching internally. -val authorizer = AuthTokenAuthorizer { - fetchLatestAuthToken() -} -//highlight-end -``` - -#### AWS_IAM - -When working directly with AppSync, you must implement the request signing yourself. - -```kotlin -// highlight-start -// Provide an implementation of the signing function. This function should implement the -// AWS Sig-v4 signing logic and return the authorization headers containing the token and signature. -val authorizer = IamAuthorizer { signRequestAndReturnHeaders(it) } -// highlight-end -``` - - - - -AWS AppSync supports the following [authorization modes](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html): - -### API_KEY - -```swift -import AWSAppSyncApolloExtensions - -let authorizer = APIKeyAuthorizer(apiKey: "[API_KEY]") -let interceptor = AppSyncInterceptor(authorizer) -``` - -### AMAZON_COGNITO_USER_POOLS - -If you are using Amplify Auth, you can create a method that retrieves the Cognito access token - -```swift -import Amplify - -func getUserPoolAccessToken() async throws -> String { - let authSession = try await Amplify.Auth.fetchAuthSession() - if let result = (authSession as? AuthCognitoTokensProvider)?.getCognitoTokens() { - switch result { - case .success(let tokens): - return tokens.accessToken - case .failure(let error): - throw error - } - } - throw AuthError.unknown("Did not receive a valid response from fetchAuthSession for get token.") -} -``` - -Then create the AuthTokenAuthorizer with this method. - -```swift -import AWSAppSyncApolloExtensions - -let authorizer = AuthTokenAuthorizer(fetchLatestAuthToken: getUserPoolAccessToken) -let interceptor = AppSyncInterceptor(authorizer) -``` - -### AWS_IAM - -If you are using Amplify Auth, you can use the following method for AWS_IAM auth - -```swift -import AWSCognitoAuthPlugin -import AWSAppSyncApolloExtensions - -let authorizer = IAMAuthorizer( - signRequest: AWSCognitoAuthPlugin.createAppSyncSigner( - region: "[REGION]")) -``` - - - -## Connecting Amplify Data to Apollo client - -Before you begin, you will need an Amplify Data backend deploy. To get started, see [Set up Data](/[platform]/build-a-backend/data/set-up-data/). - -Once you have deployed your backend and created the `amplify_outputs.json` file, you can use Amplify library to read and retrieve your configuration values with the following steps: - -1. Enter its GitHub URL (`https://github.com/aws-amplify/amplify-swift`), select **Up to Next Major Version** and click **Add Package** -2. Select the following libraries: - 1. **AWSPluginsCore** -3. Drag and drop the `amplify_outputs.json` file into your Xcode project. -4. Initialize the configuration with `try AWSAppSyncConfiguration(with: .amplifyOutputs)` - -The resulting configuration object will have the `endpoint`, `region`, and optional `apiKey.` The following example shows reading the `amplify_outputs.json` file from the main bundle to instantiate the configuration and uses it to configure the Apollo client for **API_Key** authorization. - -```swift -import Apollo -import ApolloAPI -import AWSPluginsCore -import AWSAppSyncApolloExtensions - -func createApolloClient() throws -> ApolloClient { - let store = ApolloStore(cache: InMemoryNormalizedCache()) - - // 1. Read AWS AppSync API configuration from `amplify_outputs.json` - let configuration = try AWSAppSyncConfiguration(with: .amplifyOutputs) - - // 2. Use `configuration.apiKey` with APIKeyAuthorizer - let authorizer = APIKeyAuthorizer(apiKey: configuration.apiKey ?? "") - let interceptor = AppSyncInterceptor(authorizer) - let interceptorProvider = DefaultPrependInterceptorProvider(interceptor: interceptor, - store: store) - // 3. Use `configuration.endpoint` with RequestChainNetworkTransport - let transport = RequestChainNetworkTransport(interceptorProvider: interceptorProvider, - endpointURL: configuration.endpoint) - - return ApolloClient(networkTransport: transport, store: store) -} -``` - -The AWS AppSync Apollo Extensions library provides a number of Authorizer classes to match the various authorization strategies that may be in use in your schema. You should choose the appropriate Authorizer type for your authorization strategy. To read more about the strategies and their corresponding auth modes, see [Available authorization strategies](/[platform]/build-a-backend/data/customize-authz/#available-authorization-strategies). - -Some common ones are - -* `publicAPIkey` strategy, `apiKey` authMode, **APIKeyAuthorizer** -* `guest` strategy, `identityPool` authMode, **IAMAuthorizer** -* `owner` strategy, `userPool` authMode, **AuthTokenAuthorizer** - -If you define multiple authorization strategies within your schema, you will have to create separate Apollo client instances for each Authorizer that you want to use in your app. - - -## Downloading the AWS AppSync schema - -The schema is used by Apollo’s code generation tool to generate API code that helps you execute GraphQL operations. The following steps integrate your AppSync schema with Apollo's code generation process: - - -1. Navigate to your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home) -2. On the left side, select Schema -3. Select the "Export schema" dropdown and download the `schema.json` file. -4. Add this file to your project as directed by [Apollo Code Generation documentation](https://www.apollographql.com/docs/ios/code-generation/introduction). - -You can alternatively download the introspection schema using the [`fetch-schema`](https://www.apollographql.com/docs/ios/code-generation/codegen-cli#fetch-schema) command with the `amplify-ios-cli` tool. - - - -1. Navigate to your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home) -2. On the left side, select Schema -3. Select the "Export schema" dropdown and download the `schema.json` file. -4. Add this file to your project as directed by [Apollo documentation](https://www.apollographql.com/docs/kotlin/advanced/plugin-recipes#specifying-the-schema-location) - - -## Generating Queries, Mutations, and Subscriptions for Apollo client - - - -#### [With Amplify] -**Amplify provided .graphql files** -1. Within your Amplify Gen 2 backend, run: `npx ampx generate graphql-client-code --format graphql-codegen --statement-target graphql --out graphql` -2. Copy the generated files (`mutations.graphql`, `queries.graphql`, `subscriptions.graphql`) to your `{app}/src/main/graphql` folder as shown in the [Apollo documentation](https://www.apollographql.com/docs/kotlin#getting-started) - -**Manual** -1. Navigate to the **Queries** tab in your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home). Here, you can test queries, mutations, and subscriptions in the GraphQL playground. -2. Enter your GraphQL operation (query, mutation, or subscription) in the editor and select **Run** to execute it. -3. Observe the request and response structure in the results. This gives you insight into the exact call patterns and structure that Apollo will use. -4. Copy the GraphQL operation(s) from the playground and pass them to to your `{app}/src/main/graphql` folder as shown in the [Apollo documentation](https://www.apollographql.com/docs/kotlin#getting-started) - -#### [Without Amplify] - -1. Navigate to the **Queries** tab in your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home). Here, you can test queries, mutations, and subscriptions in the GraphQL playground. -2. Enter your GraphQL operation (query, mutation, or subscription) in the editor and click **Run** to execute it. -3. Observe the request and response structure in the results. This gives you insight into the exact call patterns and structure that Apollo will use. -4. Copy the GraphQL operation from the playground and pass it to Apollo's code generation tool to automatically generate the corresponding API code for your project. - - - - -1. Navigate to the **Queries** tab in your API on the [AWS AppSync console](https://console.aws.amazon.com/appsync/home). Here, you can test queries, mutations, and subscriptions in the GraphQL playground. -2. Enter your GraphQL operation (query, mutation, or subscription) in the editor and click **Run** to execute it. -3. Observe the request and response structure in the results. This gives you insight into the exact call patterns and structure that Apollo will use. -4. Copy the GraphQL operation from the playground and pass it to Apollo's code generation tool to automatically generate the corresponding API code for your project. - - - -## Type Mapping AppSync Scalars -By default, [AWS AppSync Scalars](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html#graph-ql-aws-appsync-scalars) will default to the `Any` type. You can map these scalars to more explicit types by editing the `apollo` block in your `app/build.gradle[.kts]` file. In the example below, we are now mapping a few of our AppSync scalar types to `String` instead of `Any`. Additional improvements could be made by writing [custom class adapters](https://www.apollographql.com/docs/kotlin/essentials/custom-scalars#define-class-mapping) to convert date/time scalars into Kotlin date/time class types. - -```kotlin -apollo { - service("{serviceName}") { - packageName.set("{packageName}") - mapScalarToKotlinString("AWSDateTime") - mapScalarToKotlinString("AWSEmail") - } -} -``` - - - -## Connecting to AWS AppSync real-time endpoint - -The following example shows how you can create an Apollo client that allows performing GraphQL subscription operations with AWS AppSync. - -```swift -import Apollo -import ApolloAPI -import ApolloWebSocket -import AWSPluginsCore -import AWSAppSyncApolloExtensions - -func createApolloClient() throws -> ApolloClient { - let store = ApolloStore(cache: InMemoryNormalizedCache()) - let configuration = try AWSAppSyncConfiguration(with: .amplifyOutputs) - - // 1. Create your authorizer - let authorizer = /* your Authorizer */ - let interceptor = AppSyncInterceptor(authorizer) - - let interceptorProvider = DefaultPrependInterceptorProvider(interceptor: interceptor, - store: store) - let transport = RequestChainNetworkTransport(interceptorProvider: interceptorProvider, - endpointURL: configuration.endpoint) - - // 2. Create the AWS AppSync compatible websocket client - let websocket = AppSyncWebSocketClient(endpointURL: configuration.endpoint, - authorizer: authorizer) - // 3. Add it to the WebSocketTransport - let webSocketTransport = WebSocketTransport(websocket: websocket) - // 4. Create a SplitNetworkTransport - let splitTransport = SplitNetworkTransport( - uploadingNetworkTransport: transport, - webSocketNetworkTransport: webSocketTransport - ) - // 5. Pass the SplitNetworkTransport to the ApolloClient - return ApolloClient(networkTransport: splitTransport, store: store) -} -``` - - ---- - ---- -title: "Enable logging" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-13T22:13:21.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/enable-logging/" ---- - -You can enable logging to debug your GraphQL API using Amazon CloudWatch logs. To learn more about logging and monitoring capabilities for your GraphQL API, visit the [AWS AppSync documentation for logging and monitoring](https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html). - -## Enable default logging configuration - -Default logging can be enabled by setting the `logging` property to `true` in the call to `defineData`. For example: - -```ts title="amplify/data/resource.ts" -export const data = defineData({ - // ... - logging: true -}); -``` - -Using `logging: true` applies the default configuration: -- `excludeVerboseContent: true` (see [AppSync's Request-level logs](https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html#cwl)) -- `fieldLogLevel: 'none'` (see [AppSync's Field-level logs](https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html#cwl)) -- `retention: '1 week'` (see [Enum RetentionDays](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_logs.RetentionDays.html)) - -## Customize logging configuration - -You can customize individual configuration values by providing a [`DataLogConfig`](#datalogconfig-fields) object. For example: - -```ts title="amplify/data/resource.ts" -export const data = defineData({ - // ... - logging: { - excludeVerboseContent: false, - fieldLogLevel: 'all', - retention: '1 month' - } -}); -``` - -> **Warning:** **WARNING**: Setting `excludeVerboseContent` to `false` logs full queries and user parameters, which can contain sensitive data. We recommend limiting CloudWatch log access to only those roles or users (e.g., DevOps or developers) who genuinely require it, by carefully scoping your IAM policies. - -## Configuration Properties - -### `logging` -- `true`: Enables default logging. -- `DataLogConfig` object: Overrides one or more default fields. - -### `DataLogConfig` Fields - -- **`excludeVerboseContent?: boolean`** - - Defaults to `true` - - When `false`, logs can contain request-level logs. See [AppSync's Request-Level Logs](https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html#cwl). - -- **`fieldLogLevel?: DataLogLevel`** - - Defaults to `'none'` - - Supported values of [AppSync's Field Log Levels](https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html#cwl): - - `'none'` - - `'error'` - - `'info'` - - `'debug'` - - `'all'` - -- **`retention?: LogRetention`** - - Number of days to keep the logs - - Defaults to `'1 week'` - - Supported values of [Enum RetentionDays](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_logs.RetentionDays.html): - - `'1 day'` - - `'3 days'` - - `'5 days'` - - `'1 week'` - - `'2 weeks'` - - `'1 month'` - - `'2 months'` - - `'3 months'` - - `'4 months'` - - `'5 months'` - - `'6 months'` - - `'1 year'` - - `'13 months'` - - `'18 months'` - - `'2 years'` - - `'5 years'` - - `'10 years'` - - `'infinite'` - ---- - ---- -title: "Field-level validation" -section: "build-a-backend/data" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-03-17T16:14:03.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/data/field-level-validation/" ---- - -You can enable field-level validation in your model schema by chaining a `validate` function to the field. - -## Examples - -```ts title="amplify/data/resource.ts" -const schema = a.schema({ - Todo: a.model({ - content: a.string().validate(v => - v - .minLength(1, 'Content must be at least 1 character long') - .maxLength(100, 'Content must be less than 100 characters') - .matches('^[a-zA-Z0-9\\\\s]+$', 'Content must contain only letters, numbers, and spaces') - ) - }) - .authorization(allow => [allow.publicApiKey()]) -}); -``` - -## Supported validators - -### String Validators -For `string` fields: - -| Validator | Description | Parameters | Example | -| --- | --- | --- | --- | -| `minLength` | Validates that a string field has at least the specified length | β€’ `length`: The minimum length required
    β€’ `errorMessage`: Optional custom error message | `a.string().validate(v => v.minLength(5, 'String must be at least 5 characters'))` | -| `maxLength` | Validates that a string field does not exceed the specified length | β€’ `length`: The maximum length allowed
    β€’ `errorMessage`: Optional custom error message | `a.string().validate(v => v.maxLength(100, 'String must be at most 100 characters'))` | -| `startsWith` | Validates that a string field starts with the specified prefix | β€’ `prefix`: The prefix the string must start with
    β€’ `errorMessage`: Optional custom error message | `a.string().validate(v => v.startsWith("prefix-", 'String must start with prefix-'))` | -| `endsWith` | Validates that a string field ends with the specified suffix | β€’ `suffix`: The suffix the string must end with
    β€’ `errorMessage`: Optional custom error message | `a.string().validate(v => v.endsWith("-suffix", 'String must end with -suffix'))` | -| `matches` | Validates that a string field matches the specified regex pattern using the **Java regex engine**. See notes below. | β€’ `pattern`: The regex pattern the string must match
    β€’ `errorMessage`: Optional custom error message | `a.string().validate(v => v.matches("^[a-zA-Z0-9]+$", 'String must match the pattern'))` | - - - -**Note:** Our schema transformer uses the Java regex engine under the hood. Because of how TypeScript processes string literals, you must quadruple-escape special regex characters in your schema. In a TypeScript string literal, writing `\\\\s` produces the string `\\s`, which is the correct form for the Java regex engine. If you write `\\s`, it produces `\s`, which is invalid. Therefore, for the `matches` validator, ensure you use quadruple-escaping. For example: -`a.string().validate(v => v.matches("^[a-zA-Z0-9\\\\s]+$", 'Content must contain only letters, numbers, and spaces'))` - - - -### Numeric Validators -For `integer` and `float` fields: - -| Validator | Description | Parameters | Example | -| --- | --- | --- | --- | -| `gt` | Validates that a numeric field is greater than the specified value | β€’ `value`: The value the field must be greater than
    β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.gt(10, 'Must be greater than 10'))` | -| `gte` | Validates that a numeric field is greater than or equal to the specified value | β€’ `value`: The value the field must be greater than or equal to
    β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.gte(10, 'Must be at least 10'))` | -| `lt` | Validates that a numeric field is less than the specified value | β€’ `value`: The value the field must be less than
    β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.lt(10, 'Must be less than 10'))` | -| `lte` | Validates that a numeric field is less than or equal to the specified value | β€’ `value`: The value the field must be less than or equal to
    β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.lte(10, 'Must be at most 10'))` | -| `positive` | Validates that a numeric field is positive | β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.positive('Must be positive'))` | -| `negative` | Validates that a numeric field is negative | β€’ `errorMessage`: Optional custom error message | `a.integer().validate(v => v.negative('Must be negative'))` | - - - -**Note:** Currently, we only support validation on **non-array** fields of type `string`, `integer`, and `float`. - - - ---- - ---- -title: "API References" -section: "build-a-backend/data" -platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] -gen: 2 -last-updated: "" -url: "https://docs.amplify.aws/react/build-a-backend/data/reference/" ---- - - - ---- - ---- -title: "Storage" -section: "build-a-backend" -platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] -gen: 2 -last-updated: "2024-05-21T17:56:46.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/" ---- - - - ---- - ---- -title: "Set up Storage" -section: "build-a-backend/storage" -platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] -gen: 2 -last-updated: "2025-11-13T16:29:27.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/set-up-storage/" ---- - -In this guide, you will learn how to set up storage in your Amplify app. You will set up your backend resources, and enable listing, uploading, and downloading files. - -If you have not yet created an Amplify app, visit the [quickstart guide](/[platform]/start/quickstart/). - -Amplify Storage seamlessly integrates file storage and management capabilities into frontend web and mobile apps, built on top of Amazon Simple Storage Service (Amazon S3). It provides intuitive APIs and UI components for core file operations, enabling developers to build scalable and secure file storage solutions without dealing with cloud service complexities. - -## Building your storage backend - -First, create a file `amplify/storage/resource.ts`. This file will be the location where you configure your storage backend. Instantiate storage using the `defineStorage` function and providing a `name` for your storage bucket. This `name` is a friendly name to identify your bucket in your backend configuration. Amplify will generate a unique identifier for your app using a UUID, the name attribute is just for use in your app. - -```ts title="amplify/storage/resource.ts" -import { defineStorage } from '@aws-amplify/backend'; - -export const storage = defineStorage({ - name: 'amplifyTeamDrive' -}); -``` - -Import your storage definition in your `amplify/backend.ts` file that contains your backend definition. Add storage to `defineBackend`. - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { auth } from './auth/resource'; -// highlight-next-line -import { storage } from './storage/resource'; - -defineBackend({ - auth, - // highlight-next-line - storage -}); -``` - -Now when you run `npx ampx sandbox` or deploy your app on Amplify, it will configure an Amazon S3 bucket where your files will be stored. Before files can be accessed in your application, you must configure storage access rules. - -To deploy these changes, commit them to git and push the changes upstream. Amplify's CI/CD system will automatically pick up the changes and build and deploy the updates. - -```bash title="Terminal" showLineNumbers={false} -git commit -am "add storage backend" -git push -``` - -### Define File Path Access - -By default, no users or other project resources have access to any files in the storage bucket. Access must be explicitly granted within `defineStorage` using the `access` callback. - -The access callback returns an object where each key in the object is a file path and each value in the object is an array of access rules that apply to that path. - -The following example shows you how you can set up your file storage structure for a generic photo sharing app. Here, - -1. Guests have access to see all profile pictures and only the users that uploaded the profile picture can replace or delete them. Users are identified by their Identity Pool ID in this case i.e. identityID. -2. There's also a general pool where all users can submit pictures. - -[Learn more about customizing access to file path](/[platform]/build-a-backend/storage/authorization/). - -```ts title="amplify/storage/resource.ts" -export const storage = defineStorage({ - name: 'amplifyTeamDrive', - access: (allow) => ({ - 'profile-pictures/{entity_id}/*': [ - allow.guest.to(['read']), - allow.entity('identity').to(['read', 'write', 'delete']) - ], - 'picture-submissions/*': [ - allow.authenticated.to(['read','write']), - allow.guest.to(['read', 'write']) - ], - }) -}); -``` - -### Configure additional storage buckets - -Amplify Storage gives you the flexibility to configure your backend to automatically provision and manage multiple storage resources. - -You can define additional storage buckets by using the same `defineStorage` function and providing a unique, descriptive `name` to identify the storage bucket. You can pass this `name` to the storage APIs to specify the bucket you want to perform the action to. Ensure that this `name` attribute is unique across the defined storage buckets in order to reliably identify the correct bucket and prevent conflicts. - -It's important to note that if additional storage buckets are defined one of them must be marked as default with the `isDefault` flag. - -```ts title="amplify/storage/resource.ts" -export const firstBucket = defineStorage({ - name: 'firstBucket', - isDefault: true, // identify your default storage bucket (required) -}); - -export const secondBucket = defineStorage({ - name: 'secondBucket', - access: (allow) => ({ - 'private/{entity_id}/*': [ - allow.entity('identity').to(['read', 'write', 'delete']) - ] - }) -}) -``` - -Add additional storage resources to the backend definition. - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { auth } from './auth/resource'; -import { firstBucket, secondBucket } from './storage/resource'; - -defineBackend({ - auth, - firstBucket, - // highlight-next-line - secondBucket -}); -``` - - -### Storage bucket client usage - -Additional storage buckets can be referenced from application code by passing the `bucket` option to Amplify Storage APIs. You can provide a target bucket's name assigned in Amplify Backend. - -```ts -import { downloadData } from 'aws-amplify/storage'; - -try { - const result = downloadData({ - path: "album/2024/1.jpg", - options: { - // highlight-start - // Specify a target bucket using name assigned in Amplify Backend - bucket: "secondBucket" - // highlight-end - } - }).result; -} catch (error) { - console.log(`Error: ${error}`) -} -``` -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. See each Amplify Storage API page for additional usage examples. - -```ts -import { downloadData } from 'aws-amplify/storage'; - -try { - const result = downloadData({ - path: 'album/2024/1.jpg', - options: { - // highlight-start - // Alternatively, provide bucket name from console and associated region - bucket: { - bucketName: 'second-bucket-name-from-console', - region: 'us-east-2' - } - // highlight-end - } - }).result; -} catch (error) { - console.log(`Error: ${error}`); -} - -``` - - - -### Storage bucket client usage - -Additional storage buckets can be referenced from application code by passing the `bucket` option to Amplify Storage APIs. You can provide a target bucket's name assigned in Amplify Backend. - -#### [Java] - -```java -StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); -StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); -Amplify.Storage.downloadFile( - StoragePath.fromString("public/example"), - new File(getApplicationContext().getFilesDir() + "/download.txt"), - options, - result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), - error -> Log.e("MyAmplifyApp", "Download Failure", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val secondBucket = StorageBucket.fromOutputs("secondBucket") -val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() -val file = File("${applicationContext.filesDir}/download.txt") -Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, option, - { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, - { Log.e("MyAmplifyApp", "Download Failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val secondBucket = StorageBucket.fromOutputs("secondBucket") -val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() -val file = File("${applicationContext.filesDir}/download.txt") -val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options) -try { - val fileName = download.result().file.name - Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "Download Failure", error) -} -``` - -#### [RxJava] - -```java -StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); -StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); -RxProgressAwareSingleOperation download = - RxAmplify.Storage.downloadFile( - StoragePath.fromString("public/example"), - new File(getApplicationContext().getFilesDir() + "/download.txt"), - options - ); - -download - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), - error -> Log.e("MyAmplifyApp", "Download Failure", error) - ); -``` - -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. See each Amplify Storage API page for additional usage examples. - -#### [Java] - -```java -BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); -StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); -StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); -Amplify.Storage.downloadFile( - StoragePath.fromString("public/example"), - new File(getApplicationContext().getFilesDir() + "/download.txt"), - options, - result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), - error -> Log.e("MyAmplifyApp", "Download Failure", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") -val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) -val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() -val file = File("${applicationContext.filesDir}/download.txt") -Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options, - { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, - { Log.e("MyAmplifyApp", "Download Failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") -val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) -val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() -val file = File("${applicationContext.filesDir}/download.txt") -val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options) -try { - val fileName = download.result().file.name - Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "Download Failure", error) -} -``` - -#### [RxJava] - -```java -BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); -StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); -StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); -RxProgressAwareSingleOperation download = - RxAmplify.Storage.downloadFile( - StoragePath.fromString("public/example"), - new File(getApplicationContext().getFilesDir() + "/download.txt"), - options, - ); - -download - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), - error -> Log.e("MyAmplifyApp", "Download Failure", error) - ); -``` - - - - -### Storage bucket client usage - -Additional storage buckets can be referenced from application code by passing the `bucket` option to Amplify Storage APIs. You can provide a target bucket's name assigned in Amplify Backend. - -```swift -let downloadTask = Amplify.Storage.downloadData( - path: .fromString("public/example/path"), - options: .init( - bucket: .fromOutputs(name: "secondBucket") - ) -) -``` - -Alternatively, you can also directly specify the bucket name and region from the console. See each Amplify Storage API page for additional usage examples. - -```swift -let downloadTask = Amplify.Storage.downloadData( - path: .fromString("public/example/path"), - options: .init( - bucket: .fromBucketInfo(.init( - bucketName: "another-bucket-name", - region: "another-bucket-region") - ) - ) -) -``` - - - -### Storage bucket client usage - -Additional storage buckets can be referenced from application code by passing the `bucket` option to Amplify Storage APIs. You can provide a target bucket's name assigned in Amplify Backend. - -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -try { - final result = await Amplify.Storage.downloadData( - path: const StoragePath.fromString('album/2024/1.jpg'), - options: StorageDownloadDataOptions( - // highlight-start - // Specify a target bucket using name assigned in Amplify Backend - bucket: StorageBucket.fromOutputs('secondBucket'), - // highlight-end - ), - ).result; -} on Exception catch (e) { - print('Error: $e'); -} -``` -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. See each Amplify Storage API page for additional usage examples. - -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -try { - final result = await Amplify.Storage.downloadData( - path: const StoragePath.fromString('album/2024/1.jpg'), - options: const StorageDownloadDataOptions( - // highlight-start - // Alternatively, provide bucket name from console and associated region - bucket: StorageBucket.fromBucketInfo( - BucketInfo( - bucketName: 'second-bucket-name-from-console', - region: 'us-east-2', - ), - ), - // highlight-end - ), - ).result; -} on Exception catch (e) { - print('Error: $e'); -} -``` - - -## Connect your app code to the storage backend - -The Amplify Storage library provides client APIs that connect to the backend resources you defined. - - -### Configure Amplify in project - -Import and load the configuration file in your app. It's recommended you add the Amplify configuration step to your app's root entry point. For example `index.js` in React or `main.ts` in Angular. - -```javascript -import { Amplify } from 'aws-amplify'; -import outputs from '../amplify_outputs.json'; - -Amplify.configure(outputs); -``` - - -Make sure you call `Amplify.configure` as early as possible in your application’s life-cycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. - - - - - -### Prerequisites - -An application with Amplify libraries integrated and a minimum target of any of the following: -- **iOS 13.0**, using **Xcode 14.1** or later. -- **macOS 10.15**, using **Xcode 14.1** or later. -- **tvOS 13.0**, using **Xcode 14.3** or later. -- **watchOS 9.0**, using **Xcode 14.3** or later. -- **visionOS 1.0**, using **Xcode 15 beta 2** or later. (Preview support - see below for more details.) - -For a full example, please follow the [project setup walkthrough](/[platform]/start/quickstart/). - - - -visionOS support is currently in **preview** and can be used by using the latest [Amplify Release](https://github.com/aws-amplify/amplify-swift/releases). -As new Xcode and visionOS versions are released, the support will be updated with any necessary fixes on a best effort basis. - - - -### Install Amplify library via Swift Package Manager - -1. To install Amplify Libraries in your application, open your project in Xcode and select **File > Add Packages...**. - -2. Enter the **Amplify Library for Swift** GitHub repo URL (`https://github.com/aws-amplify/amplify-swift`) into the search bar and click **Add Package**. - - - - **Note:** **Up to Next Major Version** should be selected from the **Dependency Rule** dropdown. - - - -3. Lastly, choose **AWSS3StoragePlugin**, **AWSCognitoAuthPlugin**, and **Amplify**. Then click **Add Package**. - -### Configure Amplify in project - -Initialize the Amplify Storage category by calling `Amplify.add(plugin:)`. To complete initialization call `Amplify.configure()`. - -#### [SwiftUI] - -Add the following imports to the top of your `App` scene and configure Amplify in the `init`: -```swift -import Amplify -import AWSCognitoAuthPlugin -import AWSS3StoragePlugin - -@main -struct MyAmplifyApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } - - init() { - do { - try Amplify.add(plugin: AWSCognitoAuthPlugin()) - try Amplify.add(plugin: AWSS3StoragePlugin()) - try Amplify.configure(with: .amplifyOutputs) - print("Amplify configured with Auth and Storage plugins") - } catch { - print("Failed to initialize Amplify with \(error)") - } - } -} -``` - -#### [UIKit] - -Add the following imports to the top of your `AppDelegate.swift` file: - -```swift -import Amplify -import AWSCognitoAuthPlugin -import AWSS3StoragePlugin -``` - -Add the following code to the `application:didFinishLaunchingWithOptions` method: - -```swift -func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? -) -> Bool { - do { - try Amplify.add(plugin: AWSCognitoAuthPlugin()) - try Amplify.add(plugin: AWSS3StoragePlugin()) - try Amplify.configure(with: .amplifyOutputs) - print("Amplify configured with Auth and Storage plugins") - } catch { - print("Failed to initialize Amplify with \(error)") - } - - return true -} -``` - -Upon building and running this application you should see the following in your console window: - -```console -Amplify configured with Auth and Storage plugins -``` - - - -### Prerequisites - -* An Android application targeting Android API level 24 (Android 7.0) or above - * For a full example of creating Android project, please follow the [quickstart guide](/[platform]/start/quickstart/) - -### Install the Amplify library - -Expand **Gradle Scripts**, open **build.gradle (Module: app)**. You will already have configured Amplify by following the steps in the [quickstart guide](/[platform]/start/quickstart/). - -Add these libraries into the `dependencies` block: -```kotlin title="app/build.gradle.kts" -android { - compileOptions { - // Support for modern Java features - isCoreLibraryDesugaringEnabled = true - } -} - -dependencies { - // Amplify API dependencies - // highlight-start - implementation("com.amplifyframework:aws-storage-s3:ANDROID_VERSION") - implementation("com.amplifyframework:aws-auth-cognito:ANDROID_VERSION") - // highlight-end - // ... other dependencies - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:ANDROID_DESUGAR_VERSION") -} -``` - -`aws-auth-cognito` is used to provide authentication for Amazon S3. - -Click **Sync Now**. - -### Configure Amplify in your project - -Initialize Amplify Storage by calling `Amplify.addPlugin()`. To complete initialization, call `Amplify.configure()`. - -Add the following code to your `onCreate()` method in your application class: - -> **Warning:** Before calling the `Amplify.configure` function, make sure to either download the `amplify_outputs.json` file from the console, or generate it with the following command: -> -> ```bash title="Terminal" showLineNumbers={false} -npx ampx generate outputs --app-id --branch main --out-dir app/src/main/res/raw -``` -> -> Next, be sure the file you generated or downloaded is in the appropriate resource directory for your application (for example, `app/src/main/res/raw`) in your Android project. Otherwise, you will not be able to compile your application. - -#### [Java] - -```java -import android.util.Log; -import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin; -import com.amplifyframework.core.Amplify; -import com.amplifyframework.core.configuration.AmplifyOutputs; -import com.amplifyframework.storage.s3.AWSS3StoragePlugin; -``` - -```java -Amplify.addPlugin(new AWSCognitoAuthPlugin()); -Amplify.addPlugin(new AWSS3StoragePlugin()); -``` - -Your class will look like this: - -```java -public class MyAmplifyApp extends Application { - @Override - public void onCreate() { - super.onCreate(); - - try { - // Add these lines to add the AWSCognitoAuthPlugin and AWSS3StoragePlugin plugins - Amplify.addPlugin(new AWSCognitoAuthPlugin()); - Amplify.addPlugin(new AWSS3StoragePlugin()); - Amplify.configure(AmplifyOutputs.fromResource(R.raw.amplify_outputs), getApplicationContext()); - - Log.i("MyAmplifyApp", "Initialized Amplify"); - } catch (AmplifyException error) { - Log.e("MyAmplifyApp", "Could not initialize Amplify", error); - } - } -} -``` - -#### [Kotlin] - -```kotlin -import android.util.Log -import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin -import com.amplifyframework.core.Amplify -import com.amplifyframework.core.configuration.AmplifyOutputs -import com.amplifyframework.storage.s3.AWSS3StoragePlugin -``` - -```kotlin -Amplify.addPlugin(AWSCognitoAuthPlugin()) -Amplify.addPlugin(AWSS3StoragePlugin()) -``` - -Your class will look like this: - -```kotlin -class MyAmplifyApp : Application() { - override fun onCreate() { - super.onCreate() - - try { - // Add these lines to add the AWSCognitoAuthPlugin and AWSS3StoragePlugin plugins - Amplify.addPlugin(AWSCognitoAuthPlugin()) - Amplify.addPlugin(AWSS3StoragePlugin()) - Amplify.configure(AmplifyOutputs(R.raw.amplify_outputs), applicationContext) - Log.i("MyAmplifyApp", "Initialized Amplify") - } catch (error: AmplifyException) { - Log.e("MyAmplifyApp", "Could not initialize Amplify", error) - } - } -} -``` - -#### [RxJava] - -```java -import android.util.Log; -import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin; -import com.amplifyframework.core.configuration.AmplifyOutputs; -import com.amplifyframework.rx.RxAmplify; -import com.amplifyframework.storage.s3.AWSS3StoragePlugin; -``` - -```java -RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); -RxAmplify.addPlugin(new AWSS3StoragePlugin()); -``` - -Your class will look like this: - -```java -public class MyAmplifyApp extends Application { - @Override - public void onCreate() { - super.onCreate(); - - try { - // Add these lines to add the AWSCognitoAuthPlugin and AWSS3StoragePlugin plugins - RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); - RxAmplify.addPlugin(new AWSS3StoragePlugin()); - RxAmplify.configure(AmplifyOutputs.fromResource(R.raw.amplify_outputs), getApplicationContext()); - - Log.i("MyAmplifyApp", "Initialized Amplify"); - } catch (AmplifyException error) { - Log.e("MyAmplifyApp", "Could not initialize Amplify", error); - } - } -} -``` - -Note that because the storage category requires auth, you will need to either configure [guest access](/[platform]/build-a-backend/auth/concepts/guest-access/) or [sign in a user](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in) before using features in the storage category. - - - -### Prerequisites - -Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Refer to [Flutter's supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) when targeting Web, Windows, or Linux. - -### Install Amplify library - -Add the following dependency to your **app**'s `pubspec.yaml` along with others you added above in **Prerequisites**: - -```yaml -dependencies: - flutter: - sdk: flutter - - amplify_auth_cognito: ^2.0.0 - amplify_flutter: ^2.0.0 - amplify_storage_s3: ^2.0.0 -``` - -### Configure Amplify in project - -To initialize the Amplify Auth and Storage categories, call `Amplify.addPlugin()` for each plugin or pass all the plugins in `Amplify.addPlugins()`. To complete initialization, call `Amplify.configure()`. - -Your code should look like this: - -```dart -import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; -import 'package:flutter/material.dart'; - -import 'amplify_outputs.dart'; - -Future _configureAmplify() async { - try { - final auth = AmplifyAuthCognito(); - final storage = AmplifyStorageS3(); - await Amplify.addPlugins([auth, storage]); - - // call Amplify.configure to use the initialized categories in your app - await Amplify.configure(amplifyConfig); - } on Exception catch (e) { - safePrint('An error occurred configuring Amplify: $e'); - } -} - -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - await _configureAmplify(); - runApp(const MyApp()); -} - -class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); - - // ... -} -``` - - -### Upload your first file - -Next, let's a photo to the `picture-submissions/` path. - - -```jsx -import React from 'react'; -import { uploadData } from 'aws-amplify/storage'; - -function App() { - const [file, setFile] = React.useState(); - - const handleChange = (event) => { - setFile(event.target.files?.[0]); - }; - - const handleClick = () => { - if (!file) { - return; - } - uploadData({ - path: `picture-submissions/${file.name}`, - data: file, - }); - }; - - return ( -
    - - -
    - ); -} -``` - - - -```javascript -import { uploadData } from "aws-amplify/storage"; - -const file = document.getElementById("file"); -const upload = document.getElementById("upload"); - -upload.addEventListener("click", () => { - const fileReader = new FileReader(); - fileReader.readAsArrayBuffer(file.files[0]); - - fileReader.onload = async (event) => { - console.log("Complete File read successfully!", event.target.result); - try { - await uploadData({ - data: event.target.result, - path: `picture-submissions/${file.files[0].name}` - }); - } catch (e) { - console.log("error", e); - } - }; -}); -``` - - - -```swift -import Amplify -import SwiftUI -import PhotosUI - -struct ContentView: View { - @State private var selectedPhoto: PhotosPickerItem? - @State private var image: Image? - - var body: some View { - NavigationStack { - VStack { - image? - .resizable() - .scaledToFit() - } - .padding() - PhotosPicker( - selection: $selectedPhoto - ) { - Text("Select a photo to upload") - } - .task(id: selectedPhoto) { - if let imageData = try? await selectedPhoto?.loadTransferable(type: Data.self) { - if let uiImage = UIImage(data: imageData) { - image = Image(uiImage: uiImage) - } - let uploadTask = Amplify.Storage.uploadData( - path: .fromString("picture-submissions/myPhoto.png"), - data: imageData - ) - } - } - } - } -} -``` - - - - -#### [Java] - -```java -private void uploadFile() { - File exampleFile = new File(getApplicationContext().getFilesDir(), "myPhoto.png"); - - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); - writer.append("Example file contents"); - writer.close(); - } catch (Exception exception) { - Log.e("MyAmplifyApp", "Upload failed", exception); - } - - Amplify.Storage.uploadFile( - StoragePath.fromString("picture-submissions/myPhoto.png"), - exampleFile, - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) - ); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -private fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "myPhoto.png") - exampleFile.writeText("Example file contents") - - Amplify.Storage.uploadFile(StoragePath.fromString("picture-submissions/myPhoto.png"), exampleFile, - { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, - { Log.e("MyAmplifyApp", "Upload failed", it) } - ) -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -private suspend fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "myPhoto.png") - exampleFile.writeText("Example file contents") - - val upload = Amplify.Storage.uploadFile(StoragePath.fromString("picture-submissions/myPhoto.png"), exampleFile) - try { - val result = upload.result() - Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") - } catch (error: StorageException) { - Log.e("MyAmplifyApp", "Upload failed", error) - } -} -``` - -#### [RxJava] - -```java -private void uploadFile() { - File exampleFile = new File(getApplicationContext().getFilesDir(), "myPhoto.png"); - - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); - writer.append("Example file contents"); - writer.close(); - } catch (Exception exception) { - Log.e("MyAmplifyApp", "Upload failed", exception); - } - - RxProgressAwareSingleOperation rxUploadOperation = - RxAmplify.Storage.uploadFile(StoragePath.fromString("picture-submissions/myPhoto.png"), exampleFile); - - rxUploadOperation - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Upload failed", error) - ); -} -``` - - - - - - -**Note**: To use `AWSFilePlatform`, add [aws_common](https://pub.dev/packages/aws_common) package to your Flutter project -by running: `flutter pub add aws_common` - - - -#### [All Platforms] - -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -Future uploadFile() async { - try { - final result = await Amplify.Storage.uploadFile( - localFile: AWSFile.fromPath('/path/to/local/myPhoto.png'), - path: const StoragePath.fromString('picture-submissions/myPhoto.png'), - ).result; - safePrint('Uploaded file: ${result.uploadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - - - -```dart -import 'dart:io' show File; - -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:aws_common/vm.dart'; - -Future uploadFile(File file) async { - try { - final result = await Amplify.Storage.uploadFile( - localFile: AWSFilePlatform.fromFile(file), - path: const StoragePath.fromString('picture-submissions/myPhoto.png'), - ).result; - safePrint('Uploaded file: ${result.uploadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - -#### [Web] - -```dart -import 'dart:html' show File; - -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:aws_common/web.dart'; - -Future uploadFile(File file) async { - final awsFile = AWSFilePlatform.fromFile(file); - try { - final result = await Amplify.Storage.uploadFile( - localFile: awsFile, - path: const StoragePath.fromString('picture-submissions/myPhoto.png'), - ).result; - safePrint('Uploaded file: ${result.uploadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - - - -## Manage files in Amplify console - -After successfully publishing your storage backend and connecting your project with client APIs, you can manage files and folders in [the Amplify console](https://console.aws.amazon.com/amplify). You can perform on-demand actions like upload, download, copy, and more under the Storage tab in the console. Refer to [Manage files in Amplify Console](/[platform]/build-a-backend/storage/manage-with-amplify-console/) guide for additional information. - -## Conclusion - -Congratulations! You finished the Set up Amplify Storage guide. In this guide, you set up and connected to backend resources, customized your file paths and access definitions, and connected your application to the backend to implement features like file uploads and downloads. - -### Next steps - -Now that you have completed setting up storage in your Amplify app, you can proceed to add file management features to your app. You can use the following guides to implement upload and download functionality, or you can access more capabilities from the side navigation. - -- [Upload Files](/[platform]/build-a-backend/storage/upload-files/) -- [Download Files](/[platform]/build-a-backend/storage/download-files/) - ---- - ---- -title: "Customize authorization rules" -section: "build-a-backend/storage" -platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] -gen: 2 -last-updated: "2024-09-19T18:46:56.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/authorization/" ---- - -Customize authorization for your storage bucket by defining access to file paths for guests, authenticated users, and user groups. Access can also be defined for functions that require access to the storage bucket. - -Refer to the following examples to understand how you can further customize authorization against different user types. - -## Access Types - -Authentication is required to continue using Amplify Storage, please make sure you set it up if you haven't already - [documentation to set up Auth](/[platform]/build-a-backend/auth/set-up-auth/). - - - -**Note:** Paths in access definitions cannot have a '/' at the beginning of the string. - -By default, all paths are denied to all types of users unless explicitly granted within `defineStorage` using the `access` callback as shown below. - - - -#### [Guest Users] -To grant all guest (i.e. not signed in) users of your application read access to files under `media/`, use the following `access` values. - -```ts title="amplify/storage/resource.ts" -export const storage = defineStorage({ - name: 'myProjectFiles', - access: (allow) => ({ - 'media/*': [ - allow.guest.to(['read']) // additional actions such as "write" and "delete" can be specified depending on your use case - ] - }) -}); -``` - -#### [Authenticated Users] - - -**Note:** When a user is part of a group, they are assigned the group role, which means permissions defined for the authenticated role will not apply for this user. - -To grant access to users within a group, you must explicitly define access permissions for the group against the desired prefix. - - - -To grant all authenticated (i.e. signed in) users of your application read access to files under `media/`, use the following `access` configuration. - -```ts title="amplify/storage/resource.ts" -export const storage = defineStorage({ - name: 'myProjectFiles', - access: (allow) => ({ - 'media/*': [ - allow.authenticated.to(['read']) // additional actions such as "write" and "delete" can be specified depending on your use case - ] - }) -}); -``` - -#### [User Groups] - - - -**Note:** When a user is part of a group, they are assigned the group role, which means permissions defined for the authenticated role will not apply for this user. - -To grant access to users within a group, you must explicitly define access permissions for the group against the desired prefix. - - - -If you have configured user groups when setting up auth in your `defineAuth` object, you can scope storage access to specific groups. In this example, assume you have a `defineAuth` config with `admin` and `auditor` groups. - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from '@aws-amplify/backend'; - -export const auth = defineAuth({ - loginWith: { - email: true - }, - groups: ['admin', 'auditor'] -}); -``` - -With the following `access` definition, you can configure permissions such that auditors have read only permissions to `media/*` while admin has full permissions. - -```ts title="amplify/storage/resource.ts" -export const storage = defineStorage({ - name: 'myProjectFiles', - access: (allow) => ({ - 'media/*': [ - allow.groups(['auditor']).to(['read']), - allow.groups(['admin']).to(['read', 'write', 'delete']) - ] - }) -}); -``` - -If multiple groups require the same set of actions, this can be combined into a single rule. - -```ts title="amplify/storage/resource.ts" -export const storage = defineStorage({ - name: 'myProjectFiles', - access: (allow) => ({ - 'media/*': [ - allow.groups(['auditor', 'admin']).to(['read', 'write']) - ] - }) -}); -``` - -#### [Owners] -In some use cases, you may want just the uploader of a file to be able to perform actions on it. For example, in a music sharing app anyone can listen to a song, but only the person who uploaded that song could delete it. - -In Amplify Storage, you can do this by using the `entity_id` to represent the user which scopes files to individual users. - -The `entity_id` is a reserved token that will be replaced with the users' identifier when the file is being uploaded. You can specify the method of identification when defining access to the path like `allow.entity().to([..])`. - -Currently, Identity Pool is the only identification method available - `allow.entity('identity').to([..])` - -The following policy would allow authenticated users full access to `media/` that matches their identity id. - -```ts title="amplify/storage/resource.ts" -export const storage = defineStorage({ - name: 'myProjectFiles', - access: (allow) => ({ - 'media/{entity_id}/*': [ - // {entity_id} is the token that is replaced with the user identity id - allow.entity('identity').to(['read', 'write', 'delete']) - ] - }) -}); -``` - -A user with identity id `user123` would be able to perform read/write/delete operations on files within `media/user123/*` but would not be able to perform actions on files with any other path. - -Likewise, a user with identity ID `userABC` would be able to perform read/write/delete operation on files only within `media/userABC/*`. In this way, each user can be granted access to a storage path that is not accessible to any other user. - -The following example shows how you can define access to profile pictures where anyone can view them but only the owner can modify/delete them. - -```ts title="amplify/storage/resource.ts" -export const storage = defineStorage({ - name: 'myProjectFiles', - access: (allow) => ({ - 'media/profile-pictures/{entity_id}/*': [ - allow.entity('identity').to(['read', 'write', 'delete']), - allow.guest.to(['read']), - allow.authenticated.to(['read']) - ] - }) -}); -``` - -When a rule for guests, authenticated users, user groups, or resources is applied to a path with the `{entity_id}` token, the token is replaced with a wildcard (`*`). This means that the access will apply to files uploaded by _any_ user. In the above policy, write and delete is scoped to just the owner, but read is allowed for guest and authenticated users for any file within `media/profile-pictures/*/*`. - -#### [Functions] -In addition to granting application users access to storage files, you may also want to grant a backend function access to storage files. This could be used to enable a use case like resizing images or automatically deleting old files. The following configuration is used to define function access. - -```ts title="amplify/storage/resource.ts" -import { defineStorage, defineFunction } from '@aws-amplify/backend'; - -const demoFunction = defineFunction({}); - -export const storage = defineStorage({ - name: 'myProjectFiles', - access: (allow) => ({ - 'media/*': [allow.resource(demoFunction).to(['read', 'write', 'delete'])] - }) -}); -``` - -This would grant the function `demoFunction` the ability to read write and delete files within `media/*`. - -When a function is granted access to storage, it also receives an environment variable that contains the name of the Amazon S3 bucket configured by storage. This environment variable can be used in the function to make AWS SDK calls to the storage bucket. The environment variable is named `_BUCKET_NAME`. In the above example, it would be named `myProjectFiles_BUCKET_NAME`. - -[Learn more about function resource access environment variables](/[platform]/build-a-backend/functions/#resource-access) - -### Access definition rules - -There are some rules for the types of paths that can be specified at the same time in the storage access definition. - -1. All paths must end with `/*`. -2. Only one level of nesting is allowed. For example, you can define access controls on `media/*` and `media/albums/*` but not on `media/albums/photos/*` because there are two other definitions along the same path. -3. Wildcards cannot conflict with the `{entity_id}` token. For example, you cannot have both `media/*` and `media/{entity_id}/*` defined because the wildcard in the first path conflicts with the `{entity_id}` token in the second path. -4. A path cannot be a prefix of another path with an `{entity_id}` token. For example `media/*` and `media/albums/{entity_id}/*` is not allowed. - -When one path is a subpath of another, the permissions on the subpath _always override_ the permissions from the parent path. Permissions are not "inherited" from a parent path. Consider the following access definition example: - -```ts -export const storage = defineStorage({ - name: 'myProjectFiles', - access: (allow) => ({ - 'media/*': [allow.authenticated.to(['read', 'write', 'delete'])], - 'media/profile-pictures/*': [allow.guest.to(['read'])], - 'media/albums/*': [allow.authenticated.to(['read'])], - 'other/*': [ - allow.guest.to(['read']), - allow.authenticated.to(['read', 'write']) - ] - }) -}); -``` - -The access control matrix for this configuration is - -| Path | media/\* | media/profile-pictures/\* | media/albums/\* | other/\* | -| --- | --- | --- | --- | --- | -| **Authenticated Users** | read, write, delete | NONE | read | read, write | -| **Guest users** | NONE | read | NONE | read | - -Authenticated users have access to read, write, and delete everything under `media/*` EXCEPT `media/profile-pictures/*` and `media/albums/*`. For those subpaths, the scoped down access overrides the access granted on the parent `media/*` - -### Available actions - -When you configure access to a particular path, you can scope the access to one or more CRUDL actions. - -| Access | Corresponding Library APIs | -| -------- | ----------------------------------------------------- | -| `read` | `getUrl`, `downloadData`, `list`, and `getProperties` | -| `get` | `getUrl` and `downloadData` | -| `list` | `list`, and `getProperties` | -| `write` | `uploadData`, `copy` | -| `delete` | `remove` | - - - -**Note:** `read` is a combination of `get` and `list` access definitions and hence cannot be defined in the presence of `get` or `list`. - - - -## For Gen 1 public, protected, and private access pattern - -To configure `defineStorage` in Amplify Gen 2 to behave the same way as the storage category in Gen 1, the following definition can be used. - -```ts title="amplify/storage/resource.ts" -export const storage = defineStorage({ - name: 'myProjectFiles', - access: (allow) => ({ - 'public/*': [ - allow.guest.to(['read']), - allow.authenticated.to(['read', 'write', 'delete']), - ], - 'protected/{entity_id}/*': [ - allow.authenticated.to(['read']), - allow.entity('identity').to(['read', 'write', 'delete']) - ], - 'private/{entity_id}/*': [ - allow.entity('identity').to(['read', 'write', 'delete']) - ] - }) -}); -``` - ---- - ---- -title: "Upload files" -section: "build-a-backend/storage" -platforms: ["angular", "javascript", "nextjs", "react", "vue", "android", "swift", "flutter", "react-native"] -gen: 2 -last-updated: "2025-01-30T19:37:25.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/upload-files/" ---- - - -You can implement upload functionality in your app by either using the File Uploader UI component or further customizing the upload experience using the upload API. - -## File Uploader React UI Component - -Upload files from your app in minutes by using the cloud-connected File Uploader UI Component. - -```bash title="Terminal" showLineNumbers={false} -npm add @aws-amplify/ui-react-storage aws-amplify -``` -Then, use the component in your app. - -```tsx -import { FileUploader } from '@aws-amplify/ui-react-storage'; -import '@aws-amplify/ui-react/styles.css'; - -export const DefaultFileUploaderExample = () => { - return ( - - ); -}; -``` - -![Showing File Uploader UI component](/images/gen2/storage/upload-ui-component.png) - -Learn more about how you can further customize the UI component by referring to the [File Uploader documentation](https://ui.docs.amplify.aws/react/connected-components/storage/fileuploader). - - -## Implement upload functionality - - - -**Note:** Refer to [the Transfer Acceleration documentation](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) to learn how to enable transfer acceleration for storage APIs. - - - - - -### Upload from file - -The following is an example of how you would upload a file from a file object, this could be retrieved from the local machine or a different source. - - -```jsx -import React from 'react'; -import { uploadData } from 'aws-amplify/storage'; - -function App() { - const [file, setFile] = React.useState(); - - const handleChange = (event) => { - setFile(event.target.files?.[0]); - }; - - const handleClick = () => { - if (!file) { - return; - } - uploadData({ - path: `photos/${file.name}`, - data: file, - }); - }; - - return ( -
    - - -
    - ); -} -``` - - - -```javascript -import { uploadData } from "aws-amplify/storage"; - -const file = document.getElementById("file"); -const upload = document.getElementById("upload"); - -upload.addEventListener("click", () => { - const fileReader = new FileReader(); - fileReader.readAsArrayBuffer(file.files[0]); - - fileReader.onload = async (event) => { - console.log("Complete File read successfully!", event.target.result); - try { - await uploadData({ - data: event.target.result, - path: file.files[0].name - }); - } catch (e) { - console.log("error", e); - } - }; -}); -``` - - -### Upload from data - -You can follow this example if you have data saved in memory and would like to upload this data to the cloud. - -```javascript -import { uploadData } from 'aws-amplify/storage'; - -try { - const result = await uploadData({ - path: "album/2024/1.jpg", - // Alternatively, path: ({identityId}) => `album/${identityId}/1.jpg` - data: file, - }).result; - console.log('Succeeded: ', result); -} catch (error) { - console.log('Error : ', error); -} -``` - - - -### Upload from file - -#### [Java] - -```java -private void uploadFile() { - File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); - - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); - writer.append("Example file contents"); - writer.close(); - } catch (Exception exception) { - Log.e("MyAmplifyApp", "Upload failed", exception); - } - - Amplify.Storage.uploadFile( - StoragePath.fromString("public/example"), - exampleFile, - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) - ); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -private fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "example") - exampleFile.writeText("Example file contents") - - Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, - { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, - { Log.e("MyAmplifyApp", "Upload failed", it) } - ) -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -private suspend fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "example") - exampleFile.writeText("Example file contents") - - val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile) - try { - val result = upload.result() - Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") - } catch (error: StorageException) { - Log.e("MyAmplifyApp", "Upload failed", error) - } -} -``` - -#### [RxJava] - -```java -private void uploadFile() { - File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); - - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); - writer.append("Example file contents"); - writer.close(); - } catch (Exception exception) { - Log.e("MyAmplifyApp", "Upload failed", exception); - } - - RxProgressAwareSingleOperation rxUploadOperation = - RxAmplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile); - - rxUploadOperation - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Upload failed", error) - ); -} -``` - - - - -### Upload from Input Stream - -#### [Java] - -```java -private void uploadInputStream() { - try { - InputStream exampleInputStream = getContentResolver().openInputStream(uri); - - Amplify.Storage.uploadInputStream( - StoragePath.fromString("public/example"), - exampleInputStream, - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) - ); - } catch (FileNotFoundException error) { - Log.e("MyAmplifyApp", "Could not find file to open for input stream.", error); - } -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -private fun uploadInputStream(uri: Uri) { - val stream = contentResolver.openInputStream(uri) - - Amplify.Storage.uploadInputStream(StoragePath.fromString("public/example"), stream, - { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, - { Log.e("MyAmplifyApp", "Upload failed", it) } - ) -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -private suspend fun uploadInputStream(uri: Uri) { - val stream = contentResolver.openInputStream(uri) - - val upload = Amplify.Storage.uploadInputStream(StoragePath.fromString("public/example"), stream) - try { - val result = upload.result() - Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}.") - } catch (error: StorageException) { - Log.e("MyAmplifyApp", "Upload failed") - } -} -``` - -#### [RxJava] - -```java -private void uploadInputStream() { - try { - InputStream exampleInputStream = getContentResolver().openInputStream(uri); - - RxProgressAwareSingleOperation rxUploadOperation = - RxAmplify.Storage.uploadInputStream(StoragePath.fromString("public/example"), exampleInputStream); - - rxUploadOperation - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Upload failed", error) - ); - } catch (FileNotFoundException error) { - Log.e("MyAmplifyApp", "Could not find file to open for input stream.", error); - } -} -``` - - - - -### Upload from file - -When you have a file that you want to upload, you can specify the url to the file in the `local` parameter. -If a file with the same `path` already exists in S3, the existing S3 file will be overwritten. - -```swift -let dataString = "My Data" -let fileName = "myFile.txt" -guard let fileUrl = FileManager.default.urls( - for: .documentDirectory, - in: .userDomainMask -).first?.appendingPathComponent(fileName) -else { return } - -try dataString.write( - to: fileUrl, - atomically: true, - encoding: .utf8 -) - -let uploadTask = Amplify.Storage.uploadFile( - path: .fromString("public/example/path/myFile.txt"), - local: fileUrl -) - -``` - -### Upload from data - -To upload a file from a data object, specify the `path` and the `data` object to be uploaded. - -```swift -let dataString = "My Data" -let data = Data(dataString.utf8) -let uploadTask = Amplify.Storage.uploadData( - path: .fromString("public/example/path/myFile.txt"), - data: data -) -``` - - - -### Upload from file - - - -**Note**: To use `AWSFilePlatform`, add [aws_common](https://pub.dev/packages/aws_common) package to your Flutter project -by running: `flutter pub add aws_common` - - - -#### [All Platforms] - -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -Future uploadFile() async { - try { - final result = await Amplify.Storage.uploadFile( - localFile: AWSFile.fromPath('/path/to/local/file.txt'), - path: const StoragePath.fromString('public/file.txt'), - ).result; - safePrint('Uploaded file: ${result.uploadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - - - -```dart -import 'dart:io' show File; - -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:aws_common/vm.dart'; - -Future uploadFile(File file) async { - try { - final result = await Amplify.Storage.uploadFile( - localFile: AWSFilePlatform.fromFile(file), - path: const StoragePath.fromString('public/file.png'), - ).result; - safePrint('Uploaded file: ${result.uploadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - -#### [Web] - -```dart -import 'dart:html' show File; - -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:aws_common/web.dart'; - -Future uploadFile(File file) async { - final awsFile = AWSFilePlatform.fromFile(file); - try { - final result = await Amplify.Storage.uploadFile( - localFile: awsFile, - path: const StoragePath.fromString('public/file.png'), - ).result; - safePrint('Uploaded file: ${result.uploadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - -### Upload from Flutter's `file_picker` plugin - -The [file_picker](https://pub.dev/packages/file_picker) plugin can be used to retrieve arbitrary file types from the user's device. - -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:file_picker/file_picker.dart'; - -Future uploadImage() async { - // Select a file from the device - final result = await FilePicker.platform.pickFiles( - type: FileType.custom, - withData: false, - // Ensure to get file stream for better performance - withReadStream: true, - allowedExtensions: ['jpg', 'png', 'gif'], - ); - - if (result == null) { - safePrint('No file selected'); - return; - } - - // Upload file using the filename - final platformFile = result.files.single; - try { - final result = await Amplify.Storage.uploadFile( - localFile: AWSFile.fromStream( - platformFile.readStream!, - size: platformFile.size, - ), - path: StoragePath.fromString('public/${platformFile.name}'), - onProgress: (progress) { - safePrint('Fraction completed: ${progress.fractionCompleted}'); - }, - ).result; - safePrint('Successfully uploaded file: ${result.uploadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - -### Upload from data - -To upload from a data object, specify the `path` and `data`, where `data` is an instance of `S3DataPayload` created from various data formats. - -#### [String] - -```dart -Future uploadData() async { - try { - final result = await Amplify.Storage.uploadData( - data: StorageDataPayload.string( - 'hello world', - contentType: 'text/plain', - ), - path: const StoragePath.fromString('public/example.txt'), - ).result; - safePrint('Uploaded data: ${result.uploadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - -#### [JSON Object] - -```dart -Future uploadData() async { - try { - final result = await Amplify.Storage.uploadData( - data: StorageDataPayload.json({ - 'title': 'example', - 'author': { - 'firstName': 'Jane', - 'lastName': 'Doe', - }, - }), - path: const StoragePath.fromString('public/example.json'), - ).result; - safePrint('Uploaded data: ${result.uploadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - -#### [Data URL] - -See more info about [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs). - -```dart -Future uploadData() async { - // dataUrl should be a valid Data Url. - // see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs - const dataUrl = 'data:text/plain;charset=utf-8;base64,aGVsbG8gd29ybGQ='; - try { - final result = await Amplify.Storage.uploadData( - data: StorageDataPayload.dataUrl(dataUrl), - path: const StoragePath.fromString('public/example.txt'), - ).result; - safePrint('Uploaded data: ${result.uploadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - -#### [Bytes] - -```dart -Future uploadBytes() async { - try { - final bytes = 'hello world'.codeUnits; - final result = await Amplify.Storage.uploadData( - data: StorageDataPayload.bytes( - bytes, - contentType: 'text/plain', - ), - path: const StoragePath.fromString('public/example.txt'), - ).result; - safePrint('Uploaded data: ${result.uploadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - - - - -### Monitor upload progress - -```dart -final operation = Amplify.Storage.uploadFile( - localFile: AWSFile.fromPath('/path/to/local/file'), - path: const StoragePath.fromString('public/example.txt'), - onProgress: (progress) { - safePrint('fraction totalBytes: ${progress.totalBytes}'); - safePrint('fraction transferredBytes: ${progress.transferredBytes}'); - safePrint('fraction completed: ${progress.fractionCompleted}'); - } -); -``` - - - -### Pause, resume, and cancel uploads - -A call to `Amplify.Storage.uploadFile` or `Amplify.Storage.uploadData` returns a reference to the operation that is performing the upload. - -```dart -Future upload() async { - final operation = Amplify.Storage.uploadFile( - localFile: AWSFile.fromPath('/path/to/local/file'), - path: const StoragePath.fromString('public/example.txt'), - ); - - // pause operation - await operation.pause(); - - // resume operation - await operation.resume(); - - // cancel operation - await operation.cancel(); -} -``` - - - -### Upload to a specified bucket - -You can also perform an `upload` operation to a specific bucket by providing the `bucket` option. You can pass in a `StorageBucket` object representing the target bucket from the name defined in the Amplify Backend. - -```dart -final data = 'multi bucket upload data byte'.codeUnits; -final result = await Amplify.Storage.uploadData( - data: StorageDataPayload.bytes(data), - path: const StoragePath.fromString('path/to/file.txt'), - options: StorageUploadDataOptions( - // highlight-start - // Specify a target bucket using name assigned in Amplify Backend - bucket: StorageBucket.fromOutputs('secondBucket'), - // highlight-end - ), -).result; -``` -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. - -```dart -final data = 'multi bucket upload data byte'.codeUnits; -final result = await Amplify.Storage.uploadData( - data: StorageDataPayload.bytes(data), - path: const StoragePath.fromString('path/to/file.txt'), - options: StorageUploadDataOptions( - // highlight-start - // Alternatively, provide bucket name from console and associated region - bucket: StorageBucket.fromBucketInfo( - BucketInfo( - bucketName: 'second-bucket-name-from-console', - region: 'us-east-2', - ), - ), - // highlight-end - ), -).result; -``` - - - -### More upload options - -Option | Type | Description | -| -- | -- | ----------- | -| bucket | StorageBucket | The target bucket from the assigned name in the Amplify Backend or from the bucket name and region in the console

    Defaults to default bucket and region from the Amplify configuration if this option is not provided.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | -| getProperties | boolean | Whether to retrieve properties for the uploaded object using theAmplify.Storage.getProperties() after the operation completes. When set to true the returned item will contain additional info such as metadata and content type. | -| useAccelerateEndpoint | boolean | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/upload-files/#transfer-acceleration) | - -Example of `uploadFile` with options: - -```dart -final operation = Amplify.Storage.uploadFile( - localFile: AWSFile.fromPath('/path/to/local/file'), - path: const StoragePath.fromString('public/example.txt'), - options: const StorageUploadFileOptions( - metadata: {'key': 'value'}, - pluginOptions: S3UploadFilePluginOptions( - getProperties: true, - useAccelerateEndpoint: true, - ), - ), -); -``` - -Example of `uploadData` with options: - -```dart -final operation = Amplify.Storage.uploadData( - data: StorageDataPayload.string('example'), - path: const StoragePath.fromString('public/example.txt'), - options: const StorageUploadDataOptions( - metadata: {'key': 'value'}, - pluginOptions: S3UploadDataPluginOptions( - getProperties: true, - useAccelerateEndpoint: true, - ), - ), -); -``` - - -### Upload to a specified bucket - -You can also perform an upload operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. - -```ts -import { uploadData } from 'aws-amplify/storage'; - -const result = await uploadData({ - path: 'album/2024/1.jpg', - data: file, - options: { - // highlight-start - // Specify a target bucket using name assigned in Amplify Backend - bucket: 'assignedNameInAmplifyBackend' - // highlight-end - } -}).result; -``` -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. - -```ts -import { uploadData } from 'aws-amplify/storage'; - -const result = await uploadData({ - path: 'album/2024/1.jpg', - data: file, - options: { - // highlight-start - // Alternatively, provide bucket name from console and associated region - bucket: { - bucketName: 'bucket-name-from-console', - region: 'us-east-2' - } - // highlight-end - } -}).result; - -``` - - - -### Upload to a specified bucket - -You can also perform an upload operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. - -#### [Java] - -```java -private void uploadFile() { - File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); - - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); - writer.append("Example file contents"); - writer.close(); - } catch (Exception exception) { - Log.e("MyAmplifyApp", "Upload failed", exception); - } - - StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); - StorageUploadFileOptions options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); - - Amplify.Storage.uploadFile( - StoragePath.fromString("public/example"), - exampleFile, - options, - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) - ); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -private fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "example") - exampleFile.writeText("Example file contents") - - val secondBucket = StorageBucket.fromOutputs("secondBucket") - val options = StorageUploadFileOptions.builder().bucket(secondBucket).build() - - Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options, - { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, - { Log.e("MyAmplifyApp", "Upload failed", it) } - ) -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -private suspend fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "example") - exampleFile.writeText("Example file contents") - - val secondBucket = StorageBucket.fromOutputs("secondBucket") - val options = StorageUploadFileOptions.builder().bucket(secondBucket).build() - - val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options) - try { - val result = upload.result() - Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") - } catch (error: StorageException) { - Log.e("MyAmplifyApp", "Upload failed", error) - } -} -``` - -#### [RxJava] - -```java -private void uploadFile() { - File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); - - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); - writer.append("Example file contents"); - writer.close(); - } catch (Exception exception) { - Log.e("MyAmplifyApp", "Upload failed", exception); - } - - StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); - StorageUploadFileOptions options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); - - RxProgressAwareSingleOperation rxUploadOperation = - RxAmplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options); - - rxUploadOperation - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Upload failed", error) - ); -} -``` - -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. - -#### [Java] - -```java -private void uploadFile() { - File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); - - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); - writer.append("Example file contents"); - writer.close(); - } catch (Exception exception) { - Log.e("MyAmplifyApp", "Upload failed", exception); - } - - BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); - StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); - StorageUploadFileOptions options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); - - Amplify.Storage.uploadFile( - StoragePath.fromString("public/example"), - exampleFile, - options, - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) - ); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -private fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "example") - exampleFile.writeText("Example file contents") - - val bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); - val secondBucket = StorageBucket.fromBucketInfo(bucketInfo); - val options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); - - Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options, - { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, - { Log.e("MyAmplifyApp", "Upload failed", it) } - ) -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -private suspend fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "example") - exampleFile.writeText("Example file contents") - - val bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); - val secondBucket = StorageBucket.fromBucketInfo(bucketInfo); - val options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); - - val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options) - try { - val result = upload.result() - Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") - } catch (error: StorageException) { - Log.e("MyAmplifyApp", "Upload failed", error) - } -} -``` - -#### [RxJava] - -```java -private void uploadFile() { - File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); - - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); - writer.append("Example file contents"); - writer.close(); - } catch (Exception exception) { - Log.e("MyAmplifyApp", "Upload failed", exception); - } - - BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); - StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); - StorageUploadFileOptions options = StorageUploadFileOptions.builder().bucket(secondBucket).build(); - - RxProgressAwareSingleOperation rxUploadOperation = - RxAmplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options); - - rxUploadOperation - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Upload failed", error) - ); -} -``` - - - - -### Upload to a specified bucket - -You can perform an upload operation to a specific bucket by providing the `bucket` option. - -#### [From Outputs] -You can use `.fromOutputs(name:)` to provide a string representing the target bucket's assigned name in the Amplify Backend. - -```swift -// Upload from File -let uploadTask = Amplify.Storage.uploadFile( - path: .fromString("public/example/path/myFile.txt"), - local: fileUrl, - options: .init( - bucket: .fromOutputs(name: "secondBucket") - ) -) - -// Upload from Data -let uploadTask = Amplify.Storage.uploadData( - path: .fromString("public/example/path/myFile.txt"), - data: data, - options: .init( - bucket: .fromOutputs(name: "secondBucket") - ) -) -``` - -#### [From Bucket Info] -You can also use `.fromBucketInfo(_:)` to provide a bucket name and region directly. - -```swift -// Upload from File -let uploadTask = Amplify.Storage.uploadFile( - path: .fromString("public/example/path/myFile.txt"), - local: fileUrl, - options: .init( - bucket: .fromBucketInfo(.init( - bucketName: "another-bucket-name", - region: "another-bucket-region") - ) - ) -) - -// Upload from Data -let uploadTask = Amplify.Storage.uploadData( - path: .fromString("public/example/path/myFile.txt"), - data: data, - options: .init( - bucket: .fromBucketInfo(.init( - bucketName: "another-bucket-name", - region: "another-bucket-region") - ) - ) -) -``` - - - - -### Monitor upload progress - -Monitor progress of upload by using the `onProgress` option. - -```javascript -import { uploadData } from 'aws-amplify/storage'; - -const monitorUpload = async () => { - try { - const result = await uploadData({ - path: "album/2024/1.jpg", - // Alternatively, path: ({identityId}) => `album/${identityId}/1.jpg` - data: file, - options: { - onProgress: ({ transferredBytes, totalBytes }) => { - if (totalBytes) { - console.log( - `Upload progress ${Math.round( - (transferredBytes / totalBytes) * 100 - )} %` - ); - } - }, - }, - }).result; - console.log("Path from Response: ", result.path); - } catch (error) { - console.log("Error : ", error); - } -} -``` - - - -### Monitor upload progress - -To track progress of the upload, use the reference returned by the `uploadFile` or `uploadData` as shown below. - -#### [Async/Await] -```swift -Task { - for await progress in await uploadTask.progress { - print("Progress: \(progress)") - } -} - -let value = try await uploadTask.value -print("Completed: \(value)") -``` - -#### [Combine] - -```swift -let progressSink = uploadTask - .inProcessPublisher - .sink { progress in - print("Progress: \(progress)") - } - -let resultSink = uploadTask - .resultPublisher - .sink { - if case let .failure(storageError) = $0 { - print("Failed: \(storageError.errorDescription). \(storageError.recoverySuggestion)") - } - } - receiveValue: { data in - print("Completed: \(data)") - } -``` - - - - -### Monitor upload progress - -To track progress of the upload, use the `uploadFile` API that includes a progress listener callback. - -#### [Java] - -```java -private void uploadFile() { - File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); - - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); - writer.append("Example file contents"); - writer.close(); - } catch (Exception exception) { - Log.e("MyAmplifyApp", "Upload failed", exception); - } - - Amplify.Storage.uploadFile( - StoragePath.fromString("public/example"), - exampleFile, - StorageUploadFileOptions.defaultInstance(), - progress -> Log.i("MyAmplifyApp", "Fraction completed: " + progress.getFractionCompleted()), - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) - ); -} -``` - -#### [Kotlin - Callbacks] - -```kotlin -private fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "example") - exampleFile.writeText("Example file contents") - - val options = StorageUploadFileOptions.defaultInstance() - Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options, - { Log.i("MyAmplifyApp", "Fraction completed: ${it.fractionCompleted}") }, - { Log.i("MyAmplifyApp", "Successfully uploaded: ${it.path}") }, - { Log.e("MyAmplifyApp", "Upload failed", it) } - ) -} -``` - -#### [Kotlin - Coroutines] - -```kotlin -private suspend fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "example") - exampleFile.writeText("Example file contents") - - val options = StorageUploadFileOptions.defaultInstance() - val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options) - val progressJob = activityScope.async { - upload.progress().collect { - Log.i("MyAmplifyApp", "Fraction completed: ${it.fractionCompleted}") - } - } - try { - val result = upload.result() - Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") - } catch (error: StorageException) { - Log.e("MyAmplifyApp", "Upload failed", error) - } - progressJob.cancel() -} -``` - -#### [RxJava] - -```java -RxProgressAwareSingleOperation upload = - RxAmplify.Storage.uploadFile("example", exampleFile); - -upload - .observeProgress() - .subscribe( - progress -> Log.i("MyAmplifyApp", progress.getFractionCompleted()) - ); -``` - - - - -### All `upload` options - -Option | Type | Description | -| -- | -- | ----------- | -| metadata | Map\ | Metadata for the object to store. | -| contentType | String | The standard MIME type describing the format of the object to store. | -| bucket | StorageBucket | The bucket in which the object should be stored. | -| serverSideEncryption | ServerSideEncryption | The server side encryption algorithm. | -| useAccelerateEndpoint | boolean | Flag to determine whether to use acceleration endpoint. | - - - -### Query transfers - -When an upload or download operation is requested using the Amplify Android library, the request is first persisted in the local SQLite Database and then queued for execution. You can query the transfer operation queued in the local database using the transfer ID returned by an upload or download API. Get-Transfer API could retrieve a pending transfer previously en-queued and enable attaching a listener to receive updates on progress change, on-error or on-success, or pause, cancel or resume it. - -#### [Java] - -```java -Amplify.Storage.getTransfer("TRANSFER_ID", - operation -> { - Log.i("MyAmplifyApp", "Current State" + operation.getTransferState()); - // set listener to receive updates - operation.setOnProgress( progress -> {}); - operation.setOnSuccess( result -> {}); - operation.setOnError(error -> {}); - - // possible actions - operation.pause(); - operation.resume(); - operation.start(); - operation.cancel(); - }, - { - error -> Log.e("MyAmplifyApp", "Failed to query transfer", error) - } -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Storage.getTransfer("TRANSFER_ID", - { operation -> - Log.i("MyAmplifyApp", "Current State" + operation.transferState) - // set listener to receive updates - operation.setOnProgress { } - operation.setOnSuccess { } - operation.setOnError { } - - // possible actions - operation.pause() - operation.resume() - operation.start() - operation.cancel() - }, - { - Log.e("MyAmplifyApp", "Failed to query transfer", it) - } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val operation = Amplify.Storage.getTransfer("TRANSFER_ID") - Log.i("MyAmplifyApp", "Current State" + operation.transferState) - // set listener to receive updates - operation.setOnProgress { } - operation.setOnSuccess { } - operation.setOnError { } - - // possible actions - operation.pause() - operation.resume() - operation.start() - operation.cancel() -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "Failed to query transfer", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Storage.getTransfer("TRANSFER_ID") - .subscribe( - operation -> { - Log.i("MyAmplifyApp", "Current State" + operation.getTransferState()); - // set listener to receive updates - operation.setOnProgress( progress -> {}); - operation.setOnSuccess( result -> {}); - operation.setOnError(error -> {}); - - // possible actions - operation.pause(); - operation.resume(); - operation.start(); - operation.cancel(); - }, - error -> Log.e("MyAmplifyApp", "Failed to query transfer", error); - ); -``` - - - - -### Transfer with Object Metadata - -To upload a file accompanied by metadata, utilize the `StorageUploadFileOptions` builder. Start by creating a hashMap object, then incorporate it into the `StorageUploadFileOptions` during the build process before passing it along to the upload function. - -#### [Java] -```java -private void uploadFile() { - File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); - writer.append("Example file contents"); - writer.close(); - } catch (Exception exception) { - Log.e("MyAmplifyApp", "Upload failed", exception); - } - - // Create metadata - Map userMetadata = new HashMap<>(); - userMetadata.put("myKey", "myVal"); - - // Configure upload options with metadata - StorageUploadFileOptions options = StorageUploadFileOptions.builder() - .metadata(userMetadata) - .build(); - - // Perform the upload - Amplify.Storage.uploadFile( - StoragePath.fromString("public/example"), - exampleFile, - options, - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Upload failed", error) - ); -} -``` - -#### [Kotlin - Callbacks] -```kotlin -fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "example") - exampleFile.writeText("Example file contents") - - // Create metadata - val userMetadata: MutableMap = HashMap() - userMetadata["myKey"] = "myVal" - - // Configure upload options with metadata - val options = StorageUploadFileOptions.builder() - .metadata(userMetadata) - .build() - - // Perform the upload - Amplify.Storage.uploadFile( - StoragePath.fromString("public/example"), - exampleFile, - options, - { result -> Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") }, - { error -> Log.e("MyAmplifyApp", "Upload failed", error) } - ) -} -``` - -#### [Kotlin - Coroutines] -```kotlin -fun uploadFile() { - val exampleFile = File(applicationContext.filesDir, "example") - exampleFile.writeText("Example file contents") - - // Create metadata - val userMetadata: MutableMap = HashMap() - userMetadata["myKey"] = "myVal" - - // Configure upload options with metadata - val options = StorageUploadFileOptions.builder() - .metadata(userMetadata) - .build() - - val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options) - val progressJob = activityScope.async { - upload.progress().collect { - Log.i("MyAmplifyApp", "Fraction completed: ${it.fractionCompleted}") - } - } - try { - val result = upload.result() - Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") - } catch (error: StorageException) { - Log.e("MyAmplifyApp", "Upload failed", error) - } - progressJob.cancel() -} -``` - -#### [RxJava] -```Java -private void uploadFile() { - File exampleFile = new File(getApplicationContext().getFilesDir(), "example"); - - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(exampleFile)); - writer.append("Example file contents"); - writer.close(); - } catch (Exception exception) { - Log.e("MyAmplifyApp", "Upload failed", exception); - } - - Map userMetadata = new HashMap<>(); - userMetadata.put("myKey", "myVal"); - - StorageUploadFileOptions options = StorageUploadFileOptions.builder() - .metadata(userMetadata) - .build(); - - RxStorageBinding.RxProgressAwareSingleOperation rxUploadOperation = - RxAmplify.Storage.uploadFile(StoragePath.fromString("public/example"), exampleFile, options); - - rxUploadOperation - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Upload failed", error) - ); -} - ``` - - - - -### Pause, resume, and cancel uploads - -We have callback functions that support resuming, pausing, and cancelling `uploadData` requests. - -```javascript -import { uploadData, isCancelError } from 'aws-amplify/storage'; - -// Pause, resume, and cancel a task -const uploadTask = uploadData({ path, data: file }); -//... -uploadTask.pause(); -//... -uploadTask.resume(); -//... -uploadTask.cancel(); -//... -try { - await uploadTask.result; -} catch (error) { - if (isCancelError(error)) { - // Handle error thrown by task cancellation - } -} -``` - - - -### Pause, resume, and cancel uploads - -Calls to `uploadData` or `uploadFile` return a reference to the task that is actually performing the upload. - -You can pause then resume the task or cancel a task as shown below. - -```swift -uploadTask.pause() -uploadTask.resume() -uploadTask.cancel() -``` - - - -Upload tasks are run using `URLSessionTask` instances internally. You can learn more about them in [Apple's official documentation](https://developer.apple.com/documentation/foundation/urlsessiontask). - - - - - -### All `upload` options - -Option | Type | Description | -| -- | -- | ----------- | -| metadata | [String: String] | Metadata for the object to store. | -| contentType | String | The standard MIME type describing the format of the object to store. | -| bucket | StorageBucket | The bucket in which the object should be stored | - - - -## Working with Security Scoped Resources (from iCloud) -Security scoped resources refer to files that are retrieved from iCloud or other cloud storage providers. You're likely to run into these file types when using system components that provide access to files stored in iCloud, e.g. [UIDocumentBrowserViewController](https://developer.apple.com/documentation/uikit/uidocumentbrowserviewcontroller). - -To upload security scoped resources, you'll need to: -1. use [startAccessingSecurityScopedResource()](https://developer.apple.com/documentation/foundation/url/1779698-startaccessingsecurityscopedreso) and [stopAccessingSecurityScopedResource()](https://developer.apple.com/documentation/foundation/url/1780153-stopaccessingsecurityscopedresou) to access the data within security scoped files -2. temporarily persist the data from the security scoped files in your app's sandbox -3. upload files using the temporary URLs -4. delete temporarily persisted files (optional) -```swift -struct ScopedResourceFile { - let name: String - let data: Data -} - -func getTempUrls(securityScopedUrls: [URL]) -> [URL] { - // 1. get the content of security scoped resources into ScopedResourceFile struct - let fileContents = securityScopedUrls.compactMap { url -> ScopedResourceFile? in - let startAccess = url.startAccessingSecurityScopedResource() - guard startAccess else { - print("Issue accessing security scoped resource at :\(url)") - return nil - } - defer { url.stopAccessingSecurityScopedResource() } - do { - let data = try Data(contentsOf: url) - let fileName = url.lastPathComponent - return ScopedResourceFile(name: fileName, data: data) - } catch { - print("Couldn't create Data from contents of file at url: \(url)") - return nil - } - } - - // 2. write the file contents to temporary files and return the URLs of the temp files - let localFileURLs = persistTemporaryFiles(fileContents) - - // 3. Now you have local URLs for the files you'd like to upload. - return localFileURLs -} -``` - - - -### Transfer with Object Metadata - -Custom metadata can be associated with your uploaded object by passing the metadata option. - -```ts -import { uploadData } from 'aws-amplify/storage'; - -const result = await uploadData({ - path: 'album/2024/1.jpg', - data: file, - options: { - metadata: { - customKey: 'customValue', - }, - }, -}); -``` - -### More upload options - -The behavior of `uploadData` and properties of the uploaded object can be customized by passing in additional options. - -```ts -import { uploadData } from 'aws-amplify/storage'; - -const result = await uploadData({ - path: 'album/2024/1.jpg', - data: file, - options: { - // content-type header to be used when downloading - contentType: "image/jpeg", - // configure how object is presented - contentDisposition: "attachment", - // whether to use accelerate endpoint - useAccelerateEndpoint: true, - // the account ID that owns requested bucket - expectedBucketOwner: "123456789012", - // whether to check if an object with the same key already exists before completing the upload - preventOverwrite: true, - // whether to compute the checksum for the data to be uploaded, so the S3 can verify the data integrity - checksumAlgorithm: "crc-32", // only 'crc-32' is supported currently - }, -}); -``` -Option | Type | Default | Description | -| -- | :--: | :--: | ----------- | -| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | -| contentType | string | application/octet-stream | The default content-type header value of the file when downloading it.

    Read more at [Content-Type documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) | -| contentEncoding | string | β€” | The default content-encoding header value of the file when downloading it.

    Read more at [Content-Encoding documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding) | -| contentDisposition | string | β€” | Specifies presentational information for the object.

    Read more at [Content-Disposition documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) | -| metadata | map\ | β€” | A map of metadata to store with the object in S3.

    Read more at [S3 metadata documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html#UserMetadata) | -| useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/upload-files/#transfer-acceleration) | -| expectedBucketOwner | string | - | The account ID that owns requested bucket. | -| preventOverwrite | boolean | false | Whether to check if an object with the same key already exists before completing the upload. If exists, a `Precondition Failed` error will be thrown | -| checksumAlgorithm | "crc-32" | - | Whether to compute the checksum for the data to be uploaded, so the S3 can verify the data integrity. Only 'crc-32' is supported currently | - - - -Uploads that were initiated over one hour ago will be cancelled automatically. There are instances (e.g. device went offline, user logs out) where the incomplete file remains in your Amazon S3 account. It is recommended to [setup a S3 lifecycle rule](https://aws.amazon.com/blogs/aws-cloud-financial-management/discovering-and-deleting-incomplete-multipart-uploads-to-lower-amazon-s3-costs/) to automatically cleanup incomplete upload requests. - - - - -## MultiPart upload - -Amplify will automatically perform an Amazon S3 multipart upload for objects that are larger than 5MB. For more information about S3's multipart upload, see [Uploading and copying objects using multipart upload](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) - ---- - ---- -title: "Download files" -section: "build-a-backend/storage" -platforms: ["angular", "javascript", "nextjs", "react", "vue", "android", "swift", "flutter", "react-native"] -gen: 2 -last-updated: "2025-07-04T12:58:35.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/download-files/" ---- - - -## Storage Image React UI Component - -You can easily display images in your app by using the cloud-connected Storage Image React UI component. This component fetches images securely from your storage resource and displays it on the web page. - -```bash title="Terminal" showLineNumbers={false} -npm add @aws-amplify/ui-react-storage aws-amplify -``` -```tsx -import { StorageImage } from '@aws-amplify/ui-react-storage'; - -export const DefaultStorageImageExample = () => { - return ; -}; -``` - -Learn more about how you can further customize the UI component by referring to the [Storage Image documentation](https://ui.docs.amplify.aws/react/connected-components/storage/storageimage). - - -To further customize your in-app experience, you can use the `getUrl` or `downloadData` API from the Amplify Library for Storage. - - - -**Note:** Refer to [the Transfer Acceleration documentation](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) to learn how to enable transfer acceleration for storage APIs. - - - -## Get or download file from a URL - -With the `getUrl` API, you can get a presigned URL which is valid for 900 seconds or 15 minutes by default. You can use this URL to create a download link for users to click on. The `expiresAt` property is a `Date` object that represents the time at which the URL will expire. - - -```typescript -import { getUrl } from 'aws-amplify/storage'; - -const linkToStorageFile = await getUrl({ - path: "album/2024/1.jpg", - // Alternatively, path: ({identityId}) => `album/${identityId}/1.jpg` -}); -console.log('signed URL: ', linkToStorageFile.url); -console.log('URL expires at: ', linkToStorageFile.expiresAt); -``` -Inside your template or JSX code, you can use the `url` property to create a link to the file: - -```tsx - - {fileName} - -``` - - - -This function does not check if the file exists by default. As result, the signed URL may fail if the file to be downloaded does not exist. - - - -### More getUrl options - -The behavior of the `getUrl` API can be customized by passing in options. - -```typescript -import { getUrl } from 'aws-amplify/storage'; - -const linkToStorageFile = await getUrl({ - path: "album/2024/1.jpg", - options: { - // specify a target bucket using name assigned in Amplify Backend - bucket: 'assignedNameInAmplifyBackend', - // ensure object exists before getting url - validateObjectExistence: true, - // url expiration time in seconds. - expiresIn: 300, - // whether to use accelerate endpoint - useAccelerateEndpoint: true, - // The account ID that owns the requested bucket. - expectedBucketOwner: '123456789012', - } -}); -``` - -Option | Type | Default | Description | -| :--: | :--: | :--: | ----------- | -| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) -| validateObjectExistence | boolean | false | Whether to head object to make sure the object existence before downloading. | -| expiresIn | number | 900 | Number of seconds till the URL expires.

    The expiration time of the presigned url is dependent on the session and will max out at 1 hour. | -| useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | -| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | - - - - -#### [Java] - -```java -Amplify.Storage.getUrl( - StoragePath.fromString("public/example"), - result -> Log.i("MyAmplifyApp", "Successfully generated: " + result.getUrl()), - error -> Log.e("MyAmplifyApp", "URL generation failure", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Storage.getUrl( - StoragePath.fromString("public/example"), - { Log.i("MyAmplifyApp", "Successfully generated: ${it.url}") }, - { Log.e("MyAmplifyApp", "URL generation failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val url = Amplify.Storage.getUrl(StoragePath.fromString("public/example")).url - Log.i("MyAmplifyApp", "Successfully generated: $url") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "URL generation failure", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Storage.getUrl(StoragePath.fromString("public/example")).subscribe( - result -> Log.i("MyAmplifyApp", "Successfully generated: " + result.getUrl()), - error -> Log.e("MyAmplifyApp", "URL generation failure", error) -); -``` - -### Check the existence of a file - -When creating a downloadable URL, you can choose to check if the file exists by setting `validateObjectExistence` to -`true` in `AWSS3StorageGetPresignedUrlOptions`. If the file is inaccessible or does not exist, a `StorageException` is thrown. -This allows you to check if an object exists when generating the presigned URL, which you can then use to download -that object. - -#### [Java] - -```java -AWSS3StorageGetPresignedUrlOptions options = AWSS3StorageGetPresignedUrlOptions - .builder() - .setValidateObjectExistence(true) - .build(); - -Amplify.Storage.getUrl( - StoragePath.fromString("public/example"), - options, - result -> Log.i("MyAmplifyApp", "Successfully generated: " + result.getUrl()), - error -> Log.e("MyAmplifyApp", "URL generation failure", error) -); -``` - -#### [Kotlin - Callbacks] -```kotlin -val options = AWSS3StorageGetPresignedUrlOptions - .builder() - .setValidateObjectExistence(true) - .build() - -Amplify.Storage.getUrl( - StoragePath.fromString("public/example"), - options, - { Log.i("MyAmplifyApp", "Successfully generated: ${it.url}") }, - { Log.e("MyAmplifyApp", "URL generation failure", it) } -) -``` - -#### [Kotlin - Coroutines] -```kotlin -try { - val options = AWSS3StorageGetPresignedUrlOptions - .builder() - .setValidateObjectExistence(true) - .build() - - val url = Amplify.Storage.getUrl(StoragePath.fromString("public/example"), options).url - Log.i("MyAmplifyApp", "Successfully generated: $url") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "URL generation failure", error) -} -``` - -#### [RxJava] -```java -AWSS3StorageGetPresignedUrlOptions options = AWSS3StorageGetPresignedUrlOptions - .builder() - .setValidateObjectExistence(true) - .build(); - -RxAmplify.Storage.getUrl(StoragePath.fromString("public/example"), options).subscribe( - result -> Log.i("MyAmplifyApp", "Successfully generated: " + result.getUrl()), - error -> Log.e("MyAmplifyApp", "URL generation failure", error) -); -``` - -### All `getURL` options - -Option | Type | Description | -| -- | -- | ----------- | -| bucket | StorageBucket | The bucket in which the object is stored. | -| expires | Integer | Number of seconds before the URL expires. | -| useAccelerateEndpoint | Boolean | Flag to configure use of acceleration mode. | -| validateObjectExistence | Boolean | Flag to check if the file exists. | - - - -```swift -let url = try await Amplify.Storage.getURL( - path: .fromString("public/example/path") -) -print("Completed: \(url)") -``` - -### Check the existence of a file - -When creating a downloadable URL, you can choose to check if the file exists by setting `validateObjectExistence` to -`true` in `AWSStorageGetURLOptions`. If the file is inaccessible or does not exist, a `StorageError` is thrown. -This allows you to check if an object exists during generating the presigned URL, which you can then use to download -that object. - -```swift -let url = try await Amplify.Storage.getURL( - path: .fromString("public/example/path"), - options: .init( - pluginOptions: AWSStorageGetURLOptions( - validateObjectExistence: true - ) - ) -) -``` - -### All `getURL` options - -Option | Type | Description | -| -- | -- | ----------- | -| expires | Int | Number of seconds before the URL expires | -| bucket | StorageBucket | The bucket in which the object is stored | - - - -When creating a downloadable URL, you can choose to check if the file exists by setting `validateObjectExistence` to -`true` in `S3GetUrlPluginOptions`. If the file is inaccessible or does not exist, a `StorageException` is thrown. -This allows you to check if an object exists during generating the presigned URL, which you can then use to download -that object. You may also pass in a bucket to target into `StorageGetUrlOptions` from either the chosen name in the -backend or the console name and region. If no bucket is provided, the default bucket defined in the backend will be used. -Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) - -```dart -Future getDownloadUrl() async { - try { - final result = await Amplify.Storage.getUrl( - path: const StoragePath.fromString('public/example.txt'), - /* - // targeting a specific bucket by the name defined in the backend - options: StorageGetUrlOptions( - bucket: StorageBucket.fromOutputs('secondBucket'), - ), - */ - ).result; - safePrint('url: ${result.url}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - - - -## Download to a file - -Use the `downloadData` API to download the file locally. - -```javascript -import { downloadData } from 'aws-amplify/storage'; - -// Downloads file content to memory -const { body, eTag } = await downloadData({ - path: "album/2024/1.jpg" -}).result; -``` - - - -## Download to a file - -Use the `downloadFile` API to download the file locally on the client. - - -**Note:** When downloading a file that will overwrite a preexisting file, ensure that your app has the proper write permission to overwrite it. If you are attempting to write to a file that a different app already contributed to the media store, you must request user consent[as described here](https://developer.android.com/training/data-storage/shared/media#update-other-apps-files). - -To learn more, refer to Android's developer documentation about [Scoped Storage](https://developer.android.com/training/data-storage#scoped-storage). - -Amplify will throw a `StorageException` if it is unable to modify a preexisting file. - - -#### [Java] - -```java -Amplify.Storage.downloadFile( - StoragePath.fromString("public/example"), - new File(getApplicationContext().getFilesDir() + "/download.txt"), - result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), - error -> Log.e("MyAmplifyApp", "Download Failure", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val file = File("${applicationContext.filesDir}/download.txt") -Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, - { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, - { Log.e("MyAmplifyApp", "Download Failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val file = File("${applicationContext.filesDir}/download.txt") - val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file) - try { - val fileName = download.result().file.name - Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") - } catch (error: StorageException) { - Log.e("MyAmplifyApp", "Download Failure", error) - } -} -``` - -#### [RxJava] - -```java -RxProgressAwareSingleOperation download = - RxAmplify.Storage.downloadFile( - StoragePath.fromString("public/example"), - new File(getApplicationContext().getFilesDir() + "/download.txt") - ); - -download - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), - error -> Log.e("MyAmplifyApp", "Download Failure", error) - ); -``` - - - - -## Download files -### Download to a local file - -Use the `downloadFile` API to download the file locally on the client. - -You can download to a file [URL](https://developer.apple.com/documentation/foundation/url) with `Amplify.Storage.downloadFile`: - -#### [Async/Await] -```swift -let downloadToFileUrl = FileManager.default.urls( - for: .documentDirectory, - in: .userDomainMask -)[0].appendingPathComponent("myFile.txt") - -let downloadTask = Amplify.Storage.downloadFile( - path: .fromString("public/example/path"), - local: downloadToFileUrl, - options: nil -) -Task { - for await progress in await downloadTask.progress { - print("Progress: \(progress)") - } -} -try await downloadTask.value -print("Completed") -``` - -#### [Combine] -```swift -let downloadToFileUrl = FileManager.default.urls( - for: .documentDirectory, - in: .userDomainMask -)[0].appendingPathComponent("myFile.txt") - -let downloadTask = Amplify.Storage.downloadFile( - path: .fromString("public/example/path"), - local: downloadToFileUrl, - options: nil -) -let progressSink = downloadTask - .inProcessPublisher - .sink { progress in - print("Progress: \(progress)") - } - -let resultSink = downloadTask - .resultPublisher - .sink { - if case let .failure(storageError) = $0 { - print("Failed: \(storageError.errorDescription). \(storageError.recoverySuggestion)") - } - } - receiveValue: { - print("Completed") - } -``` - -### Download to data in memory - -You can download to in-memory buffer [Data](https://developer.apple.com/documentation/foundation/data) object with `Amplify.Storage.downloadData`: - -#### [Async/Await] - -```swift -let downloadTask = Amplify.Storage.downloadData( - path: .fromString("public/example/path") -) -Task { - for await progress in await downloadTask.progress { - print("Progress: \(progress)") - } -} -let data = try await downloadTask.value -print("Completed: \(data)") -``` - -#### [Combine] - -```swift -let downloadTask = Amplify.Storage.downloadData( - path: .fromString("public/example/path") -) -let progressSink = downloadTask - .inProcessPublisher - .sink { progress in - print("Progress: \(progress)") - } - -let resultSink = downloadTask - .resultPublisher - .sink { - if case let .failure(storageError) = $0 { - print("Failed: \(storageError.errorDescription). \(storageError.recoverySuggestion)") - } - } - receiveValue: { data in - print("Completed: \(data)") - } -``` - -### Download from a specified bucket - -You can also perform a download operation from a specific bucket by providing the `bucket` option - -#### [From Outputs] -You can use `.fromOutputs(name:)` to provide a string representing the target bucket's assigned name in the Amplify Backend. - -```swift -// Download to File -let downloadTask = Amplify.Storage.downloadFile( - path: .fromString("public/example/path"), - local: downloadToFileUrl, - options: .init( - bucket: .fromOutputs(name: "secondBucket") - ) -) - -// Download to Data -let downloadTask = Amplify.Storage.downloadData( - path: .fromString("public/example/path"), - options: .init( - bucket: .fromOutputs(name: "secondBucket") - ) -) -``` - -#### [From Bucket Info] -You can also use `.fromBucketInfo(_:)` to provide a bucket name and region directly. - -```swift -// Download to File -let downloadTask = Amplify.Storage.downloadFile( - path: .fromString("public/example/path"), - local: downloadToFileUrl, - options: .init( - bucket: .fromBucketInfo(.init( - bucketName: "another-bucket-name", - region: "another-bucket-region") - ) - ) -) - -// Download to Data -let downloadTask = Amplify.Storage.downloadData( - path: .fromString("public/example/path"), - options: .init( - bucket: .fromBucketInfo(.init( - bucketName: "another-bucket-name", - region: "another-bucket-region") - ) - ) -) -``` - - - - -## Download to a file - -You can download a file to a local directory using `Amplify.Storage.downloadFile`. - -You can use the [path_provider](https://pub.dev/packages/path_provider) package to create a local file in the user's documents directory where you can store the downloaded data. - -#### [Mobile & Desktop] - -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:path_provider/path_provider.dart'; - -Future downloadFile() async { - final documentsDir = await getApplicationDocumentsDirectory(); - final filepath = '${documentsDir.path}/example.txt'; - try { - final result = await Amplify.Storage.downloadFile( - path: const StoragePath.fromString('public/example.txt'), - localFile: AWSFile.fromPath(filepath), - ).result; - safePrint('Downloaded file is located at: ${result.localFile.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - -#### [Web] - -On Web, the download process will be handled by the browser. You can provide the downloaded file name by specifying the `path` parameter of `AWSFile.fromPath`. E.g. this instructs the browser to download the file `download.txt`. - -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; - -Future downloadFile() async { - try { - final result = await Amplify.Storage.downloadFile( - path: const StoragePath.fromString('public/example.txt'), - localFile: AWSFile.fromPath('download.txt'), - ).result; - safePrint('Downloaded file: ${result.downloadedItem.path}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - - - - -### Get the text value of downloaded File - -You can get the value of file in any of the three formats: `blob`, `json`, or `text`. You can call the respective method on the `body` property to consume the set data in the respective format. - -```javascript -import { downloadData } from 'aws-amplify/storage'; - -try { - const downloadResult = await downloadData({ - path: "album/2024/1.jpg" - }).result; - const text = await downloadResult.body.text(); - // Alternatively, you can use `downloadResult.body.blob()` - // or `downloadResult.body.json()` get read body in Blob or JSON format. - console.log('Succeed: ', text); -} catch (error) { - console.log('Error : ', error); -} -``` - - - -### Download from a specified bucket - -You can also perform an upload operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. - -```ts -import { downloadData } from 'aws-amplify/storage'; - -const result = await downloadData({ - path: 'album/2024/1.jpg', - options: { - // highlight-start - // Specify a target bucket using name assigned in Amplify Backend - bucket: 'assignedNameInAmplifyBackend' - // highlight-end - } -}).result; - -``` -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. - -```ts -import { downloadData } from 'aws-amplify/storage'; - -const result = await downloadData({ - path: 'album/2024/1.jpg', - options: { - // highlight-start - // Alternatively, provide bucket name from console and associated region - bucket: { - bucketName: 'bucket-name-from-console', - region: 'us-east-2' - } - // highlight-end - } -}).result; - -``` -### Monitor download progress - -```javascript -import { downloadData } from 'aws-amplify/storage'; - -// Download a file from S3 bucket -const { body, eTag } = await downloadData( - { - path: 'album/2024/1.jpg', - options: { - onProgress: (progress) => { - console.log(`Download progress: ${(progress.transferredBytes/progress.totalBytes) * 100}%`); - } - } - } -).result; -``` - -### Cancel download - -```javascript -import { downloadData, isCancelError } from 'aws-amplify/storage'; - -const downloadTask = downloadData({ path: 'album/2024/1.jpg' }); -downloadTask.cancel(); -try { - await downloadTask.result; -} catch (error) { - if (isCancelError(error)) { - // Handle error thrown by task cancellation. - } -} -``` - - -### Download from a specified bucket - -You can also perform a download operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. - -#### [Java] - -```java -StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); -StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); -Amplify.Storage.downloadFile( - StoragePath.fromString("public/example"), - new File(getApplicationContext().getFilesDir() + "/download.txt"), - options, - result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), - error -> Log.e("MyAmplifyApp", "Download Failure", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val secondBucket = StorageBucket.fromOutputs("secondBucket") -val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() -val file = File("${applicationContext.filesDir}/download.txt") -Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, option, - { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, - { Log.e("MyAmplifyApp", "Download Failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val secondBucket = StorageBucket.fromOutputs("secondBucket") -val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() -val file = File("${applicationContext.filesDir}/download.txt") -val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options) -try { - val fileName = download.result().file.name - Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "Download Failure", error) -} -``` - -#### [RxJava] - -```java -StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); -StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); -RxProgressAwareSingleOperation download = - RxAmplify.Storage.downloadFile( - StoragePath.fromString("public/example"), - new File(getApplicationContext().getFilesDir() + "/download.txt"), - options - ); - -download - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), - error -> Log.e("MyAmplifyApp", "Download Failure", error) - ); -``` - -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. - -#### [Java] - -```java -BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); -StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); -StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); -Amplify.Storage.downloadFile( - StoragePath.fromString("public/example"), - new File(getApplicationContext().getFilesDir() + "/download.txt"), - options, - result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), - error -> Log.e("MyAmplifyApp", "Download Failure", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") -val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) -val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() -val file = File("${applicationContext.filesDir}/download.txt") -Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options, - { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, - { Log.e("MyAmplifyApp", "Download Failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") -val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) -val options = StorageDownloadFileOptions.builder().bucket(secondBucket).build() -val file = File("${applicationContext.filesDir}/download.txt") -val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options) -try { - val fileName = download.result().file.name - Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "Download Failure", error) -} -``` - -#### [RxJava] - -```java -BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); -StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); -StorageDownloadFileOptions options = StorageDownloadFileOptions.builder().bucket(secondBucket).build(); -RxProgressAwareSingleOperation download = - RxAmplify.Storage.downloadFile( - StoragePath.fromString("public/example"), - new File(getApplicationContext().getFilesDir() + "/download.txt"), - options, - ); - -download - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), - error -> Log.e("MyAmplifyApp", "Download Failure", error) - ); -``` - -### Monitor download progress - -#### [Java] - -```java -Amplify.Storage.downloadFile( - StoragePath.fromString("public/example"), - new File(getApplicationContext().getFilesDir() + "/download.txt"), - StorageDownloadFileOptions.defaultInstance(), - progress -> Log.i("MyAmplifyApp", "Fraction completed: " + progress.getFractionCompleted()), - result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()), - error -> Log.e("MyAmplifyApp", "Download Failure", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val file = File("${applicationContext.filesDir}/download.txt") -val options = StorageDownloadFileOptions.defaultInstance() -Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options, - { Log.i("MyAmplifyApp", "Fraction completed: ${it.fractionCompleted}") }, - { Log.i("MyAmplifyApp", "Successfully downloaded: ${it.file.name}") }, - { Log.e("MyAmplifyApp", "Download Failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val file = File("${applicationContext.filesDir}/download.txt") -val options = StorageDownloadFileOptions.defaultInstance() -val download = Amplify.Storage.downloadFile(StoragePath.fromString("public/example"), file, options) -val progressJob = activityScope.async { - download.progress().collect { progress -> - Log.i("MyAmplifyApp", "Fraction completed: ${progress.fractionCompleted}") - } -} -try { - val fileName = download.result().file.name - Log.i("MyAmplifyApp", "Successfully downloaded: $fileName") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "Download Failure", error) -} -progressJob.cancel() -``` - -#### [RxJava] - -```java -RxProgressAwareSingleOperation download = - RxAmplify.Storage.downloadFile(StoragePath.fromString("public/example"), localFile); - -download - .observeProgress() - .subscribe( - progress -> Log.i("MyAmplifyApp", progress.getFractionCompleted()) - ); -``` - - - - -### Monitor download progress - -```dart -final operation = Amplify.Storage.downloadData( - path: const StoragePath.fromString('public/example.txt'), - onProgress: (progress) { - safePrint('fraction totalBytes: ${progress.totalBytes}'); - safePrint('fraction transferredBytes: ${progress.transferredBytes}'); - safePrint('fraction completed: ${progress.fractionCompleted}'); - }, -); -``` - - - -### Pause, resume, and cancel downloads - -```dart - -Future upload() async { - final operation = Amplify.Storage.downloadFile( - localFile: AWSFile.fromPath('/path/to/local/file'), - path: const StoragePath.fromString('public/example.txt'), - ); - - // pause operation - await operation.pause(); - - // resume operation - await operation.resume(); - - // cancel operation - await operation.cancel(); -} - -``` - - - -### Query transfers - -When an upload or download operation is requested using the Amplify Android library, the request is first persisted in the local SQLite Database and then queued for execution. You can query the transfer operation queued in the local database using the transfer ID returned by an upload or download API. Get-Transfer API could retrieve a pending transfer previously en-queued and enable attaching a listener to receive updates on progress change, on-error or on-success, or pause, cancel or resume it. - -#### [Java] - -```java -Amplify.Storage.getTransfer("TRANSFER_ID", - operation -> { - Log.i("MyAmplifyApp", "Current State" + operation.getTransferState()); - // set listener to receive updates - operation.setOnProgress( progress -> {}); - operation.setOnSuccess( result -> {}); - operation.setOnError(error -> {}); - - // possible actions - operation.pause(); - operation.resume(); - operation.start(); - operation.cancel(); - }, - { - error -> Log.e("MyAmplifyApp", "Failed to query transfer", error) - } -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Storage.getTransfer("TRANSFER_ID", - { operation -> - Log.i("MyAmplifyApp", "Current State" + operation.transferState) - // set listener to receive updates - operation.setOnProgress { } - operation.setOnSuccess { } - operation.setOnError { } - - // possible actions - operation.pause() - operation.resume() - operation.start() - operation.cancel() - }, - { - Log.e("MyAmplifyApp", "Failed to query transfer", it) - } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val operation = Amplify.Storage.getTransfer("TRANSFER_ID") - Log.i("MyAmplifyApp", "Current State" + operation.transferState) - // set listener to receive updates - operation.setOnProgress { } - operation.setOnSuccess { } - operation.setOnError { } - - // possible actions - operation.pause() - operation.resume() - operation.start() - operation.cancel() -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "Failed to query transfer", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Storage.getTransfer("TRANSFER_ID") - .subscribe( - operation -> { - Log.i("MyAmplifyApp", "Current State" + operation.getTransferState()); - // set listener to receive updates - operation.setOnProgress( progress -> {}); - operation.setOnSuccess( result -> {}); - operation.setOnError(error -> {}); - - // possible actions - operation.pause(); - operation.resume(); - operation.start(); - operation.cancel(); - }, - error -> Log.e("MyAmplifyApp", "Failed to query transfer", error); - ); -``` - - - - -## API to download data in memory - -You can download a file to in-memory buffer with `Amplify.Storage.downloadData`: - -```dart -Future download() async { - try { - final result = await Amplify.Storage.downloadData( - path: const StoragePath.fromString('public/example.txt'), - ).result; - safePrint('Downloaded data: ${result.bytes}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - -## More download options - -Option | Type | Description | -| -- | -- | ----------- | -| bucket | StorageBucket | The target bucket from the assigned name in the Amplify Backend or from the bucket name and region in the console

    Defaults to the default bucket and region from the Amplify configuration if this option is not provided.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | -| getProperties | boolean | Whether to retrieve properties for the downloaded object using theAmplify.Storage.getProperties() after the operation completes. When set to true the returned item will contain additional info such as metadata and content type. | -| useAccelerateEndpoint | boolean | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/upload-files/#transfer-acceleration) | -| bytesRange | S3DataBytesRange | The byte range to download from the object | - -### Example of `downloadFile` with options - -```dart -final operation = Amplify.Storage.downloadFile( - path: const StoragePath.fromString('public/example.txt'), - localFile: AWSFile.fromPath('/path/to/local/file.txt'), - options: const StorageDownloadFileOptions( - pluginOptions: S3DownloadFilePluginOptions( - getProperties: true, - useAccelerateEndpoint: true, - ), - bucket: StorageBucket.fromOutputs('secondBucket'), - ), -); -``` - -### Example of `downloadData` with options - -```dart -final operation = Amplify.Storage.downloadData( - path: const StoragePath.fromString('public/example.txt'), - options: StorageDownloadDataOptions( - pluginOptions: S3DownloadDataPluginOptions( - getProperties: true, - useAccelerateEndpoint: true, - bytesRange: S3DataBytesRange(start: 0, end: 100), - ), - ), -); -``` - -You can also perform a `downloadData` or `downloadFile` operation to a specific bucket by providing the `bucket` option. You can pass in a `StorageBucket` object representing the target bucket from the name defined in the Amplify Backend. - -```dart -final operation = Amplify.Storage.downloadFile( - path: const StoragePath.fromString('public/example.txt'), - localFile: AWSFile.fromPath('/path/to/local/file.txt'), - options: const StorageDownloadFileOptions( - pluginOptions: S3DownloadFilePluginOptions( - getProperties: true, - useAccelerateEndpoint: true, - ), - bucket: StorageBucket.fromOutputs('secondBucket'), - ), -); -``` - -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. - -```dart -final operation = Amplify.Storage.downloadData( - path: const StoragePath.fromString('public/example.txt'), - options: StorageDownloadDataOptions( - pluginOptions: S3DownloadDataPluginOptions( - getProperties: true, - useAccelerateEndpoint: true, - bytesRange: S3DataBytesRange(start: 0, end: 100), - ), - bucket: StorageBucket.fromBucketInfo( - BucketInfo( - bucketName: 'second-bucket-name-from-console', - region: 'us-east-2', - ), - ), - ), -); -``` - - - -### Pause, resume, and cancel downloads - -Calls to `downloadData` or `downloadFile` return a reference to the task that is actually performing the download. - -You can pause then resume the task or cancel a task as shown below. - -```swift -downloadTask.pause() -downloadTask.resume() -downloadTask.cancel() -``` - - - -Download tasks are run using `URLSessionTask` instances internally. You can learn more about them in [Apple's official documentation](https://developer.apple.com/documentation/foundation/urlsessiontask). - - - - - -### More download options -The behavior of the `downloadData` API can be customized by passing in options. - -```javascript -import { downloadData } from 'aws-amplify/storage'; - -// Downloads file content to memory -const { body, eTag } = await downloadData({ - path: "album/2024/1.jpg", - options: { - // optional bytes range parameter to download a part of the file, the 2nd MB of the file in this example - bytesRange: { - start: 1024, - end: 2048 - }, - useAccelerateEndpoint: true, - } -}).result; - -``` - -Option | Type | Default | Description | -| :--: | :--: | :--: | ----------- | -| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | -| onProgress | callback | β€” | Callback function tracking the upload/download progress. | -| bytesRange | \{ start: number; end:number; \} | β€” | Bytes range parameter to download a part of the file. | -| useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | -| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | - -## Frequently Asked Questions - -- [Image compression](https://github.com/aws-amplify/amplify-js/issues/6081) or CloudFront CDN caching for your S3 buckets is not yet possible. -- `downloadData` does not provide a cache control option and it replies on runtime HTTP caching behavior. If you need to bypass the cache, you can use the `getUrl` API to create a presigned URL for downloading the file. -- `downloadData` does not support S3 object versioning, it always downloads the latest version. - - ---- - ---- -title: "List file properties" -section: "build-a-backend/storage" -platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] -gen: 2 -last-updated: "2025-02-07T16:03:59.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/list-files/" ---- - -You can list files without having to download all the files. You can do this by using the `list` API from the Amplify Library for Storage. -You can also get properties individually for a file using the `getProperties` API. - - -## List Files - - -```javascript -import { list } from 'aws-amplify/storage'; - -const result = await list({ - path: 'album/photos/', - // Alternatively, path: ({identityId}) => `album/${identityId}/photos/` -}); -``` - -Note the trailing slash `/` - if you had requested `list({ path : 'album/photos' })` it would also match against files like `album/photos123.jpg` alongside `album/photos/123.jpg`. - -The format of the response will look similar to the below example: - -```js -{ - items: [ - { - path: "album/photos/123.jpg", - eTag: "30074401292215403a42b0739f3b5262", - lastModified: "Thu Oct 08 2020 23:59:31 GMT+0800 (Singapore Standard Time)", - size: 138256 - }, - // ... - ], -} -```` - -If the `pageSize` is set lower than the total file size, a single `list` call only returns a subset of all the files. To list all the files with multiple calls, users can use the `nextToken` flag: - -```javascript -import { list } from 'aws-amplify/storage'; - -const PAGE_SIZE = 20; -let nextToken; -// ... -const loadNextPage = async () => { - const response = await list({ - path: 'photos/', - // Alternatively, path: ({ identityId }) => `album/${identityId}/photos/` - options: { - pageSize: PAGE_SIZE, - nextToken, - }, - }); - if (response.nextToken) { - nextToken = response.nextToken; - } else { - nextToken = undefined; - } - // render list items from response.items -}; -``` - -### List All files -```javascript -import { list } from 'aws-amplify/storage'; - -const result = await list({ - path: 'album/photos/', - // Alternatively, path: ({identityId}) => `album/${identityId}/photos/`, - options: { - listAll: true, - } -}); -``` - - Manually created folders will show up as files with a `size` of 0, but you can also match keys against a regex like `file.key.match(/\.[0-9a-z]+$/i)` to distinguish files from folders. Since "folders" are a virtual concept in Amazon S3, any file may declare any depth of folder just by having a `/` in its name. - -To access the contents and subpaths of a "folder", you have two options: - -1. Request the entire path and parse the contents. -2. Use the subpathStrategy option to retrieve only the files within the specified path (i.e. exclude files under subpaths). - -### Get all nested files within a path - -This retrieves all files and folders under a given path. You may need to parse the result to get only the files within the specified path. - -```js -function processStorageList(response) { - let files = []; - let folders = new Set(); - response.items.forEach((res) => { - if (res.size) { - files.push(res); - // sometimes files declare a folder with a / within then - let possibleFolder = res.path.split('/').slice(0, -1).join('/'); - if (possibleFolder) folders.add(possibleFolder); - } else { - folders.add(res.path); - } - }); - return { files, folders }; -} -``` - -If you need the files and folders in terms of a nested object instead (for example, to build an explorer UI), you could parse it recursively: - -```js -function processStorageList(response) { - const filesystem = {}; - // https://stackoverflow.com/questions/44759750/how-can-i-create-a-nested-object-representation-of-a-folder-structure - const add = (source, target, item) => { - const elements = source.split('/'); - const element = elements.shift(); - if (!element) return; // blank - target[element] = target[element] || { __data: item }; // element; - if (elements.length) { - target[element] = - typeof target[element] === 'object' ? target[element] : {}; - add(elements.join('/'), target[element], item); - } - }; - response.items.forEach((item) => add(item.path, filesystem, item)); - return filesystem; -} -``` - -This places each item's data inside a special `__data` key. - -### Excluding subpaths - -In addition to using the `list` API to get all the contents of a path, you can also use it to get only the files within a path while excluding files under subpaths. - -For example, given the following keys in your `path` you may want to return only the jpg object, and not the "vacation" subpath and its contents: - -``` -photos/photo1.jpg -photos/vacation/ -``` - -This can be accomplished with the `subpathStrategy` option: - -```ts title="src/main.ts" -import { list } from "aws-amplify/storage"; -const result = await list({ - path: "photos/", - options:{ - subpathStrategy: { strategy:'exclude' } - } -}); -``` - -The response will include only the objects within the `photos/` path and will also communicate any excluded subpaths: - -```js -{ - excludedSubpaths: [ - 'photos/vacation/' - ], - items: [ - { - path: "photos/photo1.jpg", - eTag: "30074401292215403a42b0739f3b5262", - lastModified: "Thu Oct 08 2020 23:59:31 GMT+0800 (Singapore Standard Time)", - size: 138256 - }, - ] -} -``` - -The default delimiter character is '/', but this can be changed by supplying a custom delimiter: - -```ts title="src/main.ts" -const result = await list({ - // Path uses '-' character to organize files rather than '/' - path: 'photos-', - options: { - subpathStrategy: { - strategy: 'exclude', - delimiter: '-' - } - } -}); -``` - -### List files from a specified bucket - -You can also perform an `copy` operation to a specific bucket by providing the `bucket` option. This option can either be a string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console. - -```ts -import { list } from 'aws-amplify/storage'; - -const result = await list({ - path: 'photos/', - options: { - // Specify a target bucket using name assigned in Amplify Backend - bucket: 'assignedNameInAmplifyBackend', - // Alternatively, provide bucket name from console and associated region - // bucket: { - // bucketName: 'generated-secondary-bucket-name', - // region: 'us-east-2' - // } - } -}); -``` - -### More `list` options - -| Option | Type | Default | Description | -| -- | :--: | :--: | ----------- | -| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | -| listAll | boolean | false | Set to true to list all files within the specified `path` | -| pageSize | number | 1000 | Sets the maximum number of files to be return. The range is 0 - 1000 | -| nextToken | string | β€” | Indicates whether the list is being continued on this bucket with a token | -| subpathStrategy | \{ strategy: 'include' \} \|
    \{ 'exclude',
    delimiter?: string \} | \{ strategy: 'include' \} | An object representing the subpath inclusion strategy and the delimiter used to group results for exclusion.

    Read more at [Excluding subpaths](/[platform]/build-a-backend/storage/list-files/#excluding-subpaths) | -| useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

    Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | -| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | - - - -The following example lists all objects inside the `public/photos/` path: - -#### [Java] - -```java -StoragePagedListOptions options = StoragePagedListOptions.builder() - .setPageSize(1000) - .build(); - -Amplify.Storage.list( - StoragePath.fromString("public/photos/"), - options, - result -> { - for (StorageItem item : result.getItems()) { - Log.i("MyAmplifyApp", "Item: " + item.getPath()); - } - Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); - }, - error -> Log.e("MyAmplifyApp", "List failure", error); -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val options = StoragePagedListOptions.builder() - .setPageSize(1000) - .build() - -Amplify.Storage.list(StoragePath.fromString("public/photos/"), options, - { result -> - result.items.forEach { item -> - Log.i("MyAmplifyApp", "Item: ${item.path}") - } - Log.i("MyAmplifyApp", "Next Token: ${result.nextToken}") - }, - { Log.e("MyAmplifyApp", "List failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val options = StoragePagedListOptions.builder() - .setPageSize(1000) - .build() - -try { - val result = Amplify.Storage.list(StoragePath.fromString("public/photos/"), options) - result.items.forEach { - Log.i("MyAmplifyApp", "Item: $it") - } - Log.i("MyAmplifyApp", "next token: ${result.nextToken}") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "List failure", error) -} -``` - -#### [RxJava] - -```java -StoragePagedListOptions options = StoragePagedListOptions.builder() - .setPageSize(1000) - .build(); - -RxAmplify.Storage.list(StoragePath.fromString("public/photos/"), options) - .subscribe( - result -> { - for (StorageItem item : result.getItems()) { - Log.i("MyAmplifyApp", "Item: " + item.getPath()); - } - Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); - }, - error -> Log.e("MyAmplifyApp", "List failure", error); - ); -``` - - -Note the trailing slash `/` in the given path. - -If you had used `public/photos` as path, it would also match against files like `public/photos01.jpg`. - - -### List files from a specified bucket - -You can also perform a list operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. - -#### [Java] - -```java -StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); -StoragePagedListOptions options = StoragePagedListOptions.builder() - .setPageSize(1000) - .bucket(secondBucket) - .build(); - -Amplify.Storage.list( - StoragePath.fromString("public/photos/"), - options, - result -> { - for (StorageItem item : result.getItems()) { - Log.i("MyAmplifyApp", "Item: " + item.getPath()); - } - Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); - }, - error -> Log.e("MyAmplifyApp", "List failure", error); -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val secondBucket = StorageBucket.fromOutputs("secondBucket") -val options = StoragePagedListOptions.builder() - .setPageSize(1000) - .bucket(secondBucket) - .build() - -Amplify.Storage.list(StoragePath.fromString("public/photos/"), options, - { result -> - result.items.forEach { item -> - Log.i("MyAmplifyApp", "Item: ${item.path}") - } - Log.i("MyAmplifyApp", "Next Token: ${result.nextToken}") - }, - { Log.e("MyAmplifyApp", "List failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val secondBucket = StorageBucket.fromOutputs("secondBucket") -val options = StoragePagedListOptions.builder() - .setPageSize(1000) - .bucket(secondBucket) - .build() - -try { - val result = Amplify.Storage.list(StoragePath.fromString("public/photos/"), options) - result.items.forEach { - Log.i("MyAmplifyApp", "Item: $it") - } - Log.i("MyAmplifyApp", "next token: ${result.nextToken}") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "List failure", error) -} -``` - -#### [RxJava] - -```java -StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); -StoragePagedListOptions options = StoragePagedListOptions.builder() - .setPageSize(1000) - .bucket(secondBucket) - .build(); - -RxAmplify.Storage.list(StoragePath.fromString("public/photos/"), options) - .subscribe( - result -> { - for (StorageItem item : result.getItems()) { - Log.i("MyAmplifyApp", "Item: " + item.getPath()); - } - Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); - }, - error -> Log.e("MyAmplifyApp", "List failure", error); - ); -``` - -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. - -#### [Java] - -```java -BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); -StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); -StoragePagedListOptions options = StoragePagedListOptions.builder() - .setPageSize(1000) - .bucket(secondBucket) - .build(); - -Amplify.Storage.list( - StoragePath.fromString("public/photos/"), - options, - result -> { - for (StorageItem item : result.getItems()) { - Log.i("MyAmplifyApp", "Item: " + item.getPath()); - } - Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); - }, - error -> Log.e("MyAmplifyApp", "List failure", error); -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") -val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) -val options = StoragePagedListOptions.builder() - .setPageSize(1000) - .bucket(secondBucket) - .build() - -Amplify.Storage.list(StoragePath.fromString("public/photos/"), options, - { result -> - result.items.forEach { item -> - Log.i("MyAmplifyApp", "Item: ${item.path}") - } - Log.i("MyAmplifyApp", "Next Token: ${result.nextToken}") - }, - { Log.e("MyAmplifyApp", "List failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") -val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) -val options = StoragePagedListOptions.builder() - .setPageSize(1000) - .bucket(secondBucket) - .build() - -try { - val result = Amplify.Storage.list(StoragePath.fromString("public/photos/"), options) - result.items.forEach { - Log.i("MyAmplifyApp", "Item: $it") - } - Log.i("MyAmplifyApp", "next token: ${result.nextToken}") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "List failure", error) -} -``` - -#### [RxJava] - -```java -BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); -StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); -StoragePagedListOptions options = StoragePagedListOptions.builder() - .setPageSize(1000) - .bucket(secondBucket) - .build(); - -RxAmplify.Storage.list(StoragePath.fromString("public/photos/"), options) - .subscribe( - result -> { - for (StorageItem item : result.getItems()) { - Log.i("MyAmplifyApp", "Item: " + item.getPath()); - } - Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); - }, - error -> Log.e("MyAmplifyApp", "List failure", error); - ); -``` - -### Exclude results from nested subpaths - -By default, the `list` API will return all objects contained within the given path, including objects inside nested subpaths. - -For example, the previous `public/photos/` path would include these objects: - -```bash -Path: public/photos/photo1.jpg -Path: public/photos/vacation/photo1.jpg -Path: public/photos/thumbnails/photo1.jpg -``` - -In order to exclude objects within the `vacation` and `thumbnails` subpaths, you can set the `subpathStrategy` option to `SubpathStrategy.Exclude()`: - -#### [Java] - -```java -StoragePagedListOptions options = StoragePagedListOptions.builder() - .setPageSize(1000) - .setSubpathStrategy(SubpathStrategy.Exclude()) - .build(); - -Amplify.Storage.list( - StoragePath.fromString("public/photos/"), - options, - result -> { - for (StorageItem item : result.getItems()) { - Log.i("MyAmplifyApp", "Item: " + item.getPath()); - } - Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); - }, - error -> Log.e("MyAmplifyApp", "List failure", error); -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val options = StoragePagedListOptions.builder() - .setPageSize(1000) - .setSubpathStrategy(SubpathStrategy.Exclude()) - .build() - -Amplify.Storage.list(StoragePath.fromString("public/photos/"), options, - { result -> - result.items.forEach { item -> - Log.i("MyAmplifyApp", "Item: ${item.path}") - } - Log.i("MyAmplifyApp", "Next Token: ${result.nextToken}") - }, - { Log.e("MyAmplifyApp", "List failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val options = StoragePagedListOptions.builder() - .setPageSize(1000) - .setSubpathStrategy(SubpathStrategy.Exclude()) - .build() - -try { - val result = Amplify.Storage.list(StoragePath.fromString("public/photos/"), options) - result.items.forEach { - Log.i("MyAmplifyApp", "Item: $it") - } - Log.i("MyAmplifyApp", "next token: ${result.nextToken}") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "List failure", error) -} -``` - -#### [RxJava] - -```java -StoragePagedListOptions options = StoragePagedListOptions.builder() - .setPageSize(1000) - .setSubpathStrategy(SubpathStrategy.Exclude()) - .build(); - -RxAmplify.Storage.list(StoragePath.fromString("public/photos/"), options) - .subscribe( - result -> { - for (StorageItem item : result.getItems()) { - Log.i("MyAmplifyApp", "Item: " + item.getPath()); - } - Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); - }, - error -> Log.e("MyAmplifyApp", "List failure", error); - ); -``` - -The response will only include objects within the `public/photos/` path and will also provide a list of the excluded subpaths: - -```bash -Path: public/photos/photo01.jpg -Subpath: public/photos/vacation/ -Subpath: public/photos/thumbnails/ -``` - -The default delimiter character is `"/"`, but this can be changed by supplying a custom delimiter: - -#### [Java] - -```java -StoragePagedListOptions options = StoragePagedListOptions.builder() - .setPageSize(1000) - .setSubpathStrategy(SubpathStrategy.Exclude("-")) - .build(); - -Amplify.Storage.list( - StoragePath.fromString("public/photos/"), - options, - result -> { - for (StorageItem item : result.getItems()) { - Log.i("MyAmplifyApp", "Item: " + item.getPath()); - } - Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); - }, - error -> Log.e("MyAmplifyApp", "List failure", error); -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val options = StoragePagedListOptions.builder() - .setPageSize(1000) - .setSubpathStrategy(SubpathStrategy.Exclude("-")) - .build() - -Amplify.Storage.list(StoragePath.fromString("public/photos/"), options, - { result -> - result.items.forEach { item -> - Log.i("MyAmplifyApp", "Item: ${item.path}") - } - Log.i("MyAmplifyApp", "Next Token: ${result.nextToken}") - }, - { Log.e("MyAmplifyApp", "List failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val options = StoragePagedListOptions.builder() - .setPageSize(1000) - .setSubpathStrategy(SubpathStrategy.Exclude("-")) - .build() - -try { - val result = Amplify.Storage.list(StoragePath.fromString("public/photos/"), options) - result.items.forEach { - Log.i("MyAmplifyApp", "Item: $it") - } - Log.i("MyAmplifyApp", "next token: ${result.nextToken}") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "List failure", error) -} -``` - -#### [RxJava] - -```java -StoragePagedListOptions options = StoragePagedListOptions.builder() - .setPageSize(1000) - .setSubpathStrategy(SubpathStrategy.Exclude("-")) - .build(); - -RxAmplify.Storage.list(StoragePath.fromString("public/photos/"), options) - .subscribe( - result -> { - for (StorageItem item : result.getItems()) { - Log.i("MyAmplifyApp", "Item: " + item.getPath()); - } - Log.i("MyAmplifyApp", "Next Token: " + result.getNextToken()); - }, - error -> Log.e("MyAmplifyApp", "List failure", error); - ); -``` - -The response will only include objects within the `public/photos/` path not grouped by the delimiter `-`. - -```bash -Path: public/photos/2023/photos01.jpg -Path: public/photos/2024/photos02.jpg -Subpath: public/photos/202- -``` - -### All `list` options - -| Option | Type | Description | -| --- | --- | --- | -| subpathStrategy | SubpathStrategy | The strategy to use when listing contents from subpaths. | -| pageSize | int | Number between 1 and 1,000 that indicates the limit of how many entries to fetch when retrieving file lists from the server. | -| bucket | StorageBucket | The bucket in which the objects are stored. | -| nextToken | String | String indicating the page offset at which to resume a listing. | - -If the `pageSize` is set lower than the total file size available, a single `list` call only returns a subset of all the files. To list all the files with multiple calls, the user can use the `nextToken` value from the previous response. - - - -The following example lists all objects inside the `public/photos/` path: - -#### [Async/Await] - -```swift -let listResult = try await Amplify.Storage.list( - path: .fromString("public/photos/") -) -listResult.items.forEach { item in - print("Path: \(item.path)") -} -``` - -#### [Combine] - -```swift -let sink = Amplify.Publisher.create { - try await Amplify.Storage.list( - path: .fromString("public/photos/") - ) -}.sink { - if case let .failure(error) = $0 { - print("Failed: \(error)") - } -} -receiveValue: { listResult in - listResult.items.forEach { item in - print("Path: \(item.path)") - } -} -``` - - -Note the trailing slash `/` in the given path. - -If you had used `public/photos` as path, it would also match against files like `public/photos01.jpg`. - - -### List files from a specified bucket - -You can perform a list operation from a specific bucket by providing the `bucket` option. - -#### [From Outputs] -You can use `.fromOutputs(name:)` to provide a string representing the target bucket's assigned name in the Amplify Backend. - -```swift -let listResult = try await Amplify.Storage.list( - path: .fromString("public/photos/"), - options: .init( - bucket: .fromOutputs(name: "secondBucket") - ) -) -``` - -#### [From Bucket Info] -You can also use `.fromBucketInfo(_:)` to provide a bucket name and region directly. - -```swift -let listResult = try await Amplify.Storage.list( - path: .fromString("public/photos/"), - options: .init( - bucket: .fromBucketInfo(.init( - bucketName: "another-bucket-name", - region: "another-bucket-region") - ) - ) -) -``` - -### Exclude results from nested subpaths - -By default, the `list` API will return all objects contained within the given path, including objects inside nested subpaths. - -For example, the previous `public/photos/` path would include these objects: - -```bash -Path: public/photos/photo1.jpg -Path: public/photos/vacation/photo1.jpg -Path: public/photos/thumbnails/photo1.jpg -``` - -In order to exclude objects within the `vacation` and `thumbnails` subpaths, you can set the `subpathStrategy` option to `.exclude`: - -#### [Async/Await] - -```swift -let listResult = try await Amplify.Storage.list( - path: .fromString("public/photos/"), - options: .init( - subpathStrategy: .exclude - ) -) -listResult.items.forEach { item in - print("Path: \(item.path)") -} -listResult.excludedSubpaths.forEach { subpath in - print("Subpath: \(subpath)") -} -``` - -#### [Combine] - -```swift -let sink = Amplify.Publisher.create { - try await Amplify.Storage.list( - path: .fromString("public/photos/"), - options: .init( - subpathStrategy: .exclude - ) - ) -}.sink { - if case let .failure(error) = $0 { - print("Failed: \(error)") - } -} -receiveValue: { listResult in - listResult.items.forEach { item in - print("Path: \(item.path)") - } - listResult.excludedSubpaths.forEach { subpath in - print("Subpath: \(subpath)") - } -} -``` - -The response will only include objects within the `public/photos/` path and will also provide a list of the excluded subpaths: - -```bash -Path: public/photos/photo01.jpg -Subpath: public/photos/vacation/ -Subpath: public/photos/thumbnails/ -``` - -The default delimiter character is `"/"`, but this can be changed by supplying a custom delimiter: - -#### [Async/Await] - -```swift -let listResult = try await Amplify.Storage.list( - path: .fromString("public/photos-"), - options: .init( - subpathStrategy: .exclude(delimitedBy: "-") - ) -) -``` - -#### [Combine] - -```swift -let sink = Amplify.Publisher.create { - try await Amplify.Storage.list( - path: .fromString("public/photos-"), - options: .init( - subpathStrategy: .exclude(delimitedBy: "-") - ) - ) -}.sink { - if case let .failure(error) = $0 { - print("Failed: \(error)") - } -} -receiveValue: { listResult in - // ... -} -``` - -### All `list` options - -| Option | Type | Description | -| --- | --- | --- | -| subpathStrategy | SubpathStrategy | The strategy to use when listing contents from subpaths | -| pageSize | UInt | Number between 1 and 1,000 that indicates the limit of how many entries to fetch when retrieving file lists from the server | -| bucket | StorageBucket | The bucket in which the objects are stored | -| nextToken | String | String indicating the page offset at which to resume a listing. | - -If the `pageSize` is set lower than the total file size available, a single `list` call only returns a subset of all the files. To list all the files with multiple calls, the user can use the `nextToken` value from the previous response. - - - -This will list all files located under path `album` that: - -- have `private` access level -- are in the root of `album/` (the result doesn't include files under any sub path) - -```dart -Future listAlbum() async { - try { - String? nextToken; - bool hasNextPage; - do { - final result = await Amplify.Storage.list( - path: const StoragePath.fromString('public/album/'), - options: StorageListOptions( - pageSize: 50, - nextToken: nextToken, - pluginOptions: const S3ListPluginOptions( - excludeSubPaths: true, - ), - ), - ).result; - safePrint('Listed items: ${result.items}'); - nextToken = result.nextToken; - hasNextPage = result.hasNextPage; - } while (hasNextPage); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - -Pagination is enabled by default. The default `pageSize` is `1000` if it is not set in the `StorageListOptions`. - -### List all files without pagination - -You can also list all files under a given path without pagination by using the `pluginOptions` and `S3ListPluginOptions.listAll()` constructor. - -This will list all public files (i.e. those with `guest` access level): - -```dart - -Future listAllUnderPublicPath() async { - try { - final result = await Amplify.Storage.list( - path: const StoragePath.fromString('public/'), - options: const StorageListOptions( - pluginOptions: S3ListPluginOptions.listAll(), - ), - ).result; - safePrint('Listed items: ${result.items}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} - -``` - -### List files from a specified bucket -You can also perform a `list` operation to a specific bucket by providing the `bucket` option. You can pass in a `StorageBucket` object representing the target bucket from the name defined in the Amplify Backend. - -```dart -final result = await Amplify.Storage.list( - path: const StoragePath.fromString('path/to/dir'), - options: StorageListOptions( - // highlight-start - // Specify a target bucket using name assigned in Amplify Backend - bucket: StorageBucket.fromOutputs('secondBucket'), - // highlight-end - ), -).result; -``` -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. - -```dart -final result = await Amplify.Storage.list( - path: const StoragePath.fromString('path/to/dir'), - options: StorageListOptions( - // highlight-start - // Alternatively, provide bucket name from console and associated region - bucket: StorageBucket.fromBucketInfo( - BucketInfo( - bucketName: 'second-bucket-name-from-console', - region: 'us-east-2', - ), - ), - // highlight-end - ), -).result; -``` - - -### More `list` options - -| Option | Type | Description | -| --- | --- | --- | -| bucket | StorageBucket | The target bucket from the assigned name in the Amplify Backend or from the bucket name and region in the console

    Defaults to the default bucket and region from the Amplify configuration if this option is not provided.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | -| excludeSubPaths | boolean | Whether to exclude objects under the sub paths of the path to list. Defaults to false. | -| delimiter | String | The delimiter to use when evaluating sub paths. If excludeSubPaths is false, this value has no impact on behavior. | - - - -## Get File Properties - -You can also view the properties of an individual file. - -```javascript -import { getProperties } from 'aws-amplify/storage'; - -try { - const result = await getProperties({ - path: 'album/2024/1.jpg', - // Alternatively, path: ({ identityId }) => `album/${identityId}/1.jpg` - options: { - // Specify a target bucket using name assigned in Amplify Backend - bucket: 'assignedNameInAmplifyBackend' - } - }); - console.log('File Properties ', result); -} catch (error) { - console.log('Error ', error); -} -``` - -The properties and metadata will look similar to the below example - -```js -{ - path: "album/2024/1.jpg", - contentType: "image/jpeg", - contentLength: 6873, - eTag: "\"56b32cf4779ff6ca3ba3f2d455fa56a7\"", - lastModified: Wed Apr 19 2023 14:20:55 GMT-0700 (Pacific Daylight Time) {}, - metadata: { owner: 'aws' } -} -``` - -### More `getProperties` options - -Option | Type | Default | Description | -| -- | -- | -- | ----------- | -| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | -| useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint. | [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | - - - -To get the metadata in result for all APIs you have to configure user defined metadata in CORS. - -Learn more about how to setup an appropriate [CORS Policy](/[platform]/build-a-backend/storage/extend-s3-resources/#for-manually-configured-s3-resources). - - - - - -## Get File Properties - -You can also view properties of an individual file. - -```dart -Future getFileProperties() async { - try { - final result = await Amplify.Storage.getProperties( - path: const StoragePath.fromString('example.txt'), - ).result; - safePrint('File size: ${result.storageItem.size}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - -As well as specify a bucket to target to view properties of a file - -```dart -Future getFileProperties() async { - try { - final result = await Amplify.Storage.getProperties( - path: const StoragePath.fromString('example.txt'), - options: StorageGetPropertiesOptions( - StorageBucket.fromOutputs('secondBucket'), - ), - // Alternatively, provide bucket name from console and associated region - /* - options: StorageGetPropertiesOptions( - bucket: StorageBucket.fromBucketInfo( - BucketInfo( - bucketName: 'second-bucket-name-from-console', - region: 'us-east-2', - ), - ), - ), - */ - ).result; - safePrint('File size: ${result.storageItem.size}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - - - -To get the metadata in result for all APIs when building against a web target, you have to configure user defined metadata in CORS. - -Learn more about how to setup an appropriate [CORS Policy](/[platform]/build-a-backend/storage/extend-s3-resources/#for-manually-configured-s3-resources). - - - - ---- - ---- -title: "Remove files" -section: "build-a-backend/storage" -platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] -gen: 2 -last-updated: "2026-02-04T14:20:47.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/remove-files/" ---- - -Files can be removed from a storage bucket using the `remove` API. If a file is protected by an identity Id, only the user who owns the file will be able to remove it. - - -## Remove a single file - -You can also perform a remove operation from a specific bucket by providing the target bucket's assigned name from Amplify Backend in `bucket` option. - -```javascript -import { remove } from 'aws-amplify/storage'; - -try { - await remove({ - path: 'album/2024/1.jpg', - // Alternatively, path: ({identityId}) => `album/${identityId}/1.jpg` - bucket: 'assignedNameInAmplifyBackend', // Specify a target bucket using name assigned in Amplify Backend - }); -} catch (error) { - console.log('Error ', error); -} -``` - -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. - -```javascript -import { remove } from 'aws-amplify/storage'; - -try { - await remove({ - path: 'album/2024/1.jpg', - // Alternatively, provide bucket name from console and associated region - bucket: { - bucketName: 'bucket-name-from-console', - region: 'us-east-2' - } - - }); -} catch (error) { - console.log('Error ', error); -} -``` - - - -## Remove folders - -You can remove entire folders and their contents by providing a folder path. The remove API will automatically detect folders and perform batch deletion of all contained files. - -```javascript -import { remove } from 'aws-amplify/storage'; - -try { - await remove({ - path: 'album/2024/' - }); -} catch (error) { - console.error(error); -} -``` - -### Folder deletion with progress tracking - -For large folders, you can track the deletion progress and handle any failures: - -```javascript -import { remove } from 'aws-amplify/storage'; - -try { - const result = await remove({ - path: 'large-folder/', - options: { - onProgress: (fileBatch) => { - console.log(fileBatch); - } - } - }); - - console.log('Success', result); -} catch (error) { - console.log('Error during folder deletion:', error); -} -``` - -### Cancellable folder deletion - -You can cancel folder deletion operations, useful for user-initiated cancellations or when navigating away from a page. - -When you call `remove()` without `await`, it returns a cancellable operation object with a `result` property and `cancel()` method. This differs from `await remove()` which directly returns the result but cannot be cancelled. - -```javascript -import { remove } from 'aws-amplify/storage'; - -let deleteOperation; - -// Start deletion when user clicks delete button -function handleDeleteFolder() { - // remove() returns { result: Promise, cancel: Function } - deleteOperation = remove({ - path: 'user-uploads/large-dataset/', - options: { - onProgress: (fileBatch) => { - updateProgressBar(fileBatch.deleted?.length || 0); - } - } - }); - - // Access the promise via .result property - deleteOperation.result.then(result => { - console.log('Success', result); - }).catch(error => { - if (error.name === 'CanceledError') { - console.log('Deletion cancelled by user'); - } else { - console.log('Error:', error); - } - }); -} - -// Cancel when user clicks cancel or navigates away -function handleCancel() { - if (deleteOperation) { - deleteOperation.cancel(); - } -} -``` - - - - -#### [Java] - -```java -Amplify.Storage.remove( - StoragePath.fromString("public/myUploadedFileName.txt"), - result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Remove failure", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), - { Log.i("MyAmplifyApp", "Successfully removed: ${it.path}") }, - { Log.e("MyAmplifyApp", "Remove failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -try { - val result = Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt")) - Log.i("MyAmplifyApp", "Successfully removed: ${result.path}") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "Remove failure", error) -} -``` - -#### [RxJava] - -```java -RxAmplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt")) - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Remove failure", error) - ); -``` - -## Remove files from a specified bucket - -You can also perform a remove operation to a specific bucket by providing the `bucket` option. You can pass in a string representing the target bucket's assigned name in Amplify Backend. - -#### [Java] - -```java -StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); -StorageRemoveOptions options = StorageRemoveOptions.builder() - .bucket(secondBucket) - .build(); - -Amplify.Storage.remove( - StoragePath.fromString("public/myUploadedFileName.txt"), - options, - result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Remove failure", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val secondBucket = StorageBucket.fromOutputs("secondBucket") -val options = StorageRemoveOptions.builder() - .bucket(secondBucket) - .build() - -Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options, - { Log.i("MyAmplifyApp", "Successfully removed: ${it.path}") }, - { Log.e("MyAmplifyApp", "Remove failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val secondBucket = StorageBucket.fromOutputs("secondBucket") -val options = StorageRemoveOptions.builder() - .bucket(secondBucket) - .build() - -try { - val result = Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options) - Log.i("MyAmplifyApp", "Successfully removed: ${result.path}") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "Remove failure", error) -} -``` - -#### [RxJava] - -```java -StorageBucket secondBucket = StorageBucket.fromOutputs("secondBucket"); -StorageRemoveOptions options = StorageRemoveOptions.builder() - .bucket(secondBucket) - .build(); -RxAmplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options) - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Remove failure", error) - ); -``` - -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. - -#### [Java] - -```java -BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); -StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); -StorageRemoveOptions options = StorageRemoveOptions.builder() - .bucket(secondBucket) - .build(); - -Amplify.Storage.remove( - StoragePath.fromString("public/myUploadedFileName.txt"), - options, - result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Remove failure", error) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") -val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) -val options = StorageRemoveOptions.builder() - .bucket(secondBucket) - .build() - -Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options, - { Log.i("MyAmplifyApp", "Successfully removed: ${it.path}") }, - { Log.e("MyAmplifyApp", "Remove failure", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val bucketInfo = BucketInfo("second-bucket-name-from-console", "us-east-2") -val secondBucket = StorageBucket.fromBucketInfo(bucketInfo) -val options = StorageRemoveOptions.builder() - .bucket(secondBucket) - .build() - -try { - val result = Amplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options) - Log.i("MyAmplifyApp", "Successfully removed: ${result.path}") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "Remove failure", error) -} -``` - -#### [RxJava] - -```java -BucketInfo bucketInfo = new BucketInfo("second-bucket-name-from-console", "us-east-2"); -StorageBucket secondBucket = StorageBucket.fromBucketInfo(bucketInfo); -StorageRemoveOptions options = StorageRemoveOptions.builder() - .bucket(secondBucket) - .build(); - -RxAmplify.Storage.remove(StoragePath.fromString("public/myUploadedFileName.txt"), options) - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully removed: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Remove failure", error) - ); -``` - - - - - -#### [Async/Await] - -```swift -let removedObject = try await Amplify.Storage.remove( - path: .fromString("public/example/path") -) -print("Deleted \(removedObject)") -``` - -#### [Combine] - -```swift -let sink = Amplify.Publisher.create { - try await Amplify.Storage.remove( - path: .fromString("public/example/path") - ) -}.sink { - if case let .failure(error) = $0 { - print("Failed: \(error)") - } -} -receiveValue: { removedObject in - print("Deleted \(removedObject)") -} -``` - -## Remove files from a specified bucket - -You can perform a remove operation from a specific bucket by providing the `bucket` option. - -#### [From Outputs] -You can use `.fromOutputs(name:)` to provide a string representing the target bucket's assigned name in the Amplify Backend. - -```swift -let removedObject = try await Amplify.Storage.remove( - path: .fromString("public/example/path"), - options: .init( - bucket: .fromOutputs(name: "secondBucket") - ) -) -``` - -#### [From Bucket Info] -You can also use `.fromBucketInfo(_:)` to provide a bucket name and region directly. - -```swift -let removedObject = try await Amplify.Storage.remove( - path: .fromString("public/example/path"), - options: .init( - bucket: .fromBucketInfo(.init( - bucketName: "another-bucket-name", - region: "another-bucket-region") - ) - ) -) -``` - - - - -You can also perform a `remove` operation to a specific bucket by providing the `bucket` option. You can pass in a `StorageBucket` object representing the target bucket from the name defined in the Amplify Backend. - -```dart -final result = await Amplify.Storage.remove( - path: const StoragePath.fromString('path/to/file.txt'), - options: StorageRemoveOptions( - // highlight-start - // Specify a target bucket using name assigned in Amplify Backend - bucket: StorageBucket.fromOutputs('secondBucket'), - // highlight-end - ), -).result; -``` - -Alternatively, you can also pass in an object by specifying the bucket name and region from the console. - -```dart -final result = await Amplify.Storage.remove( - path: const StoragePath.fromString('path/to/file.txt'), - options: StorageRemoveOption( - // highlight-start - // Alternatively, provide bucket name from console and associated region - bucket: StorageBucket.fromBucketInfo( - BucketInfo( - bucketName: 'second-bucket-name-from-console', - region: 'us-east-2', - ), - ), - // highlight-end - ), -).result; -``` - -## Remove multiple Files - -You can remove multiple files using `Amplify.Storage.removeMany`, as well as specify a bucket to target, the files to be removed in a batch should have the same access level: - -```dart -Future remove() async { - try { - final result = await Amplify.Storage.removeMany( - paths: [ - const StoragePath.fromString('public/file-1.txt'), - const StoragePath.fromString('public/file-2.txt'), - ], - // if this option is not provided, the default bucket in the Amplify Backend will be used - options: StorageRemoveManyOptions( - bucket: StorageBucket.fromOutputs('secondBucket'), - /* Alternatively, provide bucket name from console and associated region - bucket: StorageBucket.fromBucketInfo( - BucketInfo( - bucketName: 'second-bucket-name-from-console', - region: 'us-east-2', - ), - ), - */ - ), - ).result; - safePrint('Removed files: ${result.removedItems}'); - } on StorageException catch (e) { - safePrint(e.message); - } -} -``` - - - -## More `remove` options - -Option | Type | Default | Description | -| -- | :--: | :--: | ----------- | -| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | -| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | -| onProgress | (fileBatch: \{
    deleted?: \{path: string\}[];
    failed?: \{path: string; code: string; message: string\}[];
    \}) => void | Optional | Callback function for tracking folder deletion progress. Called after each batch of files is processed during folder deletion operations. | - - ---- - ---- -title: "Copy files" -section: "build-a-backend/storage" -platforms: ["angular", "javascript", "nextjs", "react", "vue", "flutter", "react-native"] -gen: 2 -last-updated: "2025-07-04T12:58:35.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/copy-files/" ---- - - - -**Note:** You can only copy files up to 5GB in a single operation - - - -You can copy an existing file to a different path within the storage bucket using the copy API. - - -The `copy` method duplicates an existing file to a designated path and returns an object `{path: 'destPath'}` upon successful completion. - -```javascript -import { copy } from 'aws-amplify/storage'; - -const copyFile = async () => { - try { - const response = await copy({ - source: { - path: `album/2024/${encodeURIComponent('#1.jpg')}`, - // Alternatively, path: ({identityId}) => `album/${identityId}/${encodeURIComponent('#1.jpg')` - }, - destination: { - path: 'shared/2024/#1.jpg', - // Alternatively, path: ({identityId}) => `shared/${identityId}/#1.jpg` - }, - }); - } catch (error) { - console.error('Error', err); - } -}; -``` - - -The operation can fail if there's a special character in the `source` path. You should URI encode the source -path with special character. You **don't** need to encode the `destination` path. - - - - - -Cross identity ID copying is only allowed if the destination path has the the right access rules to allow other authenticated users writing to it. - - - -## Specify a bucket or copy across buckets / regions - -You can also perform an `copy` operation to a specific bucket by providing the `bucket` option. This option can either be a string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console. - -```javascript -import { copy } from 'aws-amplify/storage'; - -const copyFile = async () => { - try { - const response = await copy({ - source: { - path: 'album/2024/1.jpg', - // Specify a target bucket using name assigned in Amplify Backend - // or bucket name from console and associated region - bucket: 'assignedNameInAmplifyBackend', - expectedBucketOwner: '123456789012' - }, - destination: { - path: 'shared/2024/1.jpg', - // Specify a target bucket using name assigned in Amplify Backend - // or bucket name from console and associated region - bucket: { - bucketName: 'generated-second-bucket-name', - region: 'us-east-2' - }, - expectedBucketOwner: '123456789013' - } - }); - } catch (error) { - console.error('Error', error); - } -}; -``` - - -In order to copy to or from a bucket other than your default, both source and destination must have `bucket` explicitly defined. - - -## Copy `source` and `destination` options - -Option | Type | Default | Description | -| -- | :--: | :--: | ----------- | -| path | string \|
    (\{ identityId \}) => string | Required | A string or callback that represents the path in source and destination bucket to copy the object to or from.
    **Each segment of the path in `source` must by URI encoded.** | -| bucket | string \|
    \{ bucketName: string;
    region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

    Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets). | -| eTag | string | Optional | The copy **source object** entity tag (ETag) value. Only Copies the object if its ETag matches the specified tag. | -| notModifiedSince | Date | Optional | Copies the **source object** if it hasn't been modified since the specified time.

    **This is evaluated only when `eTag` is NOT supplied**| -| expectedBucketOwner | string | Optional | `source.expectedBucketOwner`: The account ID that owns the source bucket.

    `destination.expectedBucketOwner`: The account ID that owns the destination bucket. | - - - -User who initiates a copy operation should have read permission on the copy source file. - -```dart -Future copy() async { - try { - final result = await Amplify.Storage.copy( - source: const StoragePath.fromString('album/2024/1.jpg'), - destination: const StoragePath.fromString('shared/2024/1.jpg'), - ).result; - safePrint('Copied file: ${result.copiedItem.path}'); - } on StorageException catch (e) { - safePrint(e); - } -} -``` -## Specify a bucket or copy across buckets / regions - -You can also perform a `copy` operation to a specific bucket by providing the `CopyBuckets` option. -This option is an object that takes two `StorageBucket` parameters, which can be constructed by the specified name in the Amplify Backend, or the bucket name and region from the console. - -```dart -final mainBucket = StorageBucket.fromOutputs( - 'mainBucket', -); -final bucket2 = StorageBucket.fromBucketInfo( - BucketInfo( - bucketName: 'second-bucket-name-from-console', - region: 'us-east-2', - ), -), -try { - final result = await Amplify.Storage.copy( - source: const StoragePath.fromString('album/2024/1.jpg'), - destination: const StoragePath.fromString('shared/2024/1.jpg'), - options: StorageCopyOptions( - buckets: CopyBuckets( - source: bucket1, - destination: bucket2, - ), - ), - ).result; - safePrint('Copied file: ${result.copiedItem.path}'); -} on StorageException catch (e) { - print('Error: $e'); -} -``` - - -In order to copy to or from a bucket other than your default, the source and/or destination paths must exist in that bucket - - -## `copy` options - -Option | Type | Description | -| -- | -- | ----------- | -| getProperties | boolean | Whether to retrieve properties for the copied object using theAmplify.Storage.getProperties() after the operation completes. When set to true the returned item will contain additional info such as metadata and content type. | -| buckets | CopyBuckets | An object that accepts two `StorageBucket` parameters. To copy to and from the same bucket, use the `CopyBuckets.sameBucket(targetBucket)` method, where `targetBucket` is a `StorageBucket`. Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets)| - -Example of `copy` with options: - -```dart -final result = Amplify.Storage.copy( - source: const StoragePath.fromString('album/2024/1.jpg'), - destination: const StoragePath.fromString('shared/2024/1.jpg'), - options: const StorageCopyOptions( - pluginOptions: S3CopyPluginOptions( - getProperties: true, - ), - buckets: CopyBuckets.sameBucket( - StorageBucket.fromOutputs('secondBucket'), - ), - ), -); -``` - - ---- - ---- -title: "Listen to storage events" -section: "build-a-backend/storage" -platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] -gen: 2 -last-updated: "2024-10-07T19:55:17.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/lambda-triggers/" ---- - -Function triggers can be configured to enable event-based workflows when files are uploaded or deleted. To add a function trigger, modify the `defineStorage` configuration. - -First, in your storage definition, add the following: - -```ts title="amplify/storage/resource.ts" -export const storage = defineStorage({ - name: 'myProjectFiles', - // highlight-start - triggers: { - onUpload: defineFunction({ - entry: './on-upload-handler.ts' - }), - onDelete: defineFunction({ - entry: './on-delete-handler.ts' - }) - } - // highlight-end -}); -``` - -Then create the function definitions at `amplify/storage/on-upload-handler.ts` and `amplify/storage/on-delete-handler.ts`. - -```ts title="amplify/storage/on-upload-handler.ts" -import type { S3Handler } from 'aws-lambda'; - -export const handler: S3Handler = async (event) => { - const objectKeys = event.Records.map((record) => record.s3.object.key); - console.log(`Upload handler invoked for objects [${objectKeys.join(', ')}]`); -}; -``` - -```ts title="amplify/storage/on-delete-handler.ts" -import type { S3Handler } from 'aws-lambda'; - -export const handler: S3Handler = async (event) => { - const objectKeys = event.Records.map((record) => record.s3.object.key); - console.log(`Delete handler invoked for objects [${objectKeys.join(', ')}]`); -}; -``` - -> **Info:** **Note:** The `S3Handler` type comes from the [`@types/aws-lambda`](https://www.npmjs.com/package/@types/aws-lambda) npm package. This package contains types for different kinds of Lambda handlers, events, and responses. - -Now, when you deploy your backend, these functions will be invoked whenever an object is uploaded or deleted from the bucket. - -## More Advanced Triggers - -The example listed above demonstrates what is exposed directly in your `storage` definition. Specifically, the use of the `triggers` option when you use `defineStorage`. This method is for simple triggers that always execute on file uploads or file deletions. There are no additional modifications you can make to the triggers defined in this way. - -If you want the ability to do something more than simply handle the events `onUpload` and `onDelete` you will have to use `.addEventNotification` in your `backend.ts`. If you use this method, the `triggers` section in your `storage/resource.ts` file should be removed. - -Here is an example of how you can add a Lambda trigger for an S3 object PUT event. This trigger will execute when a file that has been uploaded to the bucket defined in your `storage/resource.ts` has a matching prefix and suffix as that listed in the function input of `addEventNotification`. - -```ts title="amplify/backend.ts" -import { EventType } from 'aws-cdk-lib/aws-s3'; -import { LambdaDestination } from 'aws-cdk-lib/aws-s3-notifications'; -import { defineBackend } from '@aws-amplify/backend'; -import { storage } from './storage/resource'; -import { yourLambda } from './functions/yourLambda/resource'; - -const backend = defineBackend({ - storage, - yourLambda, -}); - -backend.storage.resources.bucket.addEventNotification( - EventType.OBJECT_CREATED_PUT, - new LambdaDestination(backend.yourLambda.resources.lambda), - { - prefix: 'protected/uploads/', - suffix: '-uploadManifest.json', - } -); -``` - -It's important to note that using this methodology does not require any changes your lambda function. This modification on your `backend.ts` file will create a new `AWS CloudFormation handler for "Custom::S3BucketNotifications" resources (@aws-cdk/aws-s3)` that specifically handles checking the prefix and suffix. - ---- - ---- -title: "Extend S3 resources" -section: "build-a-backend/storage" -platforms: ["angular", "javascript", "nextjs", "react", "vue", "swift", "android", "flutter", "react-native"] -gen: 2 -last-updated: "2024-05-21T17:56:46.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/extend-s3-resources/" ---- - -## For Amplify-generated S3 resources - -Amplify Storage generates Amazon S3 resources to offer storage features. You can access the underlying Amazon S3 resources to further customize your backend configuration by using the AWS Cloud Developer Kit (AWS CDK). - -### Example - Enable Transfer Acceleration - -The following is an example of how you would enable Transfer Acceleration on the bucket ([CDK documentation](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.CfnBucket.AccelerateConfigurationProperty.html)). In order to enable Transfer Acceleration on the bucket, you will have to unwrap the L1 CDK construct from the L2 CDK construct like the following. - -```tsx -// highlight-next-line -import * as s3 from 'aws-cdk-lib/aws-s3'; -import { defineBackend } from '@aws-amplify/backend'; -import { storage } from './storage/resource'; - -const backend = defineBackend({ - storage -}); - -// highlight-start -const s3Bucket = backend.storage.resources.bucket; - -const cfnBucket = s3Bucket.node.defaultChild as s3.CfnBucket; - -cfnBucket.accelerateConfiguration = { - accelerationStatus: "Enabled" // 'Suspended' if you want to disable transfer acceleration -} -// highlight-end -``` - -### Upload files using the accelerated S3 endpoint - -We switch to the accelerated S3 endpoint by using the `useAccelerateEndpoint` parameter set to `true` in the `AWSS3StorageUploadFileOptions`. - -#### [Java] - -```java -AWSS3StorageUploadFileOptions awsS3StorageUploadFileOptions = - AWSS3StorageUploadFileOptions.builder().setUseAccelerateEndpoint(true).build(); - Amplify.Storage.uploadFile( - StoragePath.fromString("public/example"), - file - awsS3StorageUploadFileOptions, - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - storageFailure -> Log.e("MyAmplifyApp", "Upload failed", storageFailure) -); -``` - -#### [Kotlin - Callbacks] - -```kotlin -val awsS3StorageUploadFileOptions = AWSS3StorageUploadFileOptions.builder(). - setUseAccelerateEndpoint(true). - build() - Amplify.Storage.uploadFile( - StoragePath.fromString("public/example"), - file - awsS3StorageUploadFileOptions, - { Log.i("MyAmplifyApp", "Successfully uploaded: " + it.getPath()) }, - { Log.e("MyAmplifyApp", "Upload failed", it) } -) -``` - -#### [Kotlin - Coroutines] - -```kotlin -val awsS3StorageUploadFileOptions = AWSS3StorageUploadFileOptions.builder(). - setUseAccelerateEndpoint(true). - build() -val upload = Amplify.Storage.uploadFile(StoragePath.fromString("public/example"), file, awsS3StorageUploadFileOptions) -try { - val result = upload.result() - Log.i("MyAmplifyApp", "Successfully uploaded: ${result.path}") -} catch (error: StorageException) { - Log.e("MyAmplifyApp", "Upload failed", error) -} - -``` - -#### [RxJava] - -```java -AWSS3StorageUploadFileOptions awsS3StorageUploadFileOptions = - AWSS3StorageUploadFileOptions.builder().setUseAccelerateEndpoint(true).build(); -RxProgressAwareSingleOperation rxUploadOperation = - RxAmplify.Storage.uploadFile(StoragePath.fromString("public/example"), file, awsS3StorageUploadFileOptions); -rxUploadOperation - .observeResult() - .subscribe( - result -> Log.i("MyAmplifyApp", "Successfully uploaded: " + result.getPath()), - error -> Log.e("MyAmplifyApp", "Upload failed", error) - ); - -``` - - - - -### Upload files using the accelerated S3 endpoint - -You can use transfer acceleration by setting `"useAccelerateEndpoint"` to `true` in the corresponding `pluginOptions` for any of the following Storage APIs: -- `getUrl(key:options:)` -- `downloadData(key:options:)` -- `downloadFile(key:local:options:)` -- `uploadData(key:data:options:)` -- `uploadFile(key:local:options:)` - -For example, to upload a file using transfer acceleration: - -```swift -let uploadTask = Amplify.Storage.uploadFile( - key: aKey, - local: aLocalFile, - options: .init( - pluginOptions: [ - "useAccelerateEndpoint": true - ] - ) -) - -let data = try await uploadTask.value -``` - - - -### Upload files using the accelerated S3 endpoint - -You can use transfer acceleration when calling the following APIs: - -* `getUrl` -* `downloadData` -* `downloadFile` -* `uploadData` -* `uploadFile` - -Set `useAccelerateEndpoint` to `true` in the corresponding Storage S3 plugin options to apply an accelerated S3 endpoint to the operation. For example, upload a file using transfer acceleration: - -```dart -import 'package:amplify_storage_s3/amplify_storage_s3.dart'; - -Future uploadFileUsingAcceleration(String filePath, String key) async { - final localFile = AWSFile.fromPath(filePath); - try { - final uploadFileOperation = Amplify.Storage.uploadFile( - localFile: localFile, - key: key, - options: const StorageUploadFileOptions( - pluginOptions: S3UploadFilePluginOptions( - useAccelerateEndpoint: true, - ), - ), - ); - - final result = await uploadFileOperation.result; - safePrint('Uploaded file: ${result.uploadedItem.key}'); - } on StorageException catch (error) { - safePrint('Something went wrong uploading file: ${error.message}'); - } -} -``` - - -Read more about [escape hatches in the CDK](https://docs.aws.amazon.com/cdk/v2/guide/cfn_layer.html#develop-customize-escape). - - -## For Manually configured S3 resources - - -Follow this guide if you are building against a web target. - - -> **Warning:** To make calls to your S3 bucket from your App, you need to set up a CORS Policy for your S3 bucket. This callout is only for manual configuration of your S3 bucket. - -The following steps will set up your CORS Policy: - -1. Go to [Amazon S3 console](https://s3.console.aws.amazon.com/s3/home?region=us-east-1) and click on your project's `userfiles` bucket, which is normally named as [Bucket Name][Id]-dev. ![Go to [Amazon S3 Console]](/images/storage/CORS1.png) -2. Click on the **Permissions** tab for your bucket. ![Click on the **Permissions** tab for your bucket](/images/storage/CORS2.png) -3. Click the edit button in the **Cross-origin resource sharing (CORS)** section. ![Click the edit button in the **Cross-origin resource sharing (CORS)** section](/images/storage/CORS3.png) -4. Make the Changes and click on Save Changes. You can add required metadata to be exposed in `ExposeHeaders` with `x-amz-meta-XXXX` format. ![Click on Save Changes:](/images/storage/CORS4.png) - -```json -[ - { - "AllowedHeaders": ["*"], - "AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"], - "AllowedOrigins": ["*"], - "ExposeHeaders": [ - "x-amz-server-side-encryption", - "x-amz-request-id", - "x-amz-id-2", - "ETag", - "x-amz-meta-foo" - ], - "MaxAgeSeconds": 3000 - } -] -``` - - - -**Note:** You can restrict the access to your bucket by updating AllowedOrigin to include individual domains. - - - - ---- - ---- -title: "Use AWS SDK for S3 APIs" -section: "build-a-backend/storage" -platforms: ["android", "swift"] -gen: 2 -last-updated: "2024-09-24T23:57:23.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/use-aws-sdk/" ---- - -For advanced use cases where Amplify does not provide the functionality, you can retrieve the escape hatch to access the `S3Client` instance: - - - -#### [Java] - - - -Learn more about consuming Kotlin clients from Java using either a blocking interface or an equivalent async interface based on futures [here](https://github.com/awslabs/smithy-kotlin/blob/main/docs/design/kotlin-smithy-sdk.md#java-interop). - - - -```java -AWSS3StoragePlugin plugin = (AWSS3StoragePlugin) Amplify.Storage.getPlugin("awsS3StoragePlugin"); -S3Client client = plugin.getEscapeHatch(); -``` - -#### [Kotlin] - -```kotlin -val plugin = Amplify.Storage.getPlugin("awsS3StoragePlugin") as AWSS3StoragePlugin -val client = plugin.escapeHatch -``` - - - - -Add the following import: - -```swift -import AWSS3StoragePlugin -``` - -Then retrieve the escape hatch with this code: - -```swift -do { - // Retrieve the reference to AWSS3StoragePlugin - let plugin = try Amplify.Storage.getPlugin(for: "awsS3StoragePlugin") - guard let storagePlugin = plugin as? AWSS3StoragePlugin else { - return - } - - // Retrieve the reference to S3Client - let s3Client = storagePlugin.getEscapeHatch() - - // Make requests using s3Client... - // ... -} catch { - print("Get escape hatch failed with error - \(error)") -} -``` - -For additional client documentation, see the [AWS SDK for Swift Client documentation](https://docs.aws.amazon.com/sdk-for-swift/latest/developer-guide/using-client-services.html). For S3Client code examples, see the [Amazon S3 examples using SDK for Swift](https://docs.aws.amazon.com/sdk-for-swift/latest/developer-guide/swift_s3_code_examples.html) - - ---- - ---- -title: "Use Amplify Storage with any S3 bucket" -section: "build-a-backend/storage" -platforms: ["javascript", "react", "react-native", "angular", "vue", "nextjs", "swift", "android", "flutter"] -gen: 2 -last-updated: "2025-04-21T11:34:09.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/use-with-custom-s3/" ---- - -With Amplify Storage APIs, you can use your own S3 buckets instead of the Amplify-created ones. - - -**Important:** To utilize the storage APIs with an S3 bucket outside of Amplify, you must have Amplify Auth configured in your project. - -## Use storage resources with an Amplify backend - -### Add necessary permissions to the S3 bucket - -For the specific Amazon S3 bucket that you want to use with these APIs, you need to make sure that the associated IAM role has the necessary permissions to read and write data to that bucket. - -To do this, go to **Amazon S3 console** > **Select the S3 bucket** > **Permissions** > **Edit** Bucket Policy. - -![Showing Amplify console showing Storage tab selected](/images/gen2/storage/s3-console-permissions.png) - -The policy will look something like this: - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Statement1", - "Principal": { - "AWS": "arn:aws:iam:::role/" - }, - "Effect": "Allow", - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject", - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::", - "arn:aws:s3:::/*" - ] - } - ] -} -``` -Replace `` with your AWS account ID and `` with the IAM role associated with your Amplify Auth setup. Replace `` with the S3 bucket name. - -You can refer to [Amazon S3's Policies and Permissions documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-policy-language-overview.html) for more ways to customize access to the bucket. - -> **Warning:** In order to make calls to your manually configured S3 bucket from your application, you must also set up a [CORS Policy](/[platform]/build-a-backend/storage/extend-s3-resources/#for-manually-configured-s3-resources) for the bucket. - -### Specify the S3 bucket in Amplify's backend config - -Next, use the `addOutput` method from the backend definition object to define a custom S3 bucket by specifying the name and region of the bucket in your `amplify/backend.ts` file. You must also set up the appropriate resources and IAM policies to be attached to the backend. - - -**Important:** You can use a storage backend configured through Amplify and a custom S3 bucket at the same time using this method. However, the Amplify-configured storage will be used as the **default bucket** and the custom S3 bucket will only be used as an additional bucket. - - -#### Configure the S3 bucket - -Below are several examples of configuring the backend to define a custom S3 bucket: - -#### [Guest Users] -Below is an example of expanding the original backend object to grant all guest (i.e. not signed in) users read access to files under `public/`: - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend"; -import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam"; -import { Bucket } from "aws-cdk-lib/aws-s3"; -import { auth } from "./auth/resource"; - -const backend = defineBackend({ - auth, -}); -// highlight-start -const customBucketStack = backend.createStack("custom-bucket-stack"); - -// Import existing bucket -const customBucket = Bucket.fromBucketAttributes(customBucketStack, "MyCustomBucket", { - bucketArn: "arn:aws:s3:::", - region: "" -}); - -backend.addOutput({ - storage: { - aws_region: customBucket.env.region, - bucket_name: customBucket.bucketName, - // optional: `buckets` can be used when setting up more than one existing bucket - buckets: [ - { - aws_region: customBucket.env.region, - bucket_name: customBucket.bucketName, - name: customBucket.bucketName, - /* - optional: `paths` can be used to set up access to specific - bucket prefixes and configure user access types to them - */ - paths: { - "public/*": { - // "write" and "delete" can also be added depending on your use case - guest: ["get", "list"], - }, - }, - } - ] - }, -}); - -/* - Define an inline policy to attach to Amplify's unauth role - This policy defines how unauthenticated/guest users can access your existing bucket -*/ -const unauthPolicy = new Policy(backend.stack, "customBucketUnauthPolicy", { - statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["s3:GetObject"], - resources: [`${customBucket.bucketArn}/public/*`], - }), - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["s3:ListBucket"], - resources: [ - `${customBucket.bucketArn}`, - `${customBucket.bucketArn}/*` - ], - conditions: { - StringLike: { - "s3:prefix": ["public/", "public/*"], - }, - }, - }), - ], -}); - -// Add the policies to the unauthenticated user role -backend.auth.resources.unauthenticatedUserIamRole.attachInlinePolicy( - unauthPolicy, -); -// highlight-end -``` - -#### [Authenticated Users] -Below is an example of expanding the original backend object to grant all authenticated (i.e. signed in) users with full access to files under `public/`: -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend"; -import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam"; -import { Bucket } from "aws-cdk-lib/aws-s3"; -import { auth } from "./auth/resource"; - -const backend = defineBackend({ - auth, -}); - -const customBucketStack = backend.createStack("custom-bucket-stack"); - -// Import existing bucket -const customBucket = Bucket.fromBucketAttributes(customBucketStack, "MyCustomBucket", { - bucketArn: "arn:aws:s3:::", - region: "" -}); - -backend.addOutput({ - storage: { - aws_region: customBucket.env.region, - bucket_name: customBucket.bucketName, - buckets: [ - { - aws_region: customBucket.env.region, - bucket_name: customBucket.bucketName, - name: customBucket.bucketName, - paths: { - "public/*": { - guest: ["get", "list"], - // highlight-start - authenticated: ["get", "list", "write", "delete"], - // highlight-end - }, - }, - } - ] - }, -}); - -// ... Unauthenticated/guest user policies and role attachments go here ... -// highlight-start -/* - Define an inline policy to attach to Amplify's auth role - This policy defines how authenticated users can access your existing bucket -*/ -const authPolicy = new Policy(backend.stack, "customBucketAuthPolicy", { - statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, - actions: [ - "s3:GetObject", - "s3:PutObject", - "s3:DeleteObject" - ], - resources: [`${customBucket.bucketArn}/public/*`,], - }), - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["s3:ListBucket"], - resources: [ - `${customBucket.bucketArn}`, - `${customBucket.bucketArn}/*` - ], - conditions: { - StringLike: { - "s3:prefix": ["public/*", "public/"], - }, - }, - }), - ], -}); - -// Add the policies to the authenticated user role -backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy(authPolicy); -// highlight-end -``` - -#### [User Groups] -Below is an example of expanding the original backend object with user group permissions. Here, any authenticated users can read from `admin/` and `public/` and authenticated users belonging to the "admin" user group can only manage `admin/`: - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend"; -import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam"; -import { Bucket } from "aws-cdk-lib/aws-s3"; -import { auth } from "./auth/resource"; - -const backend = defineBackend({ - auth, -}); - -const customBucketStack = backend.createStack("custom-bucket-stack"); - -// Import existing bucket -const customBucket = Bucket.fromBucketAttributes(customBucketStack, "MyCustomBucket", { - bucketArn: "arn:aws:s3:::", - region: "" -}); - -backend.addOutput({ - storage: { - aws_region: customBucket.env.region, - bucket_name: customBucket.bucketName, - buckets: [ - { - aws_region: customBucket.env.region, - bucket_name: customBucket.bucketName, - name: customBucket.bucketName, - /* - @ts-expect-error: Amplify backend type issue - https://github.com/aws-amplify/amplify-backend/issues/2569 - */ - paths: { - "public/*": { - authenticated: ["get", "list", "write", "delete"], - }, - // highlight-start - "admin/*": { - authenticated: ["get", "list"], - groupsadmin: ["get", "list", "write", "delete"], - }, - // highlight-end - }, - } - ] - }, -}); - -// ... Authenticated user policy and role attachment goes here ... -// highlight-start -/* - Define an inline policy to attach to "admin" user group role - This policy defines how authenticated users with - "admin" user group role can access your existing bucket -*/ -const adminPolicy = new Policy(backend.stack, "customBucketAdminPolicy", { - statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, - actions: [ - "s3:GetObject", - "s3:PutObject", - "s3:DeleteObject" - ], - resources: [ `${customBucket.bucketArn}/admin/*`], - }), - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["s3:ListBucket"], - resources: [ - `${customBucket.bucketArn}` - `${customBucket.bucketArn}/*` - ], - conditions: { - StringLike: { - "s3:prefix": ["admin/*", "admin/"], - }, - }, - }), - ], -}); - -// Add the policies to the "admin" user group role -backend.auth.resources.groups["admin"].role.attachInlinePolicy(adminPolicy); -// highlight-end -``` - -#### [Owners] -Amplify allows scoping file access to individual users via the user's identity ID. To specify the user's identity ID, you can use the token `${cognito-identity.amazonaws.com:sub}`. - -Below is an example of expanding the original backend object to define read access for guests to the `public/` folder, as well as defining a `protected/` folder where anyone can view uploaded files, but only the file owner can modify/delete them: - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend"; -import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam"; -import { Bucket } from "aws-cdk-lib/aws-s3"; -import { auth } from "./auth/resource"; - -const backend = defineBackend({ - auth, -}); - -const customBucketStack = backend.createStack("custom-bucket-stack"); - -// Import existing bucket -const customBucket = Bucket.fromBucketAttributes(customBucketStack, "MyCustomBucket", { - bucketArn: "arn:aws:s3:::", - region: "" -}); - -backend.addOutput({ - storage: { - aws_region: customBucket.env.region, - bucket_name: customBucket.bucketName, - buckets: [ - { - aws_region: customBucket.env.region, - bucket_name: customBucket.bucketName, - name: customBucket.bucketName, - /* - @ts-expect-error: Amplify backend type issue - https://github.com/aws-amplify/amplify-backend/issues/2569 - */ - paths: { - "public/*": { - guest: ["get", "list"], - authenticated: ["get", "list", "write", "delete"], - }, - // highlight-start - // allow all users to view all folders/files within `protected/` - "protected/*": { - guest: ["get", "list"], - authenticated: ["get", "list"], - }, - // allow owners to read, write and delete their own files in assigned subfolder - "protected/${cognito-identity.amazonaws.com:sub}/*": { - entityidentity: ["get", "list", "write", "delete"] - } - // highlight-end - }, - } - ] - }, -}); -// highlight-start -/* - Define an inline policy to attach to Amplify's unauth role - This policy defines how unauthenticated users/guests - can access your existing bucket -*/ -const unauthPolicy = new Policy(backend.stack, "customBucketUnauthPolicy", { - statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["s3:GetObject"], - resources: [ - `${customBucket.bucketArn}/public/*` - `${customBucket.bucketArn}/protected/*` - ], - }), - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["s3:ListBucket"], - resources: [ - `${customBucket.bucketArn}` - `${customBucket.bucketArn}/*` - ], - conditions: { - StringLike: { - "s3:prefix": [ - "public/", - "public/*", - "protected/", - "protected/*" - ], - }, - }, - }), - ], -}); - -/* - Define an inline policy to attach to Amplify's auth role - This policy defines how authenticated users can access your - existing bucket and customizes owner access to their individual folder -*/ -const authPolicy = new Policy(backend.stack, "customBucketAuthPolicy", { - statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["s3:GetObject"], - resources: [ - `${customBucket.bucketArn}/public/*` - `${customBucket.bucketArn}/protected/*` - ], - }), - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["s3:ListBucket"], - resources: [ - `${customBucket.bucketArn}` - `${customBucket.bucketArn}/*` - ], - conditions: { - StringLike: { - "s3:prefix": [ - "public/", - "public/*", - "protected/", - "protected/*" - ], - }, - }, - }), - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["s3:PutObject"], - resources: [ - `${customBucket.bucketArn}/public/*` - `${customBucket.bucketArn}/protected/${cognito-identity.amazonaws.com:sub}/*` - ], - }), - new PolicyStatement({ - effect: Effect.ALLOW, - actions: ["s3:DeleteObject"], - resources: [ - `${customBucket.bucketArn}/protected/${cognito-identity.amazonaws.com:sub}/*` - ], - }), - ], -}); - -// Add the policies to the unauthenticated user role -backend.auth.resources.unauthenticatedUserIamRole.attachInlinePolicy( - unauthPolicy, -); - -// Add the policies to the authenticated user role -backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy(authPolicy); -// highlight-end -``` - - -The custom authorization rules defined in the examples can be combined, and follow the same rules as Amplify-defined storage. Please refer to our documentation on [customizing authorization rules](/[platform]/build-a-backend/storage/authorization/) for more information. - - - -### Import latest `amplify_outputs.json` file - -To ensure the local `amplify_outputs.json` file is up-to-date, you can run [the `npx ampx generate outputs` command](/[platform]/reference/cli-commands/#npx-ampx-generate-outputs) or download the latest `amplify_outputs.json` from the Amplify console as shown below. - -![](/images/gen2/getting-started/react/amplify-outputs-download.png) - - - -### Import latest `amplify_outputs.dart` file - -To ensure the local `amplify_outputs.dart` file is up-to-date, you can run [the `npx ampx generate outputs` command](/[platform]/reference/cli-commands/#npx-ampx-generate-outputs). - - -Now that you've configured the necessary permissions, you can start using the storage APIs with your chosen S3 bucket. - -## Use storage resources without an Amplify backend - -While using the Amplify backend is the easiest way to get started, existing storage resources can also be integrated with Amplify Storage. - -In addition to manually configuring your storage options, you will also need to ensure Amplify Auth is properly configured in your project and associated IAM roles have the necessary permissions to interact with your existing bucket. Read more about [using existing auth resources without an Amplify backend](/[platform]/build-a-backend/auth/use-existing-cognito-resources/#use-auth-resources-without-an-amplify-backend). - -### Using `Amplify.configure` -Existing storage resource setup can be accomplished by passing the resource metadata to `Amplify.configure`. This will configure the Amplify Storage client library to interact with the additional resources. It's recommended to add the Amplify configuration step as early as possible in the application lifecycle, ideally at the root entry point. - -```ts -import { Amplify } from "aws-amplify"; - -Amplify.configure({ - Auth: { - // add your auth configuration - }, - Storage: { - S3: { - bucket: "", - region: "", - // default bucket metadata should be duplicated below with any additional buckets - buckets: { - "": { - bucketName: "", - region: "", - paths: { - "public/*": { - guest: ["get", "list"], - authenticated: ["get", "list", "write", "delete"], - groupsadmin: ["get", "list", "write", "delete"] - }, - "protected/*": { - guest: ["get", "list"], - authenticated: ["get", "list"], - groupsadmin: ["get", "list", "write", "delete"] - } - "protected/${cognito-identity.amazonaws.com:sub}/*": { - entityidentity: ["get", "list", "write", "delete"] - }, - "admin/*": { - authenticated: ["get", "list"], - groupsadmin: ["get", "list", "write", "delete"], - }, - } - }, - "": { - bucketName: "", - region: "", - paths: { - // ... - } - } - } - } - } -}); -``` - -### Using `amplify_outputs.json` - -Alternatively, existing storage resources can be used by creating or modifying the `amplify_outputs.json` file directly. - -```ts title="amplify_outputs.json" -{ - "auth": { - // add your auth configuration - }, - "storage": { - "aws_region": "", - "bucket_name": "", - // default bucket metadata should be duplicated below with any additional buckets - "buckets": [ - { - "name": "", - "bucket_name": "", - "aws_region": "", - "paths": { - "public/*": { - "guest": [ - "get", - "list" - ], - "authenticated": [ - "get", - "list", - "write", - "delete" - ], - "groupsadmin": [ - "get", - "list", - "write", - "delete" - ] - }, - "protected/*": { - "guest": [ - "get", - "list" - ], - "authenticated": [ - "get", - "list" - ], - "groupsadmin": [ - "get", - "list", - "write", - "delete" - ] - }, - "protected/${cognito-identity.amazonaws.com:sub}/*": { - "entityidentity": [ - "get", - "list", - "write", - "delete" - ] - }, - "admin/*": { - "authenticated": [ - "get", - "list" - ], - "groupsadmin": [ - "get", - "list", - "write", - "delete" - ] - } - } - }, - { - "name": "", - "bucket_name": "", - "aws_region": "", - "paths": { - // add more paths for the bucket - } - } - ] - } -} -``` - - - ---- - ---- -title: "Data usage policy" -section: "build-a-backend/storage" -platforms: ["swift", "flutter"] -gen: 2 -last-updated: "2024-10-25T16:39:44.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/data-usage/" ---- - -Apple requires app developers to provide the data usage policy of the app when they submit their app to the App Store. See Apple's [User privacy and data use](https://developer.apple.com/app-store/user-privacy-and-data-use/) for more details. Amplify Library is used to interact with AWS resources under the developer’s ownership and management. The library cannot predict the usage of its APIs and it is up to the developer to provide the privacy manifest that accurately reflects the data collected by the app. Below are the different categories identified by Apple and the corresponding data type used by the Amplify Library. - -By utilizing the library, Amplify gathers API usage metrics from the AWS services accessed. This process involves adding a user agent to the request made to your AWS service. The user-agent header is included with information about the Amplify Library version, operating system name, and version. AWS collects this data to generate metrics related to our library usage. This information is not linked to the user’s identity and not used for tracking purposes as described in Apple's privacy and data use guidelines. - -Should you have any specific concerns or require additional information for the enhancement of your privacy manifest, please don't hesitate to reach out. - -## Contact info - -| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | -| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | -| **Name** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | βœ… | -| **Email Address** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | βœ… | -| **Phone Number** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | βœ… | - -## User Content - -| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | -| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | -| **Photos or Videos** | | | | | | -| | Storage | App Functionality | ❌ | ❌ | βœ… | -| | Predictions | App Functionality | ❌ | ❌ | βœ… | -| **Audio Data** | | | | | | -| | Predictions | App Functionality | ❌ | ❌ | βœ… | - -## Identifiers - -| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | -| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | -| **User ID** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| | Analytics | Analytics | βœ… | ❌ | ❌ | -| **Device ID** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| | Analytics | Analytics | βœ… | ❌ | ❌ | - -## Other Data - -| Data Type | Amplify Category | Purpose | Linked To Identity | Tracking | Provided by developer | -| ------------------------------ | ------------------ | ------------------- | :------------------: | :--------: | :---------------------: | -| **OS Version** | | | | | | -| | All categories | Analytics | ❌ | ❌ | ❌ | -| **OS Name** | | | | | | -| | All categories | Analytics | ❌ | ❌ | ❌ | -| **Locale Info** | | | | | | -| | All categories | Analytics | ❌ | ❌ | ❌ | -| **App Version** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Min OS target of the app** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Timezone information** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Network information** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Has SIM card** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Cellular Carrier Name** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Device Model** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Device Name** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Device OS Version** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Device Height and Width** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **Device Language** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | -| **identifierForVendor** | | | | | | -| | Auth | App Functionality | βœ… | ❌ | ❌ | - -## Health and Fitness -No data is collected - -## Financial Info -No data is collected - -## Location -No data is collected - -## Sensitive Info -No data is collected - -## Contacts -No data is collected - -## Browsing History -No data is collected - -## Search History -No data is collected - -## Diagnostics -No data is collected - -## Clearing data - -Some Amplify categories such as Analytics, Auth, and DataStore persist data to the local device. Some of that data is automatically removed when a user uninstalls the app from the device. - -Amplify stores Auth information in the local [system keychain](https://developer.apple.com/documentation/security/keychain_services), which does not guarantee any particular behavior around whether data is removed when an app is uninstalled. - -Deciding on when to clear this auth information is not something that the SDK can do in a generic way, so App developers should decide when to clear the data by signing out. One strategy for accomplishing this would be to use [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) to detect whether or not the app is launching for the first time, and invoking [`Auth.signOut()`](/[platform]/build-a-backend/auth/connect-your-frontend/sign-out/) if the app has not been launched before. - ---- - ---- -title: "Manage files with Amplify console" -section: "build-a-backend/storage" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-08-06T19:20:15.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/storage/manage-with-amplify-console/" ---- - -The **File storage** page in the Amplify console provides a user-friendly interface for managing your application's backend file storage. It allows for efficient testing and management of your files. - -If you have not yet created a **storage** resource, visit the [Storage setup guide](/[platform]/build-a-backend/storage/set-up-storage/). - -## Access File storage - -After you've deployed your storage resource, you can access the manager on Amplify Console. - -1. Log in to the [Amplify console](https://console.aws.amazon.com/amplify/home) and choose your app. -2. Select the branch you would like to access. -3. Select **Storage** from the left navigation bar. - -### To upload a file - -1. On the **Storage** page, select the **Upload** button -2. Select the file you would like to upload and then select **Done** - -Alternatively, you can **Drag and drop** a file onto the Storage page. - -### To delete a file - -1. On the **Storage** page, select the file you want to delete. -2. Select the **Actions** dropdown and then select **Delete**. - -### To copy a file - -1. On the **Storage** page, select the file you want to copy. -2. Select the **Actions** dropdown and then select **Copy to**. -3. Select or create the folder you want a copy of your file to be saved to. -4. Select **Copy** to copy your file to the selected folder. - -### To move a file - -1. On the **Storage** page, select the file you want to move. -3. Select the **Actions** dropdown and then select **Move to**. -4. Select or create the folder you want to move your file to. -5. Select **Move** to move your file to the selected folder. - ---- - ---- -title: "API References" -section: "build-a-backend/storage" -platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] -gen: 2 -last-updated: "" -url: "https://docs.amplify.aws/react/build-a-backend/storage/reference/" ---- - - - ---- - ---- -title: "Functions" -section: "build-a-backend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-04-18T20:39:52.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/" ---- - - - ---- - ---- -title: "Set up a Function" -section: "build-a-backend/functions" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-03-25T21:50:31.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/set-up-function/" ---- - -Amplify Functions are powered by [AWS Lambda](https://aws.amazon.com/lambda/), and allow you to perform a wide variety of customization through self-contained _functions_. Functions can respond to events from other resources, execute some logic in-between events like an authentication flow, or act as standalone jobs. They are used in a variety of settings and use cases: - -- Authentication flow customizations (e.g. attribute validations, allowlisting email domains) -- Resolvers for GraphQL APIs -- Handlers for individual REST API routes, or to host an entire API -- Scheduled jobs - -To get started, create a new directory and a resource file, `amplify/functions/say-hello/resource.ts`. Then, define the Function with `defineFunction`: - -```ts title="amplify/functions/say-hello/resource.ts" -import { defineFunction } from '@aws-amplify/backend'; - -export const sayHello = defineFunction({ - // optionally specify a name for the Function (defaults to directory name) - name: 'say-hello', - // optionally specify a path to your handler (defaults to "./handler.ts") - entry: './handler.ts' -}); -``` - -Next, create the corresponding handler file at `amplify/functions/say-hello/handler.ts`. This is where your function code will go. - -```ts title="amplify/functions/say-hello/handler.ts" -import type { Handler } from 'aws-lambda'; - -export const handler: Handler = async (event, context) => { - // your function code goes here - return 'Hello, World!'; -}; -``` - -The handler file _must_ export a function named "handler". This is the entry point to your function. For more information on writing functions, refer to the [AWS documentation for Lambda function handlers using Node.js](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html). - -Lastly, this function needs to be added to your backend. - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -// highlight-next-line -import { sayHello } from './functions/say-hello/resource'; - -defineBackend({ - // highlight-next-line - sayHello -}); -``` - -Now when you run `npx ampx sandbox` or deploy your app on Amplify, it will include your Function. - -To invoke your Function, we recommend adding your [Function as a handler for a custom query with your Amplify Data resource](/[platform]/build-a-backend/data/custom-business-logic/). This will enable you to strongly type Function arguments and the return statement, and use this to author your Function's business logic. To get started, open your `amplify/data/resource.ts` file and specify a new query in your schema: - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend" -import { sayHello } from "../functions/say-hello/resource" - -const schema = a.schema({ - // highlight-start - sayHello: a - .query() - .arguments({ - name: a.string(), - }) - .returns(a.string()) - .authorization(allow => [allow.guest()]) - .handler(a.handler.function(sayHello)), - // highlight-end -}) - -export type Schema = ClientSchema - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "iam", - }, -}) -``` - -Now you can use this query from the `Schema` export to strongly type your Function handler: - -```ts title="amplify/functions/say-hello/handler.ts" -import type { Schema } from "../../data/resource" - -export const handler: Schema["sayHello"]["functionHandler"] = async (event) => { - // arguments typed from `.arguments()` - const { name } = event.arguments - // return typed from `.returns()` - return `Hello, ${name}!` -} -``` - -Finally, use the data client to invoke your Function by calling its associated query. - - -```ts title="src/main.ts" -import type { Schema } from "./amplify/data/resource" -import { Amplify } from "aws-amplify" -import { generateClient } from "aws-amplify/api" -import outputs from "./amplify_outputs.json" - -Amplify.configure(outputs) - -const client = generateClient() - -// highlight-start -client.queries.sayHello({ - name: "Amplify", -}) -// highlight-end -``` - - -```kt -data class SayHelloDetails( - val name: String, -) - -data class SayHelloResponse( - val sayHello: SayHelloDetails -) - -val document = """ - query SayHelloQuery(${'$'}name: String!) { - sayHello(name: ${'$'}name) { - name - executionDuration - } - } -""".trimIndent() -val sayHelloQuery = SimpleGraphQLRequest( - document, - mapOf("name" to "Amplify"), - String::class.java, - GsonVariablesSerializer()) - -Amplify.API.query( - sayHelloQuery, - { - var gson = Gson() - val response = gson.fromJson(it.data, SayHelloResponse::class.java) - Log.i("MyAmplifyApp", "${response.sayHello.name}") - }, - { Log.e("MyAmplifyApp", "$it")} -) -``` - - -First define a class that matches your response shape: - -```dart -class SayHelloResponse { - final SayHello sayHello; - - SayHelloResponse({required this.sayHello}); - - factory SayHelloResponse.fromJson(Map json) { - return SayHelloResponse( - sayHello: SayHello.fromJson(json['sayHello']), - ); - } -} - -class SayHello { - final String name; - final double executionDuration; - - SayHello({required this.name, required this.executionDuration}); - - factory SayHello.fromJson(Map json) { - return SayHello( - name: json['name'], - executionDuration: json['executionDuration'], - ); - } -} -``` - -Next, make the request and map the response to the classes defined above: - -```dart -// highlight-next-line -import 'dart:convert'; - -// highlight-start -const graphQLDocument = ''' - query SayHello(\$name: String!) { - sayHello(name: \$name) { - name - executionDuration - } - } -'''; - -final echoRequest = GraphQLRequest( - document: graphQLDocument, - variables: {"name": "Amplify"}, -); - -final response = - await Amplify.API.query(request: echoRequest).response; -safePrint(response); - -Map jsonMap = json.decode(response.data!); -SayHelloResponse SayHelloResponse = SayHelloResponse.fromJson(jsonMap); -safePrint(SayHelloResponse.sayHello.name); -// highlight-end -``` - - -```swift -struct SayHelloResponse: Codable { - public let sayHello: SayHello - - struct SayHello: Codable { - public let name: String - public let executionDuration: Float - } -} - -let document = """ - query EchoQuery($name: String!) { - sayHello(name: $name) { - name - executionDuration - } - } - """ - -let result = try await Amplify.API.query(request: GraphQLRequest( - document: document, - variables: [ - "name": "Amplify" - ], - responseType: SayHelloResponse.self -)) -switch result { -case .success(let response): - print(response.sayHello) -case .failure(let error): - print(error) -} -``` - - -## Next steps - -Now that you have completed setting up your first Function, you may also want to add some additional features or modify a few settings. We recommend you learn more about: - -- [Environment variables and secrets](/[platform]/build-a-backend/functions/environment-variables-and-secrets/) -- [Grant access to other resources](/[platform]/build-a-backend/functions/grant-access-to-other-resources/) -- [Explore example use cases](/[platform]/build-a-backend/functions/examples/) -- [Modifying underlying resources with CDK](/[platform]/build-a-backend/functions/modify-resources-with-cdk/) - ---- - ---- -title: "Environment variables and secrets" -section: "build-a-backend/functions" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-22T17:49:49.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/environment-variables-and-secrets/" ---- - -Amplify Functions support setting environment variables and secrets on the `environment` property of `defineFunction`. - -> **Warning:** **Note:** do not store secret values in environment variables. Environment variables values are rendered in plaintext to the build artifacts located at `.amplify/artifacts` and may be emitted to CloudFormation stack event messages. To store secrets [skip to the secrets section](#secrets) - -> **Info:** **Note:** Environment variables and secrets configuration in `defineFunction` is not supported for [Custom Functions](/[platform]/build-a-backend/functions/custom-functions/). - -## Environment variables - -Environment variables can be configured in `defineFunction` using the `environment` property. - -```ts title="amplify/functions/say-hello/resource.ts" -import { defineFunction } from '@aws-amplify/backend'; - -export const sayHello = defineFunction({ - environment: { - NAME: 'World' - } -}); -``` - -Any environment variables specified here will be available to the function at runtime. - -Some environment variables are constant across all branches and deployments. But many environment values differ between deployment environments. [Branch-specific environment variables can be configured for Amplify hosting deployments](/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/). - -Suppose you created a branch-specific environment variable in hosting called "API_ENDPOINT" which had a different value for your "staging" vs "prod" branch. If you wanted that value to be available to your function you can pass it to the function using - -```ts title="amplify/functions/say-hello/resource.ts" -export const sayHello = defineFunction({ - environment: { - NAME: "World", - API_ENDPOINT: process.env.API_ENDPOINT - } -}); -``` - -### Accessing environment variables - -Within your function handler, you can access environment variables using the normal `process.env` global object provided by the Node runtime. However, this does not make it easy to discover what environment variables will be available at runtime. Amplify generates an `env` symbol that can be used in your function handler and provides typings for all variables that will be available at runtime. Copy the following code to use it. - -```ts title="amplify/functions/say-hello/handler.ts" -// highlight-next-line -import { env } from '$amplify/env/say-hello'; // the import is '$amplify/env/' - -export const handler = async (event) => { - // the env object has intellisense for all environment variables that are available to the function - return `Hello, ${env.NAME}!`; -}; -``` - - - -At the end of [AWS Cloud Development Kit's (AWS CDK)](https://aws.amazon.com/cdk/) synthesis, Amplify gathers names of environment variables that will be available to the function at runtime and generates the file `.amplify/generated/env/.ts`. - -If you created your project with [`create-amplify`](https://www.npmjs.com/package/create-amplify), then Amplify has already set up your project to use the `env` symbol. - -If you did not, you will need to manually configure your project. Within your `amplify/tsconfig.json` file add a `paths` compiler option: - -```json title="amplify/tsconfig.json" -{ - "compilerOptions": { - "paths": { - "$amplify/*": ["../.amplify/generated/*"] - } - } -} -``` - - - -### Generated env files - -When you configure your function with environment variables or secrets, Amplify's backend tooling generates a file using the function's `name` in `.amplify/generated` with references to your environment variables and secrets, as well as [environment variables predefined by the Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime). This provides a type-safe experience for working with environment variables that does not require typing `process.env` manually. - -> **Info:** **Note:** generated files are created before deployments when executing `ampx sandbox` or `ampx pipeline-deploy` - -For example, if you have a function with the following definition: - -```ts title="amplify/functions/say-hello/resource.ts" -import { defineFunction } from "@aws-amplify/backend"; - -export const sayHello = defineFunction({ - name: "say-hello", - environment: { - NAME: "World", - }, -}); -``` - -Upon starting your next deployment, Amplify will create a file at the following location: - -``` -.amplify/generated/env/say-hello.ts -``` - -Using the TypeScript path alias, `$amplify`, you can import the file in your function's handler: - -```ts title="amplify/functions/say-hello/handler.ts" -import { env } from "$amplify/env/say-hello" - -export const handler = async (event) => { - // the env object has intellisense for all environment variables that are available to the function - return `Hello, ${env.NAME}!`; -}; -``` - -Encountering issues with this file? [Visit the troubleshooting guide for `Cannot find module $amplify/env/`](/[platform]/build-a-backend/troubleshooting/cannot-find-module-amplify-env/) - -## Secrets - -Sometimes it is necessary to provide a secret value to a function. For example, it may need a database password or an API key to perform some business use case. Environment variables should NOT be used for this because environment variable values are included in plaintext in the function configuration. Instead, secret access can be used. - -Before using a secret in a function, you need to [define a secret](/[platform]/deploy-and-host/fullstack-branching/secrets-and-vars/#set-secrets). After you have defined a secret, you can reference it in your function config. - -```ts title="amplify/functions/say-hello/resource.ts" -import { defineFunction, secret } from '@aws-amplify/backend'; - -export const sayHello = defineFunction({ - environment: { - NAME: "World", - API_ENDPOINT: process.env.API_ENDPOINT, - API_KEY: secret('MY_API_KEY') // this assumes you created a secret named "MY_API_KEY" - } -}); -``` - -You can use this secret value at runtime in your function the same as any other environment variable. However, you will notice that the value of the environment variable is not stored as part of the function configuration. Instead, the value is fetched when your function runs and is provided in memory. - -```ts title="amplify/functions/say-hello/handler.ts" -import { env } from '$amplify/env/say-hello'; - -export const handler = async (event) => { - const request = new Request(env.API_ENDPOINT, { - headers: { - // this is the value of secret named "MY_API_KEY" - Authorization: `Bearer ${env.API_KEY}` - } - }) - // ... - return `Hello, ${env.NAME}!`; -}; -``` - ---- - ---- -title: "Configure Functions" -section: "build-a-backend/functions" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-22T17:49:49.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/configure-functions/" ---- - -`defineFunction` comes out-of-the-box with sensible but minimal defaults. The following options are provided to tweak the function configuration. - -> **Info:** **Note:** The following options are not supported for [Custom Functions](/[platform]/build-a-backend/functions/custom-functions/) except for `resourceGroupName`. - -## `name` - -By default, functions are named based on the directory the `defineFunction` call is placed in. In the above example, defining the function in `amplify/functions/my-demo-function/resource.ts` will cause the function to be named `my-demo-function` by default. - -If an entry is specified, then the name defaults to the basename of the entry path. For example, an `entry` of `./signup-trigger-handler.ts` would cause the function name to default to `signup-trigger-handler`. - -This optional property can be used to explicitly set the name of the function. - -```ts title="amplify/functions/my-demo-function/resource.ts" -export const myDemoFunction = defineFunction({ - entry: './demo-function-handler.ts', - name: 'overrideName' // explicitly set the name to override the default naming behavior -}); -``` - -## `timeoutSeconds` - -By default, functions will time out after 3 seconds. This can be configured to any whole number of seconds up to 15 minutes. - -```ts title="amplify/functions/my-demo-function/resource.ts" -export const myDemoFunction = defineFunction({ - // highlight-next-line - timeoutSeconds: 60 // 1 minute timeout -}); -``` - -## `memoryMB` - -By default, functions have 512 MB of memory allocated to them. This can be configured from 128 MB up to 10240 MB. Note that this can increase the cost of function invocation. For more pricing information see [here](https://aws.amazon.com/lambda/pricing/). - -```ts title="amplify/functions/my-demo-function/resource.ts" -export const myDemoFunction = defineFunction({ - // highlight-next-line - memoryMB: 256 // allocate 256 MB of memory to the function. -}); -``` - -## `ephemeralStorageSizeMB` - -By default, functions have 512MB of ephemeral storage to them. This can be configured from 512 MB upto 10240 MB. Note that this can increase the cost of function invocation. For more pricing information visit the [Lambda pricing documentation](https://aws.amazon.com/lambda/pricing/). - -```ts title="amplify/functions/my-demo-function/resource.ts" -export const myDemoFunction = defineFunction({ - // highlight-next-line - ephemeralStorageSizeMB: 1024 // allocate 1024 MB of ephemeral storage to the function. -}); -``` - -## `runtime` - -Currently, only Node runtimes are supported by `defineFunction`. However, you can change the Node version that is used by the function. The default is the oldest Node LTS version that is supported by AWS Lambda (currently Node 18). - -If you wish to use an older version of Node, keep an eye on the [Lambda Node version deprecation schedule](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html). As Lambda removes support for old Node versions, you will have to update to newer supported versions. - -```ts title="amplify/functions/my-demo-function/resource.ts" -export const myDemoFunction = defineFunction({ - runtime: 20 // use Node 20 -}); -``` - -## `entry` - -By default, Amplify will look for your function handler in a file called `handler.ts` in the same directory as the file where `defineFunction` is called. To point to a different handler location, specify an `entry` value. - -```ts title="amplify/functions/my-demo-function/resource.ts" -export const myDemoFunction = defineFunction({ - entry: './path/to/handler.ts' // this path should either be absolute or relative to the current file -}); -``` - -## `resourceGroupName` - -By default, functions are grouped together in a resource group named `function`. You can override this to group related function with other Amplify resources like `auth`, `data`, `storage`, or separate them into your own custom group. -This is typically useful when you have resources that depend on each other and you want to group them together. - -```ts title="amplify/functions/my-demo-function/resource.ts" -export const myDemoFunction = defineFunction({ - resourceGroupName: 'data' -}); -``` - ---- - ---- -title: "Configure client library" -section: "build-a-backend/functions" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-04-21T17:02:02.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/configure-client-library/" ---- - -The [`aws-amplify`](https://www.npmjs.com/package/aws-amplify) client library can be configured for use inside function handler files by using the credentials available from the AWS Lambda runtime. To get started, use the `getAmplifyDataClientConfig` from the backend runtime package and pass the generated `env` object to retrieve the preconfigured `resourceConfig` and `libraryOptions`. - -```ts title="amplify/my-function/handler.ts" -import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; -import { env } from '$amplify/env/my-function'; - -const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig( - env -); -``` - -> **Warning:** When using `getAmplifyDataClientConfig`, your function requires schema information stored in an Amplify deployed Amazon S3 bucket. This bucket is created during backend deployment and includes necessary access grants for your function. Modifying this bucket outside of the backend deployment process may cause unexpected failures on your function. - -`resourceConfig` and `libraryOptions` are returned for you to pass into `Amplify.configure`. This will instruct the client library which resources it can interact with, and where to retrieve AWS credentials to use when signing requests to those resources. - -```ts title="amplify/my-function/handler.ts" -import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; -// highlight-next-line -import { Amplify } from 'aws-amplify'; -import { env } from '$amplify/env/my-function'; - -const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig( - env -); - -// highlight-next-line -Amplify.configure(resourceConfig, libraryOptions); -``` - -The client library will now have access to perform operations against other AWS resources as specified by the function's IAM role. This is handled for you when [granting access to other resources using the `access` property](/[platform]/build-a-backend/functions/grant-access-to-other-resources/#using-the-access-property), however it can also be [extended using CDK](/[platform]/build-a-backend/functions/grant-access-to-other-resources/#using-cdk). - -## Under the hood - -The `getAmplifyDataClientConfig` function assists with creating the arguments' values to pass to `Amplify.configure`, which reads from the generated `env` object in order to produce configuration for the resources you have granted your function access to interact with. Under the hood this is also generating the configuration that specifies how the client library should behave, namely where the library should read credentials. - -```ts title="amplify/my-function/handler.ts" -import { env } from "$amplify/env/my-function"; - -Amplify.configure( - {/* resource configuration */}, - { - Auth: { - credentialsProvider: { - // instruct the client library to read credentials from the environment - getCredentialsAndIdentityId: async () => ({ - credentials: { - accessKeyId: env.AWS_ACCESS_KEY_ID, - secretAccessKey: env.AWS_SECRET_ACCESS_KEY, - sessionToken: env.AWS_SESSION_TOKEN, - }, - }), - clearCredentialsAndIdentityId: () => { - /* noop */ - }, - }, - }, - } -); -``` - ---- - ---- -title: "Scheduling Functions" -section: "build-a-backend/functions" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-02-12T16:04:46.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/scheduling-functions/" ---- - -Amplify offers the ability to schedule Functions to run on specific intervals using natural language or [cron expressions](https://en.wikipedia.org/wiki/Cron). To get started, specify the `schedule` property in `defineFunction`: - -> **Info:** **Note:** Configuring the schedule in `defineFunction` is not supported for [Custom Functions](/[platform]/build-a-backend/functions/custom-functions/). - -```ts title="amplify/jobs/weekly-digest/resource.ts" -import { defineFunction } from "@aws-amplify/backend"; - -export const weeklyDigest = defineFunction({ - name: "weekly-digest", - schedule: "every week", -}); -``` - -Function schedules are powered by [Amazon EventBridge rules](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rules.html), and can be leveraged to address use cases such as: - -- generating a "front page" of top-performing posts -- generating a weekly digest of top-performing posts -- generating a monthly report of warehouse inventory - -Their handlers can be typed using the `EventBridgeHandler` type: - -```ts title="amplify/jobs/weekly-digest/handler.ts" -import type { EventBridgeHandler } from "aws-lambda"; - -export const handler: EventBridgeHandler<"Scheduled Event", null, void> = async (event) => { - console.log("event", JSON.stringify(event, null, 2)) -} -``` - -> **Info:** **Note**: AWS Lambda types can be installed with -> -> ```bash title="Terminal" showLineNumbers={false} -npm add --save-dev @types/aws-lambda -``` - -Schedules can either be a single interval, or multiple intervals: - -```ts title="amplify/jobs/generate-report/resource.ts" -import { defineFunction } from "@aws-amplify/backend"; - -export const generateReport = defineFunction({ - name: "generate-report", - schedule: ["every week", "every month", "every year"], -}); -``` - -Schedules can also be defined to execute using minutes or hours with a shorthand syntax: - -```ts title="amplify/jobs/drink-some-water/resource.ts" -import { defineFunction } from "@aws-amplify/backend"; - -export const drinkSomeWater = defineFunction({ - name: "drink-some-water", - schedule: "every 1h" -}) -``` - -Or combined to create complex schedules: - -```ts title="amplify/jobs/remind-me/resource.ts" -import { defineFunction } from "@aws-amplify/backend"; - -export const remindMe = defineFunction({ - name: "remind-me", - schedule: [ - // every sunday at midnight - "every week", - // every tuesday at 5pm - "0 17 ? * 3 *", - // every wednesday at 5pm - "0 17 ? * 4 *", - // every thursday at 5pm - "0 17 ? * 5 *", - // every friday at 5pm - "0 17 ? * 6 *", - ] -}) -``` - -## Using natural language - -Schedules can be written using natural language, using terms you use every day. Amplify supports the following time periods: - -- `day` will always start at midnight -- `week` will always start on Sunday at midnight -- `month` will always start on the first of the month at midnight -- `year` will always start on the first of the year at midnight -- `m` for minutes -- `h` for hours - -Natural language expressions are prefixed with "every": - -```ts title="amplify/jobs/drink-some-water/resource.ts" -import { defineFunction } from "@aws-amplify/backend"; - -export const drinkSomeWater = defineFunction({ - name: "drink-some-water", - schedule: "every 1h" -}) -``` - -## Using cron expressions - -Schedules can be written using cron expressions. - -```ts title="amplify/jobs/remind-me/resource.ts" -import { defineFunction } from "@aws-amplify/backend"; - -export const remindMe = defineFunction({ - name: "remind-me-to-take-the-trash-out", - schedule: [ - // every tuesday at 9am - "0 9 ? * 3 *", - // every friday at 9am - "0 9 ? * 6 *", - ] -}) -``` - ---- - ---- -title: "Streaming logs" -section: "build-a-backend/functions" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-02-25T21:44:20.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/streaming-logs/" ---- - -Amplify enables you to stream logs from your AWS Lambda functions directly to your terminal while running [`ampx sandbox`](/[platform]/reference/cli-commands/#npx-ampx-sandbox). To get started, specify the `--stream-function-logs` option when starting sandbox: - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox --stream-function-logs -``` - -> **Info:** **Note**: this feature is only available for [Sandbox](/[platform]/deploy-and-host/sandbox-environments/) - -Streaming function logs directly to your terminal enable faster debug iterations, and greater insight into your functions' executions. - -## Filtering - -By default, Amplify will stream all of your functions' logs. If you wish to only stream a subset of functions you can specify a filter by function name or a regular expression for function names. For example, if you have a collection of [Auth triggers](/[platform]/build-a-backend/auth/customize-auth-lifecycle/triggers/) where the function names include "auth". - -> **Info:** When working with more than 5 functions, we recommend using the `--logs-filter` flag to filter the log output to specific functions. - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox --stream-function-logs --logs-filter auth -``` - -After you successfully deploy your personal cloud sandbox, start your frontend application, and sign up for the first time, you will see logs from your triggers' executions printed to the terminal where sandbox is running. - -```console title="Terminal" -> npx ampx sandbox --stream-function-logs --logs-filter auth -... - -✨ Total time: 158.44s - -[Sandbox] Watching for file changes... -File written: amplify_outputs.json -[auth-pre-sign-up] 3:36:34 PM INIT_START Runtime Version: nodejs:18.v30 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:f89c264158db39a1cfcbb5f9b3741413df1cfce4d550c9a475a67d923e19e2f4 -[auth-pre-sign-up] 3:36:34 PM START RequestId: 685be2bd-5df1-4dd5-9eb1-24f5f6337f91 Version: $LATEST -[auth-pre-sign-up] 3:36:34 PM END RequestId: 685be2bd-5df1-4dd5-9eb1-24f5f6337f91 -[auth-pre-sign-up] 3:36:34 PM REPORT RequestId: 685be2bd-5df1-4dd5-9eb1-24f5f6337f91 Duration: 4.12 ms Billed Duration: 5 ms Memory Size: 512 MB Max Memory Used: 67 MB Init Duration: 173.67 ms -[auth-post-confirmation] 3:38:40 PM INIT_START Runtime Version: nodejs:18.v30 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:f89c264158db39a1cfcbb5f9b3741413df1cfce4d550c9a475a67d923e19e2f4 -[auth-post-confirmation] 3:38:40 PM START RequestId: fce69b9f-b257-4af8-8a6e-821f84a39ce7 Version: $LATEST -[auth-post-confirmation] 3:38:41 PM 2024-07-19T22:38:41.209Z fce69b9f-b257-4af8-8a6e-821f84a39ce7 INFO processed 412f8911-acfa-41c7-9605-fa0c40891ea9 -[auth-post-confirmation] 3:38:41 PM END RequestId: fce69b9f-b257-4af8-8a6e-821f84a39ce7 -[auth-post-confirmation] 3:38:41 PM REPORT RequestId: fce69b9f-b257-4af8-8a6e-821f84a39ce7 Duration: 264.38 ms Billed Duration: 265 ms Memory Size: 512 MB Max Memory Used: 93 MB Init Duration: 562.19 ms -[auth-pre-authentication] 3:38:41 PM INIT_START Runtime Version: nodejs:18.v30 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:f89c264158db39a1cfcbb5f9b3741413df1cfce4d550c9a475a67d923e19e2f4 -[auth-pre-authentication] 3:38:41 PM START RequestId: 9210ca3a-1351-4826-8544-123684765710 Version: $LATEST -[auth-pre-authentication] 3:38:41 PM END RequestId: 9210ca3a-1351-4826-8544-123684765710 -[auth-pre-authentication] 3:38:41 PM REPORT RequestId: 9210ca3a-1351-4826-8544-123684765710 Duration: 3.47 ms Billed Duration: 4 ms Memory Size: 512 MB Max Memory Used: 67 MB Init Duration: 180.24 ms -[auth-post-authentication] 3:38:42 PM INIT_START Runtime Version: nodejs:18.v30 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:f89c264158db39a1cfcbb5f9b3741413df1cfce4d550c9a475a67d923e19e2f4 -[auth-post-authentication] 3:38:42 PM START RequestId: 60c1d680-ea24-4a8b-93de-02d085859140 Version: $LATEST -[auth-post-authentication] 3:38:42 PM END RequestId: 60c1d680-ea24-4a8b-93de-02d085859140 -[auth-post-authentication] 3:38:42 PM REPORT RequestId: 60c1d680-ea24-4a8b-93de-02d085859140 Duration: 4.61 ms Billed Duration: 5 ms Memory Size: 512 MB Max Memory Used: 68 MB Init Duration: 172.66 ms -``` - -## Writing to a file - -By default, Amplify will print logs to the terminal where sandbox is running, however you can alternatively write logs to a file by specifying `--logs-out-file`: - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox --stream-function-logs --logs-out-file sandbox.log -``` - -This can be combined with `--logs-filter` to create a log file of just your Auth-related functions, for example: - -```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox --stream-function-logs --logs-filter auth --logs-out-file sandbox-auth.log -``` - -However it cannot be combined multiple times to write logs to multiple files. - ---- - ---- -title: "Lambda Layers" -section: "build-a-backend/functions" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-22T17:49:49.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/add-lambda-layers/" ---- - -Amplify offers the ability to add layers to your functions which contain your library dependencies. Lambda layers allow you to separate your function code from its dependencies, enabling easier management of shared components across multiple functions and reducing deployment package sizes. - -> **Info:** **Note:** Configuring or adding layers in `defineFunction` is not supported for [Custom Functions](/[platform]/build-a-backend/functions/custom-functions/). - -To add a Lambda layer to your function, follow these steps: - -1. First, create and set up your Lambda layer in AWS. You can do this through the AWS Console or using the AWS CLI. For guidance on creating layers, refer to the [AWS documentation on creating Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html#configuration-layers-create). - -2. Once your layer is created and available in AWS, you can reference it in your Amplify project as shown below. - - Specify the `layers` property in `defineFunction`, for example: - - ```ts title="amplify/functions/my-function/resource.ts" - import { defineFunction } from "@aws-amplify/backend"; - - export const myFunction = defineFunction({ - name: "my-function", - layers: { - "@aws-lambda-powertools/logger": - "arn:aws:lambda:us-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:12", - }, - }); - ``` - - The Lambda layer is represented by an object of key/value pairs where the key is the module name that is exported from your layer and the value is the ARN of the layer. The key (module name) is used to externalize the module dependency so it doesn't get bundled with your Lambda function. A maximum of 5 layers can be attached to a function, and they must be in the same region as the function. - -
    Alternatively, you can specify the layer as `myLayer:1` where `myLayer` is the name of the layer and `1` is the version of the layer. For example: - - ```ts title="amplify/functions/my-function/resource.ts" - import { defineFunction } from "@aws-amplify/backend"; - - export const myFunction = defineFunction({ - name: "my-function", - layers: { - "some-module": "myLayer:1" - }, - }); - ``` - - Amplify will automatically convert this to the full layer ARN format `arn:aws:lambda:::layer:myLayer:1` using your existing account ID and region. - - - - When using layers, be mindful of versioning. The ARN includes a version number (e.g., `:12` in the example). Ensure you're using the appropriate version and have a strategy for updating layers when new versions are released. - - - -3. Then use the locally installed module in the function handler: - ```ts title="amplify/functions/my-function/handler.ts" - import { Logger } from "@aws-lambda-powertools/logger"; - import type { Handler } from "aws-lambda"; - - const logger = new Logger({ serviceName: "serverlessAirline" }); - - export const handler: Handler = async (event, context) => { - logger.info("Hello World"); - }; - ``` - -For further information on creating and managing your layers refer to [AWS documentation for Lambda layers](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html) - ---- - ---- -title: "Grant access to other resources" -section: "build-a-backend/functions" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-11-01T21:19:44.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/grant-access-to-other-resources/" ---- - -In order for Amplify Functions to interact with other resources they must be given access. There are two ways to grant Amplify Functions access to other resources: - -1. [Using the `access` property](#using-the-access-property) -2. [Using the AWS Cloud Development Kit (CDK)](#using-cdk) - -## Using the `access` property - -The `access` property is a property found in each of the `define*` functions for defining Amplify resources. It allows you specify the necessary actions using common language. - - - -When you grant a function access to another resource in your Amplify backend ([such as granting access to storage](/[platform]/build-a-backend/storage/#resource-access)), it will configure environment variables for that function to make SDK calls to the AWS services it has access to. Those environment variables are typed and available as part of the `env` object. - - - -Say you have a function that generates reports each month from your Data resource and needs to store the generated reports in Storage: - -```ts title="amplify/storage/resource.ts" -import { defineStorage } from '@aws-amplify/backend'; -import { generateMonthlyReports } from '../functions/generate-monthly-reports/resource'; - -export const storage = defineStorage({ - name: 'myReports', - access: (allow) => ({ - 'reports/*': [ - allow.resource(generateMonthlyReports).to(['read', 'write', 'delete']) - ] - }) -}); -``` - -This access definition will add the environment variable `myReports_BUCKET_NAME` to the function. This environment variable can be accessed on the `env` object. - -Here's an example of how it can be used to upload some content to S3. - -```ts title="amplify/functions/generate-monthly-reports/handler.ts" -import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; -import { env } from '$amplify/env/generate-monthly-reports'; - -const s3Client = new S3Client(); - -export const handler = async () => { - const command = new PutObjectCommand({ - Bucket: env.MY_REPORTS_BUCKET_NAME, - Key: `reports/${new Date().toISOString()}.csv`, - Body: new Blob([''], { type: 'text/csv;charset=utf-8;' }) - }); - - await s3Client.send(command); -}; -``` - -## Using CDK - -When permissions are needed to access resources beyond the capabilities of the `access` property, you must use CDK. - -Functions are created with an [_execution role_](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html), which is an IAM role that contains policies that dictate what resources your Function can interact with when it executes. This role can be extended using the [`addToRolePolicy()`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.IFunction.html#addwbrtowbrrolewbrpolicystatement) method: - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend" -import * as iam from "aws-cdk-lib/aws-iam" -import * as sns from "aws-cdk-lib/aws-sns" -import { weeklyDigest } from "./functions/weekly-digest/resource" - -const backend = defineBackend({ - weeklyDigest, -}) - -const weeklyDigestLambda = backend.weeklyDigest.resources.lambda - -const topicStack = backend.createStack("WeeklyDigest") -const topic = new sns.Topic(topicStack, "Topic", { - displayName: "digest", -}) - -// highlight-start -const statement = new iam.PolicyStatement({ - sid: "AllowPublishToDigest", - actions: ["sns:Publish"], - resources: [topic.topicArn], -}) - -weeklyDigestLambda.addToRolePolicy(statement) - // highlight-end -``` - -However some constructs provide a `grant*` method to grant access to common policy actions. Revisiting the example above you can grant the same access with `grantPublish`: - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend" -import * as sns from "aws-cdk-lib/aws-sns" -import { weeklyDigest } from "./functions/weekly-digest/resource" - -const backend = defineBackend({ - weeklyDigest, -}) - -const weeklyDigestLambda = backend.weeklyDigest.resources.lambda - -const topicStack = backend.createStack("WeeklyDigest") -const topic = new sns.Topic(topicStack, "Topic", { - displayName: "digest" -}) - -// highlight-next-line -topic.grantPublish(weeklyDigestLambda) -``` - ---- - ---- -title: "Examples" -section: "build-a-backend/functions" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-04-01T16:12:41.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/" ---- - - - ---- - ---- -title: "Email domain filtering" -section: "build-a-backend/functions/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-04-24T15:59:23.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/email-domain-filtering/" ---- - -You can use `defineAuth` and `defineFunction` to create a [Cognito pre sign-up Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html) that performs filtering based on the user's email address. This can allow or deny user signups based on their email address. - -To get started, install the `aws-lambda` package, which is used to define the handler type. - -```bash title="Terminal" showLineNumbers={false} -npm add --save-dev @types/aws-lambda -``` - -Next, create a new directory and a resource file, `amplify/auth/pre-sign-up/resource.ts`. Then, define the Function with `defineFunction`: - -```ts title="amplify/auth/pre-sign-up/resource.ts" -import { defineFunction } from '@aws-amplify/backend'; - -export const preSignUp = defineFunction({ - name: 'pre-sign-up', - // optionally define an environment variable for your filter - environment: { - ALLOW_DOMAIN: 'amazon.com' - } -}); -``` - -Next, create the corresponding handler file, `amplify/auth/pre-sign-up/handler.ts`, file with the following contents: - -```ts title="amplify/auth/pre-sign-up/handler.ts" -import type { PreSignUpTriggerHandler } from 'aws-lambda'; -import { env } from '$amplify/env/pre-sign-up'; - -export const handler: PreSignUpTriggerHandler = async (event) => { - const email = event.request.userAttributes['email']; - - if (!email.endsWith(env.ALLOW_DOMAIN)) { - throw new Error('Invalid email domain'); - } - - return event; -}; -``` - -Lastly, set the newly created Function resource on your auth resource: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from '@aws-amplify/backend'; -import { preSignUp } from './pre-sign-up/resource'; - -export const auth = defineAuth({ - // ... - triggers: { - preSignUp - } -}); -``` - -After deploying the changes, whenever a user attempts to sign up without an `amazon.com` email address they will receive an error. - ---- - ---- -title: "Add user to group" -section: "build-a-backend/functions/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-12-09T21:42:11.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/add-user-to-group/" ---- - -You can use `defineAuth` and `defineFunction` to create a [Cognito post confirmation Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html) that extends the behavior to perform some action when a user is confirmed. - -> **Info:** A user is "confirmed" when they verify their account. Typically this happens when the user confirms their email via the verification email. The post confirmation handler will _not_ be triggered for federated sign-ins (i.e. social sign-in). - -To get started, install the AWS SDK v3 package, which will be used to perform actions against your auth resource, and the `@types/aws-lambda` package, which is used to define the handler type: - -```bash title="Terminal" -npm add --save-dev @aws-sdk/client-cognito-identity-provider @types/aws-lambda -``` - -Next, create a new directory and a resource file, `amplify/auth/post-confirmation/resource.ts`. Then, define the Function with `defineFunction`: - -```ts title="amplify/auth/post-confirmation/resource.ts" -import { defineFunction } from '@aws-amplify/backend'; - -export const postConfirmation = defineFunction({ - name: 'post-confirmation', - // optionally define an environment variable for your group name - environment: { - GROUP_NAME: 'EVERYONE' - }, - resourceGroupName: 'auth' -}); -``` - -After creating the Function definition you will need to: - -1. create the `EVERYONE` group -2. grant access to your auth resource to ensure it can perform the `addUserToGroup` action -3. set the Function as the post confirmation trigger - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend"; -import { postConfirmation } from "./post-confirmation/resource" - -export const auth = defineAuth({ - loginWith: { - email: true, - }, - groups: ["EVERYONE"], - triggers: { - postConfirmation, - }, - access: (allow) => [ - allow.resource(postConfirmation).to(["addUserToGroup"]), - ], -}) -``` - -Then create the Function's corresponding handler file, `amplify/auth/post-confirmation/handler.ts`, file with the following contents: - -```ts title="amplify/auth/post-confirmation/handler.ts" -import type { PostConfirmationTriggerHandler } from 'aws-lambda'; -import { - CognitoIdentityProviderClient, - AdminAddUserToGroupCommand -} from '@aws-sdk/client-cognito-identity-provider'; -import { env } from '$amplify/env/post-confirmation'; - -const client = new CognitoIdentityProviderClient(); - -// add user to group -export const handler: PostConfirmationTriggerHandler = async (event) => { - const command = new AdminAddUserToGroupCommand({ - GroupName: env.GROUP_NAME, - Username: event.userName, - UserPoolId: event.userPoolId - }); - const response = await client.send(command); - console.log('processed', response.$metadata.requestId); - return event; -}; -``` - -After deploying the changes, whenever a user signs up and verifies their account they are automatically added to the group named "EVERYONE". - ---- - ---- -title: "Create a user profile record" -section: "build-a-backend/functions/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-12-09T22:00:29.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/create-user-profile-record/" ---- - -You can use `defineAuth` and `defineFunction` to create a [Cognito post confirmation Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html) to create a profile record when a user is confirmed. - -> **Info:** A user is "confirmed" when they verify their account. Typically this happens when the user confirms their email via the verification email. The post confirmation handler will _not_ be triggered for federated sign-ins (i.e. social sign-in). -To get started, install the `aws-lambda` package, which is used to define the handler type. - -```bash title="Terminal" showLineNumbers={false} -npm add --save-dev @types/aws-lambda -``` - -Update the `amplify/data/resource.ts` file to define a data model for the user's profile: - -> **Warning:** Make sure to configure the authorization rule to allow the `postConfirmation` resource as highlighted below. Granting access to resources creates environment variables for your Function such as the GraphQL API endpoint. To learn more visit the [environment variables and secrets documentation for Functions](/[platform]/build-a-backend/functions/environment-variables-and-secrets/). - -```ts title="amplify/data/resource.ts" -import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; -import { postConfirmation } from "../auth/post-confirmation/resource"; - -const schema = a - .schema({ - UserProfile: a - .model({ - email: a.string(), - profileOwner: a.string(), - }) - .authorization((allow) => [ - allow.ownerDefinedIn("profileOwner"), - ]), - }) - .authorization((allow) => [allow.resource(postConfirmation)]); -export type Schema = ClientSchema; - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "apiKey", - apiKeyAuthorizationMode: { - expiresInDays: 30, - }, - }, -}); - -``` - -Create a new directory and a resource file, `amplify/auth/post-confirmation/resource.ts`. Then, define the Function with `defineFunction`: - -```ts title="amplify/auth/post-confirmation/resource.ts" -import { defineFunction } from '@aws-amplify/backend'; - -export const postConfirmation = defineFunction({ - name: 'post-confirmation', -}); -``` - -Then, create the corresponding handler file, `amplify/auth/post-confirmation/handler.ts`, file with the following contents: - -```ts title="amplify/auth/post-confirmation/handler.ts" -import type { PostConfirmationTriggerHandler } from "aws-lambda"; -import { type Schema } from "../../data/resource"; -import { Amplify } from "aws-amplify"; -import { generateClient } from "aws-amplify/data"; -import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; -import { env } from "$amplify/env/post-confirmation"; - -const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig( - env -); - -Amplify.configure(resourceConfig, libraryOptions); - -const client = generateClient(); - -export const handler: PostConfirmationTriggerHandler = async (event) => { - await client.models.UserProfile.create({ - email: event.request.userAttributes.email, - profileOwner: `${event.request.userAttributes.sub}::${event.userName}`, - }); - - return event; -}; - -``` - -> **Warning:** When configuring Amplify with `getAmplifyDataClientConfig`, your function consumes schema information from an S3 bucket created during backend deployment with grants for the access your function need to use it. Any changes to this bucket outside of backend deployment may break your function. - -Lastly, set the newly created Function resource on your auth resource: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from '@aws-amplify/backend'; -import { postConfirmation } from './post-confirmation/resource'; - -export const auth = defineAuth({ - loginWith: { - email: true, - }, - triggers: { - postConfirmation - } -}); -``` - -After deploying the changes, whenever a user signs up and verifies their account a profile record is automatically created. - ---- - ---- -title: "Override ID token claims" -section: "build-a-backend/functions/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-12-09T21:42:11.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/override-token/" ---- - -You can use `defineAuth` and `defineFunction` to create an [Amazon Cognito Pre token generation AWS Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html) to override the token by adding a new claim or modifying the user's group membership. - -To get started, install the `aws-lambda` package, which is used to define the handler type. - -```bash title="Terminal" showLineNumbers={false} -npm add --save-dev @types/aws-lambda -``` - -Create a new directory and a resource file, `amplify/auth/pre-token-generation/resource.ts`. Then, define the function with `defineFunction`: - -```ts title="amplify/auth/pre-token-generation/resource.ts" -import { defineFunction } from '@aws-amplify/backend'; - -export const preTokenGeneration = defineFunction({ - name: 'pre-token-generation', - resourceGroupName: 'auth' -}); -``` - -Then, create the corresponding handler file, `amplify/auth/post-confirmation/pre-token-generation/handler.ts`, file with the following contents: - -```ts title="amplify/auth/pre-token-generation/handler.ts" -import type { PreTokenGenerationTriggerHandler } from "aws-lambda"; - -export const handler: PreTokenGenerationTriggerHandler = async (event) => { - event.response = { - claimsOverrideDetails: { - groupOverrideDetails: { - // This will add the user to the cognito group "amplify_group_1" - groupsToOverride: ["amplify_group_1"], - }, - claimsToAddOrOverride: { - // This will add the custom claim "amplfy_attribute" to the id token - amplfy_attribute: "amplify_gen_2", - }, - }, - }; - return event; -}; - -``` - -Lastly, set the newly created function resource on your auth resource: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from '@aws-amplify/backend'; -import { preTokenGeneration } from './pre-token-generation/resource'; - -export const auth = defineAuth({ - loginWith: { - email: true, - }, - triggers: { - preTokenGeneration - } -}); -``` - -After deploying the changes, The idToken of the user will be modified as per the trigger above. - -```json showLineNumbers={false} -{ - "cognito:groups": [ - "amplify_group_1" - ], - "email_verified": true, - "iss": "...", - "cognito:username": "...", - "origin_jti": "...", - "amplfy_attribute": "amplify_gen_2", - "aud": "...", -} - -``` - ---- - ---- -title: "User attribute validation" -section: "build-a-backend/functions/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-04-30T20:16:55.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/user-attribute-validation/" ---- - -You can use `defineAuth` and `defineFunction` to create a [Cognito pre sign-up Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html) that extends the behavior of sign-up to validate attribute values. - -To get started, create a new directory and a resource file, `amplify/auth/pre-sign-up/resource.ts`. Then, define the function with `defineFunction`: - -```ts title="amplify/auth/pre-sign-up/resource.ts" -import { defineFunction } from '@aws-amplify/backend'; - -export const preSignUp = defineFunction({ - name: "pre-sign-up", - resourceGroupName: 'auth' -}); -``` - -Next, create the corresponding handler file, `amplify/auth/pre-sign-up/handler.ts`, file with the following contents: - -```ts title="amplify/auth/pre-sign-up/handler.ts" -import type { PreSignUpTriggerHandler } from "aws-lambda" - -function isOlderThan(date: Date, age: number) { - const comparison = new Date() - comparison.setFullYear(comparison.getFullYear() - age) - return date.getTime() <= comparison.getTime() -} - -export const handler: PreSignUpTriggerHandler = async (event) => { - const birthdate = new Date(event.request.userAttributes["birthdate"]) - - // you must be 13 years or older - if (!isOlderThan(birthdate, 13)) { - throw new Error("You must be 13 years or older to use this site") - } - - return event -} -``` - -Lastly, set the newly created function resource on your auth resource: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from '@aws-amplify/backend'; -import { preSignUp } from './pre-sign-up/resource'; - -export const auth = defineAuth({ - // ... - triggers: { - preSignUp - } -}); -``` - -After deploying the changes, whenever a user attempts to sign up this handler will verify the submitter's age is above 13 years. - ---- - ---- -title: "Custom message" -section: "build-a-backend/functions/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-04-09T18:10:02.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/custom-message/" ---- - -You can use `defineAuth` and `defineFunction` to create an [Amazon Cognito custom message AWS Lambda trigger](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-message.html) thats sends an custom email or phone verification message, or a multi-factor authentication (MFA) code. - -To get started, install `@types/aws-lambda` package that will be used to define the type of the handler: - -```bash title="Terminal" showLineNumbers={false} -npm add --save-dev @types/aws-lambda -``` - -Next, create a new directory and a resource file, `amplify/auth/custom-message/resource.ts`. Then, define the function with `defineFunction`: - -```ts title="amplify/auth/custom-message/resource.ts" -import { defineFunction } from '@aws-amplify/backend'; - -export const customMessage = defineFunction({ - name: "custom-message", - resourceGroupName: 'auth' -}); -``` - -Next, create the corresponding handler file, `amplify/auth/custom-message/handler.ts`, file with the following contents: - - -The input event for the `CustomMessage_AdminCreateUser` trigger source includes both a username and verification code. Admin-created users must receive both their username and code in order to sign in and thus you must include both the `usernameParameter` and `codeParameter` in your message template. - - -```ts title="amplify/auth/custom-message/handler.ts" -import type { CustomMessageTriggerHandler } from "aws-lambda"; - -export const handler: CustomMessageTriggerHandler = async (event) => { - if (event.triggerSource === "CustomMessage_ForgotPassword") { - const locale = event.request.userAttributes["locale"]; - if (locale === "en") { - event.response.emailMessage = `Your new one-time code is ${event.request.codeParameter}`; - event.response.emailSubject = "Reset my password"; - } else if (locale === "es") { - event.response.emailMessage = `Tu nuevo cΓ³digo de un solo uso es ${event.request.codeParameter}`; - event.response.emailSubject = "Restablecer mi contraseΓ±a"; - } - } - - if (event.triggerSource === "CustomMessage_AdminCreateUser") { - event.response.emailMessage = `Your username is ${event.request.usernameParameter} and your temporary password is ${event.request.codeParameter}`; - event.response.emailSubject = 'Welcome to Example App'; - } - - return event; -}; -``` - - -Lastly, set the newly created function resource on your auth resource: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from '@aws-amplify/backend'; -import { customMessage } from "./custom-message/resource"; - -export const auth = defineAuth({ - // ... - triggers: { - customMessage, - } -}); -``` - -After deploying the changes, whenever a user with user attribute `locale` set to `es` attempts to reset a password they will receive an email with a one-time code in Spanish. - ---- - ---- -title: "Google reCAPTCHA challenge" -section: "build-a-backend/functions/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-12-09T21:42:11.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/google-recaptcha-challenge/" ---- - -You can use `defineAuth` and `defineFunction` to create an auth experience that requires a [reCAPTCHA v3](https://developers.google.com/recaptcha/docs/v3) token. This can be accomplished by leveraging [Amazon Cognito's feature to define a custom auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#Custom-authentication-flow-and-challenges) and 3 triggers: - -1. [Create auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-create-auth-challenge.html) -2. [Define auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html) -3. [Verify auth challenge response](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-verify-auth-challenge-response.html) - -## Create auth challenge trigger - -To get started, create the first of the three triggers, `create-auth-challenge`. This is the trigger responsible for creating the reCAPTCHA challenge after a password is verified. - -```ts title="amplify/auth/create-auth-challenge/resource.ts" -import { defineFunction } from "@aws-amplify/backend" - -export const createAuthChallenge = defineFunction({ - name: "create-auth-challenge", - resourceGroupName: 'auth' -}) -``` - -After creating the resource file, create the handler with the following contents: - -```ts title="amplify/auth/create-auth-challenge/handler.ts" -import type { CreateAuthChallengeTriggerHandler } from "aws-lambda" - -export const handler: CreateAuthChallengeTriggerHandler = async (event) => { - const { request, response } = event - - if ( - // session will contain 3 "steps": SRP, password verification, custom challenge - request.session.length === 2 && - request.challengeName === "CUSTOM_CHALLENGE" - ) { - response.publicChallengeParameters = { trigger: "true" } - response.privateChallengeParameters = { answer: "" } - // optionally set challenge metadata - response.challengeMetadata = "CAPTCHA_CHALLENGE" - } - - return event -} -``` - -## Define auth challenge trigger - -Next, you will want to create the trigger responsible for _defining_ the auth challenge flow, `define-auth-challenge`. - -```ts title="amplify/auth/define-auth-challenge/resource.ts" -import { defineFunction } from "@aws-amplify/backend" - -export const defineAuthChallenge = defineFunction({ - name: "define-auth-challenge", - resourceGroupName: 'auth' -}) -``` - -After creating the resource file, create the handler with the following contents: - -```ts title="amplify/auth/define-auth-challenge/handler.ts" -import type { DefineAuthChallengeTriggerHandler } from "aws-lambda" - -export const handler: DefineAuthChallengeTriggerHandler = async (event) => { - const { response } = event - const [srp, password, captcha] = event.request.session - - // deny by default - response.issueTokens = false - response.failAuthentication = true - - if (srp?.challengeName === "SRP_A") { - response.failAuthentication = false - response.challengeName = "PASSWORD_VERIFIER" - } - - if ( - password?.challengeName === "PASSWORD_VERIFIER" && - password.challengeResult === true - ) { - response.failAuthentication = false - response.challengeName = "CUSTOM_CHALLENGE" - } - - if ( - captcha?.challengeName === "CUSTOM_CHALLENGE" && - // check for the challenge metadata set in "create-auth-challenge" - captcha?.challengeMetadata === "CAPTCHA_CHALLENGE" && - captcha.challengeResult === true - ) { - response.issueTokens = true - response.failAuthentication = false - } - - return event -} -``` - -## Verify auth challenge response trigger - -Lastly, create the trigger responsible for _verifying_ the challenge response, which in this case is the reCAPTCHA token verification. - -> **Info:** If you have not done so already, you will need to register your application and retrieve a reCAPTCHA secret key. This can then be configured for use with your cloud sandbox using: -> -> ```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox secret set GOOGLE_RECAPTCHA_SECRET_KEY -``` - -```ts title="amplify/auth/verify-auth-challenge-response/resource.ts" -import { defineFunction, secret } from "@aws-amplify/backend" - -export const verifyAuthChallengeResponse = defineFunction({ - name: "verify-auth-challenge-response", - environment: { - GOOGLE_RECAPTCHA_SECRET_KEY: secret("GOOGLE_RECAPTCHA_SECRET_KEY"), - }, - resourceGroupName: 'auth' -}) -``` - -After creating the resource file, create the handler with the following contents: - -```ts title="amplify/auth/verify-auth-challenge-response/handler.ts" -import type { VerifyAuthChallengeResponseTriggerHandler } from "aws-lambda" -import { env } from "$amplify/env/verify-auth-challenge-response" - -/** - * Google ReCAPTCHA verification response - * @see https://developers.google.com/recaptcha/docs/v3#site_verify_response - */ -type GoogleRecaptchaVerifyResponse = { - // whether this request was a valid reCAPTCHA token for your site - success: boolean - // the score for this request (0.0 - 1.0) - score: number - // the action name for this request (important to verify) - action: string - // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ) - challenge_ts: string - // the hostname of the site where the reCAPTCHA was solved - hostname: string - // optional error codes - "error-codes"?: unknown[] -} - -export const handler: VerifyAuthChallengeResponseTriggerHandler = async ( - event -) => { - if (!event.request.challengeAnswer) { - throw new Error("Missing challenge answer") - } - - // https://developers.google.com/recaptcha/docs/verify#api_request - const url = new URL("https://www.google.com/recaptcha/api/siteverify") - const params = new URLSearchParams({ - secret: env.GOOGLE_RECAPTCHA_SECRET_KEY, - response: event.request.challengeAnswer, - }) - url.search = params.toString() - - const request = new Request(url, { - method: "POST", - }) - - const response = await fetch(request) - const result = (await response.json()) as GoogleRecaptchaVerifyResponse - - if (!result.success) { - throw new Error("Verification failed", { cause: result["error-codes"] }) - } - - // indicate whether the answer is correct - event.response.answerCorrect = result.success - - return event -} -``` - -## Configure auth resource - -Finally, import and set the three triggers on your auth resource: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" -import { createAuthChallenge } from "./create-auth-challenge/resource" -import { defineAuthChallenge } from "./define-auth-challenge/resource" -import { verifyAuthChallengeResponse } from "./verify-auth-challenge-response/resource" - -/** - * Define and configure your auth resource - * @see https://docs.amplify.aws/gen2/build-a-backend/auth - */ -export const auth = defineAuth({ - loginWith: { - email: true, - }, - triggers: { - createAuthChallenge, - defineAuthChallenge, - verifyAuthChallengeResponse, - }, -}) -``` - ---- - ---- -title: "Amazon Kinesis Data Streams" -section: "build-a-backend/functions/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-01T20:22:18.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/kinesis-stream/" ---- - -With AWS Lambda, you can seamlessly integrate various event sources, such as Amazon Kinesis, Amazon SQS, and others, to trigger Lambda functions in response to real-time events. This feature enables you to build responsive, event-driven applications that react to changes in data or system state without the need for polling services. - -In this guide, let us configure a Lambda function with a Kinesis data stream as an event source. The Lambda function is automatically triggered whenever new data is published to the stream - whether you're processing streaming data, reacting to application events, or automating workflows. - -To get started, install the AWS Lambda Powertools Logger, which provides structured logging capabilities for your Lambda function, and the `aws-lambda` package, which is used to define the handler type. - -```bash title="Terminal" showLineNumbers={false} -npm add @aws-lambda-powertools/logger @types/aws-lambda -``` - -Second, create a new directory and a resource file, `amplify/functions/kinesis-function/resource.ts`. Then, define the function with `defineFunction`: - -```ts title="amplify/functions/kinesis-function/resource.ts" -import { defineFunction } from "@aws-amplify/backend"; - -export const myKinesisFunction = defineFunction({ - name: "kinesis-function", -}); -``` - -Third, create the corresponding handler file, `amplify/functions/kinesis-function/handler.ts`, file with the following contents: - -```ts title="amplify/functions/kinesis-function/handler.ts" -import type { - KinesisStreamBatchResponse, - KinesisStreamHandler, - KinesisStreamRecordPayload, -} from "aws-lambda"; -import { Buffer } from "node:buffer"; -import { Logger } from "@aws-lambda-powertools/logger"; - -const logger = new Logger({ - logLevel: "INFO", - serviceName: "kinesis-stream-handler", -}); - -export const handler: KinesisStreamHandler = async ( - event, - context -): Promise => { - for (const record of event.Records) { - try { - logger.info(`Processed Kinesis Event - EventID: ${record.eventID}`); - const recordData = await getRecordDataAsync(record.kinesis); - logger.info(`Record Data: ${recordData}`); - } catch (err) { - logger.error(`An error occurred ${err}`); - /* - When processing stream data, if any item fails, returning the failed item's position immediately - prompts Lambda to retry from this item forward, ensuring continuous processing without skipping data. - */ - return { - batchItemFailures: [{ itemIdentifier: record.kinesis.sequenceNumber }], - }; - } - } - logger.info(`Successfully processed ${event.Records.length} records.`); - return { batchItemFailures: [] }; -}; - -async function getRecordDataAsync( - payload: KinesisStreamRecordPayload -): Promise { - const data = Buffer.from(payload.data, "base64").toString("utf-8"); - await Promise.resolve(1); // Placeholder for an async process - return data; -} -``` - -Lastly, create the Kinesis stream and add it as a event source in the `amplify/backend.ts` file: - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend"; -import { Stream } from "aws-cdk-lib/aws-kinesis"; -import { StartingPosition } from "aws-cdk-lib/aws-lambda"; -import { KinesisEventSource } from "aws-cdk-lib/aws-lambda-event-sources"; -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; -import { myKinesisFunction } from "./functions/kinesis-function/resource"; - -const backend = defineBackend({ - auth, - data, - myKinesisFunction, -}); - -const kinesisStack = backend.createStack("kinesis-stack"); - -const kinesisStream = new Stream(kinesisStack, "KinesisStream", { - streamName: "myKinesisStream", - shardCount: 1, -}); - -const eventSource = new KinesisEventSource(kinesisStream, { - startingPosition: StartingPosition.LATEST, - reportBatchItemFailures: true, -}); - -backend.myKinesisFunction.resources.lambda.addEventSource(eventSource); -``` - -For examples on streaming analytics data to the Kinesis stream from your frontend, see the [Streaming analytics data](/[platform]/build-a-backend/add-aws-services/analytics/streaming-data/) documentation. - - ---- - ---- -title: "DynamoDB Streams" -section: "build-a-backend/functions/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-12-09T21:42:11.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/dynamo-db-stream/" ---- - -With AWS Lambda, you can seamlessly integrate various event sources, such as Amazon DynamoDB, Amazon SQS, and others, to trigger Lambda functions in response to real-time events. This feature enables you to build responsive, event-driven applications that react to changes in data or system state without the need for polling services. - -In this guide, lets configure a Lambda function with an Amazon DynamoDB stream as an event source. The Lambda function is automatically triggered whenever an item is added, updated, or deleted from the table, enabling you to build real-time applications that react to changes in your data. In this example, we will use a `Todo` table created by a data model on the GraphQL API. - -To get started, install the AWS Lambda Powertools Logger, which provides structured logging capabilities for your Lambda function, and the `aws-lambda` package, which is used to define the handler type. - -```bash title="Terminal" showLineNumbers={false} -npm add --save-dev @aws-lambda-powertools/logger @types/aws-lambda -``` - -Second, create a new directory and a resource file, `amplify/functions/dynamoDB-function/resource.ts`. Then, define the function with `defineFunction`: - -```ts title="amplify/functions/dynamoDB-function/resource.ts" -import { defineFunction } from "@aws-amplify/backend"; - -export const myDynamoDBFunction = defineFunction({ - name: "dynamoDB-function", - resourceGroupName: "data", -}); -``` - -Third, create the corresponding handler file, `amplify/functions/dynamoDB-function/handler.ts`, file with the following contents: - -```ts title="amplify/functions/dynamoDB-function/handler.ts" -import type { DynamoDBStreamHandler } from "aws-lambda"; -import { Logger } from "@aws-lambda-powertools/logger"; - -const logger = new Logger({ - logLevel: "INFO", - serviceName: "dynamodb-stream-handler", -}); - -export const handler: DynamoDBStreamHandler = async (event) => { - for (const record of event.Records) { - logger.info(`Processing record: ${record.eventID}`); - logger.info(`Event Type: ${record.eventName}`); - - if (record.eventName === "INSERT") { - // business logic to process new records - logger.info(`New Image: ${JSON.stringify(record.dynamodb?.NewImage)}`); - } - } - logger.info(`Successfully processed ${event.Records.length} records.`); - - return { - batchItemFailures: [], - }; -}; -``` - -Lastly, create DynamoDB table as event source in the `amplify/backend.ts` file: - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend"; -import { Stack } from "aws-cdk-lib"; -import { Policy, PolicyStatement, Effect } from "aws-cdk-lib/aws-iam"; -import { StartingPosition, EventSourceMapping } from "aws-cdk-lib/aws-lambda"; -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; -import { myDynamoDBFunction } from "./functions/dynamoDB-function/resource"; - -const backend = defineBackend({ - auth, - data, - myDynamoDBFunction, -}); - -const todoTable = backend.data.resources.tables["Todo"]; -const policy = new Policy( - Stack.of(todoTable), - "MyDynamoDBFunctionStreamingPolicy", - { - statements: [ - new PolicyStatement({ - effect: Effect.ALLOW, - actions: [ - "dynamodb:DescribeStream", - "dynamodb:GetRecords", - "dynamodb:GetShardIterator", - "dynamodb:ListStreams", - ], - resources: ["*"], - }), - ], - } -); -backend.myDynamoDBFunction.resources.lambda.role?.attachInlinePolicy(policy); - -const mapping = new EventSourceMapping( - Stack.of(todoTable), - "MyDynamoDBFunctionTodoEventStreamMapping", - { - target: backend.myDynamoDBFunction.resources.lambda, - eventSourceArn: todoTable.tableStreamArn, - startingPosition: StartingPosition.LATEST, - } -); - -mapping.node.addDependency(policy); -``` - ---- - ---- -title: "S3 Upload confirmation" -section: "build-a-backend/functions/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-12-09T21:42:11.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/s3-upload-confirmation/" ---- - -You can use `defineStorage` and `defineFunction` to create a function trigger to confirm uploading a file. - -To get started, install the `@types/aws-lambda` [package](https://www.npmjs.com/package/@types/aws-lambda), which contains types for different kinds of Lambda handlers, events, and responses. - -```bash title="Terminal" showLineNumbers={false} -npm add --save @types/aws-lambda -``` - -Update your storage definition to define the onUpload trigger as below: - -```ts title="amplify/storage/resource.ts" -import { defineFunction, defineStorage } from "@aws-amplify/backend"; - -export const storage = defineStorage({ - name: 'myProjectFiles', - triggers: { - onUpload: defineFunction({ - entry: './on-upload-handler.ts' - resourceGroupName: 'storage', - }) - } -}); -``` - -Next, create a file named `amplify/storage/on-upload-handler.ts` and use the following code to log the object keys whenever an object is uploaded to the bucket. You can add your custom logic to this function as needed. - -```ts title="amplify/storage/on-upload-handler.ts" -import type { S3Handler } from 'aws-lambda'; - -export const handler: S3Handler = async (event) => { - const objectKeys = event.Records.map((record) => record.s3.object.key); - console.log(`Upload handler invoked for objects [${objectKeys.join(', ')}]`); -}; -``` - -Now, when you deploy your backend, this function will be invoked whenever an object is uploaded to the bucket. - ---- - ---- -title: "Custom Auth Challenge" -section: "build-a-backend/functions/examples" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-12-09T21:42:11.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/examples/custom-auth-flows/" ---- - -Secure Remote Password (SRP) is a cryptographic protocol enabling password-based authentication without transmitting the password over the network. In Amazon Cognito custom authentication flows, CUSTOM_WITH_SRP incorporates SRP steps for enhanced security, while CUSTOM_WITHOUT_SRP bypasses these for a simpler process. The choice between them depends on your application's security needs and performance requirements. -This guide demonstrates how to implement both types of custom authentication flows using AWS Amplify with Lambda triggers. - -You can use `defineAuth` and `defineFunction` to create an auth experience that uses `CUSTOM_WITH_SRP` and `CUSTOM_WITHOUT_SRP`. This can be accomplished by leveraging [Amazon Cognito's feature to define a custom auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#Custom-authentication-flow-and-challenges) and 3 triggers: - -1. [Create auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-create-auth-challenge.html) -2. [Define auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html) -3. [Verify auth challenge response](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-verify-auth-challenge-response.html) - -To get started, install the `aws-lambda` package, which is used to define the handler type. - -```bash title="Terminal" showLineNumbers={false} -npm add --save-dev @types/aws-lambda -``` - -## Create auth challenge trigger - -To get started, create the first of the three triggers, `create-auth-challenge`. This is the trigger responsible for creating the reCAPTCHA challenge after a password is verified. - -```ts title="amplify/auth/create-auth-challenge/resource.ts" -import { defineFunction } from "@aws-amplify/backend" - -export const createAuthChallenge = defineFunction({ - name: "create-auth-challenge", - resourceGroupName: 'auth' -}) -``` - -After creating the resource file, create the handler with the following contents: - -```ts title="amplify/auth/create-auth-challenge/handler.ts" -import type { CreateAuthChallengeTriggerHandler } from "aws-lambda"; - -export const handler: CreateAuthChallengeTriggerHandler = async (event) => { - if (event.request.challengeName === "CUSTOM_CHALLENGE") { - // Generate a random code for the custom challenge - const challengeCode = "123456"; - - event.response.challengeMetadata = "TOKEN_CHECK"; - - event.response.publicChallengeParameters = { - trigger: "true", - code: challengeCode, - }; - - event.response.privateChallengeParameters = { trigger: "true" }; - event.response.privateChallengeParameters.answer = challengeCode; - } - return event; -}; -``` - -## Define auth challenge trigger - -Next, you will want to create the trigger responsible for _defining_ the auth challenge flow, `define-auth-challenge`. - -```ts title="amplify/auth/define-auth-challenge/resource.ts" -import { defineFunction } from "@aws-amplify/backend" - -export const defineAuthChallenge = defineFunction({ - name: "define-auth-challenge", - resourceGroupName: 'auth' -}) -``` - -After creating the resource file, create the handler with the following contents if you are using `CUSTOM_WITHOUT_SRP`: - -```ts title="amplify/auth/define-auth-challenge/handler.ts" -import type { DefineAuthChallengeTriggerHandler } from "aws-lambda" - -export const handler: DefineAuthChallengeTriggerHandler = async (event) => { - // Check if this is the first authentication attempt - if (event.request.session.length === 0) { - // For the first attempt, we start with the custom challenge - event.response.issueTokens = false; - event.response.failAuthentication = false; - event.response.challengeName = "CUSTOM_CHALLENGE"; - } else if ( - event.request.session.length === 1 && - event.request.session[0].challengeName === "CUSTOM_CHALLENGE" && - event.request.session[0].challengeResult === true - ) { - // If this is the second attempt (session length 1), - // it was a CUSTOM_CHALLENGE, and the result was successful - event.response.issueTokens = true; - event.response.failAuthentication = false; - } else { - // If we reach here, it means either: - // 1. The custom challenge failed - // 2. We've gone through more attempts than expected - // In either case, we fail the authentication - event.response.issueTokens = false; - event.response.failAuthentication = true; - } - - return event; -}; -``` - -Or if you are using `CUSTOM_WITH_SRP`: - -```ts title="amplify/auth/define-auth-challenge/handler.ts" -import type { DefineAuthChallengeTriggerHandler } from "aws-lambda" - -export const handler: DefineAuthChallengeTriggerHandler = async (event) => { - // First attempt: Start with SRP_A (Secure Remote Password protocol, step A) - if (event.request.session.length === 0) { - event.response.issueTokens = false; - event.response.failAuthentication = false; - event.response.challengeName = "SRP_A"; - } else if ( - event.request.session.length === 1 && - event.request.session[0].challengeName === "SRP_A" && - event.request.session[0].challengeResult === true - ) { - // Second attempt: SRP_A was successful, move to PASSWORD_VERIFIER - event.response.issueTokens = false; - event.response.failAuthentication = false; - event.response.challengeName = "PASSWORD_VERIFIER"; - } else if ( - event.request.session.length === 2 && - event.request.session[1].challengeName === "PASSWORD_VERIFIER" && - event.request.session[1].challengeResult === true - ) { - // Third attempt: PASSWORD_VERIFIER was successful, move to CUSTOM_CHALLENGE - event.response.issueTokens = false; - event.response.failAuthentication = false; - event.response.challengeName = "CUSTOM_CHALLENGE"; - } else if ( - event.request.session.length === 3 && - event.request.session[2].challengeName === "CUSTOM_CHALLENGE" && - event.request.session[2].challengeResult === true - ) { - // Fourth attempt: CUSTOM_CHALLENGE was successful, authentication complete - event.response.issueTokens = true; - event.response.failAuthentication = false; - } else { - // If we reach here, it means one of the challenges failed or - // we've gone through more attempts than expected - event.response.issueTokens = false; - event.response.failAuthentication = true; - } - - return event; -}; -``` - -## Verify auth challenge response trigger - -Lastly, create the trigger responsible for _verifying_ the challenge response. For the purpose of this example, the verification check will always return true. - -```ts title="amplify/auth/verify-auth-challenge-response/resource.ts" -import { defineFunction, secret } from "@aws-amplify/backend" - -export const verifyAuthChallengeResponse = defineFunction({ - name: "verify-auth-challenge-response", - resourceGroupName: 'auth' -}) -``` - -After creating the resource file, create the handler with the following contents: - -```ts title="amplify/auth/verify-auth-challenge-response/handler.ts" -import type { VerifyAuthChallengeResponseTriggerHandler } from "aws-lambda" - -export const handler: VerifyAuthChallengeResponseTriggerHandler = async ( - event -) => { - event.response.answerCorrect = true; - return event; -}; - -``` - -## Configure auth resource - -Finally, import and set the three triggers on your auth resource: - -```ts title="amplify/auth/resource.ts" -import { defineAuth } from "@aws-amplify/backend" -import { createAuthChallenge } from "./create-auth-challenge/resource" -import { defineAuthChallenge } from "./define-auth-challenge/resource" -import { verifyAuthChallengeResponse } from "./verify-auth-challenge-response/resource" - -/** - * Define and configure your auth resource - * @see https://docs.amplify.aws/gen2/build-a-backend/auth - */ -export const auth = defineAuth({ - loginWith: { - email: true, - }, - triggers: { - createAuthChallenge, - defineAuthChallenge, - verifyAuthChallengeResponse, - }, -}) -``` - -After deploying the changes, whenever a user attempts to sign in with `CUSTOM_WITH_SRP` or `CUSTOM_WITHOUT_SRP`, the Lambda challenges will be triggered. - ---- - ---- -title: "Modify Amplify-generated Lambda resources with CDK" -section: "build-a-backend/functions" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-05-16T15:59:30.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/modify-resources-with-cdk/" ---- - -Amplify Functions utilize the [`NodejsFunction`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html) construct from the [AWS Cloud Development Kit (CDK)](https://aws.amazon.com/cdk/). The underlying resources can be modified, overridden, or extended using CDK after setting the resource on your backend. - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend'; -import { myFunction } from './functions/my-function'; - -const backend = defineBackend({ - myFunction -}) - -// CDK constructs can be accessed via -backend.myFunction.resources - -// where the Lambda function can be found on -backend.myFunction.resources.lambda -``` - -The Lambda resource available is a representation of [`IFunction`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.IFunction.html). - -## Adding IAM Policies - -To learn how to add IAM policies to a Function's execution role, visit the [documentation for granting access to other resources](/[platform]/build-a-backend/functions/grant-access-to-other-resources#using-cdk). - ---- - ---- -title: "Custom functions" -section: "build-a-backend/functions" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2025-01-24T20:19:47.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/functions/custom-functions/" ---- - -AWS Amplify Gen 2 functions are AWS Lambda functions that can be used to perform tasks and customize workflows in your Amplify app. Functions can be written in Node.js, Python, Go, or any [other language supported by AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html). - -> **Warning:** **Note:** [Fullstack Git-based environments](https://docs.amplify.aws/react/how-amplify-works/concepts/#fullstack-git-based-environments) do not support Docker for functions bundling out of the box. To learn more [skip to the Docker section](#docker). - -> **Info:** **Note:** The following options in `defineFunction` are not supported for Custom Functions: -> - [Environment variables and secrets](/[platform]/build-a-backend/functions/environment-variables-and-secrets/) -> - [Scheduling configuration](/[platform]/build-a-backend/functions/scheduling-functions/) -> - [Lambda layers](/[platform]/build-a-backend/functions/add-lambda-layers/) -> - [Function options](/[platform]/build-a-backend/functions/configure-functions/) -> -> You'll need to configure these options directly in your CDK Function definition instead. However, `resourceGroupName` property is supported and can be used to group related resources together in your `defineFunction` definition. - -In this guide, you will learn how to create Python and Go functions with Amplify functions. The examples shown in this guide do not use Docker to build functions. Instead, the examples use commands that run on your host system to build, and as such require the necessary tooling for the language you are using for your functions. - -## Python - -To get started, create a new directory and a resource file, `amplify/functions/say-hello/resource.ts`. Then, define the function with `defineFunction`: -```ts title="amplify/functions/say-hello/resource.ts" -import { execSync } from "node:child_process"; -import * as path from "node:path"; -import { fileURLToPath } from "node:url"; -import { defineFunction } from "@aws-amplify/backend"; -import { DockerImage, Duration } from "aws-cdk-lib"; -import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda"; - -const functionDir = path.dirname(fileURLToPath(import.meta.url)); - -export const sayHelloFunctionHandler = defineFunction( - (scope) => - new Function(scope, "say-hello", { - handler: "index.handler", - runtime: Runtime.PYTHON_3_9, // or any other python version - timeout: Duration.seconds(20), // default is 3 seconds - code: Code.fromAsset(functionDir, { - bundling: { - image: DockerImage.fromRegistry("dummy"), // replace with desired image from AWS ECR Public Gallery - local: { - tryBundle(outputDir: string) { - execSync( - `python3 -m pip install -r ${path.join(functionDir, "requirements.txt")} -t ${path.join(outputDir)} --platform manylinux2014_x86_64 --only-binary=:all:` - ); - execSync(`cp -r ${functionDir}/* ${path.join(outputDir)}`); - return true; - }, - }, - }, - }), - }), - { - resourceGroupName: "auth" // Optional: Groups this function with auth resource - } -); -``` - -Next, create the corresponding handler file at `amplify/functions/say-hello/index.py`. This is where your function code will go. - -```ts title="amplify/functions/say-hello/index.py" -import json - -def handler(event, context): - return { - "statusCode": 200, - "body": json.dumps({ - "message": "Hello World", - }), - } -``` - -The handler file _must_ export a function named "handler". This is the entry point to your function. For more information on writing functions, refer to the [AWS documentation for Lambda function handlers using Python](https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html). - -If you need Python packages, you can add them to a `requirements.txt` file in the same directory as your handler file. The `bundling` option in the `Code.fromAsset` method will install these packages for you. -Create a `requirements.txt` file in the same directory as your handler file. This file should contain the names of the packages you want to install. For example: - -```txt title="amplify/functions/say-hello/requirements.txt" -request==2.25.1 -some-other-package>=1.0.0 -``` - -You're now ready to deploy your python function. Next is the same process as the Node.js/TypeScript function. Go to [Common steps for all languages](#common-steps-for-all-languages) to continue. - -## Go -To get started, Create a new directory and a resource file, `amplify/functions/say-hello/resource.ts`. Then, define the function with `defineFunction`: - -```ts title="amplify/functions/say-hello/resource.ts" -import { execSync } from "node:child_process"; -import * as path from "node:path"; -import { fileURLToPath } from "node:url"; -import { defineFunction } from "@aws-amplify/backend"; -import { DockerImage, Duration } from "aws-cdk-lib"; -import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda"; - -const functionDir = path.dirname(fileURLToPath(import.meta.url)); - -export const sayHelloFunctionHandler = defineFunction( - (scope) => - new Function(scope, "say-hello", { - handler: "bootstrap", - runtime: Runtime.PROVIDED_AL2023, - timeout: Duration.seconds(3), // default is 3 seconds - code: Code.fromAsset(functionDir, { - bundling: { - image: DockerImage.fromRegistry("dummy"), - local: { - tryBundle(outputDir: string) { - execSync(`rsync -rLv ${functionDir}/* ${path.join(outputDir)}`); - execSync( - `cd ${path.join(outputDir)} && GOARCH=amd64 GOOS=linux go build -tags lambda.norpc -o ${path.join(outputDir)}/bootstrap ${functionDir}/main.go` - ); - return true; - }, - }, - }, - }), - }), - { - resourceGroupName: "auth" // Optional: Groups this function with auth resource - } -); -``` - -Next, create the corresponding handler file at `amplify/functions/say-hello/main.go`. This is where your function code will go. - -```go title="amplify/functions/say-hello/main.go" -package main - -import ( - "context" - "fmt" - - "github.com/aws/aws-lambda-go/lambda" -) - -type Event struct { - Arguments Arguments `json:"arguments"` -} - -type Arguments struct { - Title string `json:"phone"` - Msg string `json:"msg"` -} - -func HandleRequest(ctx context.Context, event Event) (string, error) { - fmt.Println("Received event: ", event) - - // fmt.Println("Message sent to: ", event.Arguments.Msg) - // You can use lambda arguments in your code - - return "Hello World!", nil -} - -func main() { - lambda.Start(HandleRequest) -} -``` - -Then you should run the following command to build the go function: -```bash title="terminal" showLineNumbers={false} -go mod init lambda -``` -then run to install the dependencies. - -```bash title="terminal" showLineNumbers={false} -go mod tidy -``` - -You're now ready to deploy your golang function. Next is the same process as the Node.js/TypeScript function. - -## Common steps for all languages - -Regardless of the language used, your function needs to be added to your backend. -```ts title="amplify/backend.ts" -// highlight-next-line -import { sayHelloFunctionHandler } from './functions/say-hello/resource'; - -defineBackend({ - // highlight-next-line - sayHelloFunctionHandler, -}); -``` - -Now when you run `npx ampx sandbox` or deploy your app on Amplify, it will include your function. - -To invoke your function, we recommend adding your [function as a handler for a custom query with your Amplify Data resource](/[platform]/build-a-backend/data/custom-business-logic/). To get started, open your `amplify/data/resource.ts` file and specify a new query in your schema: - -```ts title="amplify/data/resource.ts" -import { sayHelloFunctionHandler } from "../functions/say-hello/resource" - -const schema = a.schema({ - // highlight-start - sayHello: a - .query() - .arguments({ - name: a.string(), - }) - .returns(a.string()) - .handler(a.handler.function(sayHelloFunctionHandler)), - // highlight-end -}) - -export type Schema = ClientSchema - -export const data = defineData({ - schema, - authorizationModes: { - defaultAuthorizationMode: "iam", - }, -}) -``` - -## Docker - -Custom function may require [Docker](https://www.docker.com/) in order to build and bundle function's code. A deployment failing with `CustomFunctionProviderDockerError` error indicates that a custom function requires Docker but the Docker daemon was not found. In that case you need to provide a working Docker installation at runtime. - -### Personal sandboxes - -Ensure that Docker is installed on your computer and that Docker daemon is running. You can check if Docker daemon is running using the following command: -```bash title="terminal" showLineNumbers={false} -docker info -``` - -### Fullstack Git-based environments - -Amplify does not provide Docker daemon out of the box in branch deployments. However, you have an option to provide [your own image that meets Amplify requirements](https://docs.aws.amazon.com/amplify/latest/userguide/custom-build-image.html) and includes a Docker installation. - -For example, the `aws/codebuild/amazonlinux-x86_64-standard:5.0` image ([see definition](https://github.com/aws/aws-codebuild-docker-images)) meets Amplify requirements and includes Docker installation. - ---- - ---- -title: "Server-Side Rendering" -section: "build-a-backend" -platforms: ["angular", "javascript", "nextjs", "react", "react-native", "vue"] -gen: 2 -last-updated: "2025-04-11T22:29:14.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/server-side-rendering/" ---- - -This guide walks through how to use Amplify Auth and Data APIs from Next.js server-side runtimes. - - -Before you begin: - -- [Follow the Next.js App Router tutorial](/[platform]/build-a-backend/server-side-rendering/nextjs-app-router-server-components) - - -## Install the Amplify Next.js adapter - -> **Warning:** **Note:** Amplify JS v6 supports Next.js with the version range: `>=13.5.0 <16.0.0`. -> Ensure you have the correct version to integrate with Amplify. - -To use Amplify APIs server-side, you need to install the Amplify Next.js adapter in addition to the Amplify libraries: - -```bash title="Terminal" showLineNumbers={false} -npm add aws-amplify @aws-amplify/adapter-nextjs -``` - -## Configure Amplify in Next.js - -#### [Configure Amplify for server-side usage] - -You will need to create a `runWithAmplifyServerContext` function to use Amplify APIs on the server-side of your Next.js app. - -You can create an `amplifyServerUtils.ts` file under a `utils` folder in your codebase. In this file, you will import the Amplify backend outputs from the `amplify_outputs.json` file that is generated by the Amplify CLI, and use the `createServerRunner` function to create the `runWithAmplifyServerContext` function. - -For example, the `utils/amplifyServerUtils.ts` file may contain the following content: - -```typescript title="src/utils/amplifyServerUtils.ts" -import { createServerRunner } from '@aws-amplify/adapter-nextjs'; -import outputs from '@/amplify_outputs.json'; - -export const { runWithAmplifyServerContext } = createServerRunner({ - config: outputs -}); -``` - -You can use the exported `runWithAmplifyServerContext` function to call Amplify APIs within isolated request contexts. You can review examples under the [Calling Amplify category APIs on the server side](#calling-amplify-category-apis-on-the-server-side) section. - - -**Tip:** You only need to call the `createServerRunner` function once and reuse the `runWithAmplifyServerContext` function throughout. - - -#### [Configure Amplify for client-side usage] - - -**Tip**: You only need do this step if you are using Amplify APIs on the client side of your Next.js app, for example, calling Amplify Auth `signIn` API to sign in a user, or use GraphQL subscriptions on the client side. - - -When you use the Amplify library on the client-side of your Next.js app, you will need to configure Amplify by calling the `Amplify.configure` as you would to use Amplify in a single-page application. - - - -**Note:** To use the Amplify library on the client side in a Next.js app, you will need to set `ssr` to `true` when calling `Amplify.configure`. This instructs the Amplify library to store tokens in the cookie store of a browser. Cookies will be sent along with requests to your Next.js server for authentication. - - - -```typescript -'use client'; - -import outputs from '@/amplify_outputs.json'; -import { Amplify } from 'aws-amplify'; - -Amplify.configure(outputs, { - ssr: true // required when using Amplify with Next.js -}); - -export default function RootLayoutThatConfiguresAmplifyOnTheClient({ - children -}: { - children: React.ReactNode; -}) { - return children; -} -``` - -> **Warning:** Make sure you call `Amplify.configure` as early as possible in your application’s life-cycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. Review the [Library Not Configured Troubleshooting guide](/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/) for possible causes of this issue. - - - -To avoid repetitive calls to `Amplify.configure`, you can call it once in a top-level client-side rendered layout component. - - - - - -If you're using the Next.js App Router, you can create a client component to configure Amplify and import it into your root layout. - -`ConfigureAmplifyClientSide.ts`: - -```tsx title="src/components/ConfigureAmplifyClientSide.tsx" -'use client'; - -import { Amplify } from 'aws-amplify'; -import outputs from '../amplify_outputs.json'; - -Amplify.configure(outputs, { ssr: true }); - -export default function ConfigureAmplifyClientSide() { - return null; -} -``` - -`layout.tsx`: - -```tsx title="src/app/layout.tsx" -import ConfigureAmplifyClientSide from '@/components/ConfigureAmplifyClientSide'; -import './globals.css'; - -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -}; - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - - <> - - {children} - - - - ); -} -``` - - - -## Authentication with Next.js server-side runtime - -### (Experimental) Perform authentication on the server side and enable HttpOnly cookies - -> **Warning:** **Warning:** This feature is experimental and may change in future releases. -> -> Once you enable the server-side sign-in feature, auth tokens are stored in HttpOnly cookies and you may not change the HttpOnly attribute. Since these cookies are inaccessible from client-side scripts, you won’t be able to use any Amplify JS APIs on the client side. Therefore, you don’t need to configure Amplify on the client side. You can keep using [these Amplify JS server-side APIs](/[platform]/build-a-backend/server-side-rendering/#supported-apis-for-nextjs-server-side-usage) on the server side. - -Additional setup is required to enable server-side authentication flows in your Next.js app. - -#### Step 1 - Specify the origin of your app in environment variables - -Add the following environment variable to your Next.js app. For example in a `.env` file: - -```shell title=".env" showLineNumbers={false} -AMPLIFY_APP_ORIGIN=https://myapp.com -``` - -Ensure this environment variable is accessible in your Next.js app's server runtime. - -> **Info:** **Note:** Token cookies are transmitted via server-side authentication flows. In production environments, it is recommended to use HTTPS as the origin for enhanced security. - -#### Step 2 - Export the `createAuthRouteHandlers` function - -The `createAuthRouteHandlers` function is created by the `createServerRunner` function call when you configure Amplify for server-side usage. You can export this function from your `amplifyServerUtils.ts` file. You can also configure cookie attributes with the `runtimeOptions` parameter. - -```typescript title="src/utils/amplifyServerUtils.ts" -import { createServerRunner } from '@aws-amplify/adapter-nextjs'; -import outputs from '@/amplify_outputs.json'; - -export const { - runWithAmplifyServerContext, - // highlight-start - createAuthRouteHandlers, - // highlight-end -} = createServerRunner({ - config: outputs, - // highlight-start - runtimeOptions: { - cookies: { - domain: '.myapp.com', // making cookies available to all subdomains - sameSite: 'strict', - maxAge: 60 * 60 * 24 * 7 // 7 days - } - } - // highlight-end -}); -``` - -#### Step 3 - Set up the Auth API routes - -Create an API route using the `createAuthRouteHandlers` function. For example: - -#### [App router] -```typescript title="src/app/api/auth/[slug]/route.ts" -import { createAuthRouteHandlers } from "@/utils/amplifyServerUtils"; - -export const GET = createAuthRouteHandlers({ - redirectOnSignInComplete: "/home", - redirectOnSignOutComplete: "/sign-in", -}); -``` - -#### [Pages router] -```typescript title="src/pages/api/auth/[slug].ts" -import { createAuthRouteHandlers } from "@/utils/amplifyServerUtils"; - -export default createAuthRouteHandlers({ - redirectOnSignInComplete: "/home", - redirectOnSignOutComplete: "/sign-in", -}); -``` - -With the above example, Amplify generates the following API routes: - -| API Routes | What it does | -| --------------------------------------------------- | ------------------------------------------------------------ | -| `/api/auth/sign-up` | Upon navigating an end user to this route, they’ll be redirected to the Amazon Cognito Managed Login sign-up form. After sign-up and sign-in, they’ll be redirected back to the route `/api/auth/sign-in-callback`. | -| `/api/auth/sign-in` | Upon navigating an end user to this route, they’ll be redirected to the Amazon Cognito Managed Login sign-in form. After sign-in, they’ll be redirected back to the route `/api/auth/sign-in-callback`. | -| `/api/auth/sign-in?provider=` | Upon navigating an end user to this route, they’ll be redirected first to the Amazon Cognito Managed Login and then the specified social provider sign-in page. After sign-in, they’ll be redirected back to the route `/api/auth/sign-in-callback`. | -| `/api/auth/sign-out` | Upon navigating an end user to this route, the end user will be signed out and redirected to the route `/api/auth/sign-out-callback`. | -| `/api/auth/sign-in-callback` | Amazon Cognito Managed Login redirects an end user back to this route after signing in. Amplify exchanges auth tokens and stores them as HttpOnly cookies in the browser cookie store, then redirects the end user back to the route specified by the `redirectOnSignInComplete` parameter. | -| `/api/auth/sign-out-callback` | Amazon Cognito Managed Login redirects an end user back to this route after signing out, Amplify revokes access token and refresh token and removes token cookies from browser cookie store, then redirects the end user back to the route specified by the `redirectOnSignOutComplete` parameter. | - -To customize the language of the Amazon Cognito Managed Login pages, you can add the `lang` query parameter to the `/api/auth/sign-in` and `/api/auth/sign-up` routes. For example, `/api/auth/sign-in?lang=fr`. Refer to the [Managed login localization documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-managed-login.html#managed-login-localization) for more information on the supported languages. - -> **Info:** **Note:** A signing-out call involves multiple steps, including signing out from Amazon Cognito Managed Login, revoking tokens, and removing cookies. If the user closes the browser during the process, the following may occur: -> -> 1. auth token have not been revoked - user remains signed in -> 2. auth token have been revoked but cookies have not been removed - cookies will be removed when the user visits the app again - -#### Step 4 - Provide the redirect URLs to the Auth Resource in Amplify - -You can provide the callback API routes as the redirect URLs in the Auth resource configuration. For example: - -```ts title="amplify/auth/resource.ts" -export const auth = defineAuth({ - loginWith: { - email: true, - // highlight-start - externalProviders: { - callbackUrls: ["https://myapp.com/api/auth/sign-in-callback"], - logoutUrls: ["https://myapp.com/api/auth/sign-out-callback"], - }, - // highlight-end - }, -}); -``` - -This enables Amazon Cognito Hosted UI to support the server-side authentication flows. You may upgrade to the latest Amazon Cognito Managed Login Branding to customize the sign-in and sign-up pages. See [Amazon Cognito user pool managed login](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-managed-login.html) for more information. - -#### Step 5 - Use Anchor link for initiating server-side authentication flows - -Use HTML anchor links to navigate users to the sign-in and sign-up routes. For example: - -#### [Sign in button] -```tsx title="src/components/SignInButton.tsx" -export default function SignInButton() { - return ( - - Sign In - - ); -} -``` - -#### [Sign in with Google button] -```tsx title="src/components/SignInWithGoogleButton.tsx" -export default function SignInWithGoogleButton() { - return ( - - Sign In with Google - - ); -} -``` - -#### [Sign up button] -```tsx title="src/components/SignUpButton.tsx" -export default function SignUpButton() { - return ( - - Sign Up - - ); -} -``` - -#### [Sign out button] -```tsx title="src/components/SignOutButton.tsx" -export default function SignOutButton() { - return ( - - Sign Out - - ); -} -``` - -When an end user clicks on the buttons above, a corresponding server-side authentication flow will be initiated. - -### Validate user session with the Next.js Middleware - -You can use the `fetchAuthSession` API to check the auth sessions that are attached to the incoming requests in the middleware of your Next.js app to protect your routes. For example: - -```typescript title="src/middleware.ts" -import { fetchAuthSession } from 'aws-amplify/auth/server'; -import { NextRequest, NextResponse } from 'next/server'; -import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils'; - -export async function middleware(request: NextRequest) { - const response = NextResponse.next(); - - const authenticated = await runWithAmplifyServerContext({ - nextServerContext: { request, response }, - operation: async (contextSpec) => { - try { - const session = await fetchAuthSession(contextSpec); - return ( - session.tokens?.accessToken !== undefined && - session.tokens?.idToken !== undefined - ); - } catch (error) { - console.log(error); - return false; - } - } - }); - - if (authenticated) { - return response; - } - - return NextResponse.redirect(new URL('/sign-in', request.url)); -} - -export const config = { - matcher: [ - /* - * Match all request paths except for the ones starting with: - * - api (API routes) - * - _next/static (static files) - * - _next/image (image optimization files) - * - favicon.ico (favicon file) - */ - '/((?!api|_next/static|_next/image|favicon.ico|sign-in).*)' - ] -}; -``` - -In this example, if the incoming request is not associated with a valid user session the request will be redirected to the `/sign-in` route. - - - -**Note:** When calling `fetchAuthSession` with a `response` context, it will send the refreshed tokens (if any) back to the client via the `Set-Cookie` header in the response. - - - -## Calling Amplify category APIs on the server side - -For the **Auth** categories to use Amplify APIs on the server in your Next.js app, you will need to: - -1. Import the API from the `/server` sub path. -2. Use the `runWithAmplifyServerContext` helper function created by calling the `createServerRunner` function exported from `@aws-amplify/adapter-nextjs` to call the Amplify API in an isolated server context. - -For the **GraphQL API** category, review [Connect to data from Server-side Runtimes](/[platform]/build-a-backend/data/connect-from-server-runtime/). - - - -**Note:** A subset of Amplify APIs can now be called on the server side of a Next.js app. These APIs are exported from the `/server` sub paths. See [the full list](#supported-apis-for-nextjs-server-side-usage) of supported APIs. - - - - - -**Note:** If you use the Amplify server-side APIs in a [server action](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) and encounter the following error running `next build`: - -> ./node_modules/@aws-amplify/core/node_modules/@aws-crypto/sha256-js/build/module/index.js + 12 modules - -> Cannot get final name for export 'fromUtf8' of ./node_modules/@smithy/util-utf8/dist-es/index.js - -You can add the following to your `next.config.js`: - -```ts title="next.config.js" -/** @type {import('next').NextConfig} */ -const nextConfig = { - // highlight-start - serverComponentsPackages: ['@aws-crypto'], - // highlight-end -}; -``` - -See Next.js documentation on [`serverComponentsPackages`](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages) for more details. - - -### With Next.js App Router - -#### Dynamic rendering in React server component - -Dynamic rendering is based on a user session extracted from an incoming request. - -```jsx -import { cookies } from 'next/headers'; -import { getCurrentUser } from 'aws-amplify/auth/server'; -import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils'; - -// This page always dynamically renders per request -export const dynamic = 'force-dynamic'; - -export default async function AuthGetCurrentUserServer() { - try { - const currentUser = await runWithAmplifyServerContext({ - nextServerContext: { cookies }, - operation: (contextSpec) => getCurrentUser(contextSpec) - }); - - return ( -

    {`Hello, ${currentUser.username}`}

    - ); - } catch (error) { - console.error(error); - return

    Something went wrong...

    ; - } -} -``` - -#### Static rendering in React server component - -Static rendering does not require a user session, so you can specify the `nextServerContext` parameter as `null`. This is useful for some use cases; for example, when you are using the Storage API with guest access (if you have enabled it in your backend). - -```jsx -import { getUrl } from 'aws-amplify/storage/server'; -import Image from 'next/image'; -import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils'; - -// Re-render this page every 60 minutes -export const revalidate = 60 * 60; // in seconds - -export default async function StaticallyRenderedPage() { - try { - const splashUrl = await runWithAmplifyServerContext({ - nextServerContext: null, - operation: (contextSpec) => - getUrl(contextSpec, { - key: 'splash.png' - }) - }); - - return ( - Splash Image - ); - } catch (error) { - console.error(error); - return

    Something went wrong...

    ; - } -} -``` - - - -**Note:** The URL returned by the `getUrl` API expires in the above example. You may want to specify the `revalidate` parameter to rerender the page as required to ensure the URL gets regenerated. - - - -#### In Route Handlers - -In route handlers require implementing an API route that enables `GET /apis/get-current-user`. - -```typescript -import { getCurrentUser } from 'aws-amplify/auth/server'; -import { cookies } from 'next/headers'; -import { NextResponse } from 'next/server'; -import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils'; - -export async function GET() { - const user = await runWithAmplifyServerContext({ - nextServerContext: { cookies }, - operation: (contextSpec) => getCurrentUser(contextSpec) - }); - - return NextResponse.json({ user }); -} -``` - -When you call `fetch('/apis/get-current-user')` it returns a payload that contains the `user` data for the current signed-in user. - -### With Next.js Pages Router - -#### In `getServerSideProps` - -The following example extracts current user data from the request and provides them to a page react component via its props. - -```typescript -export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { - const currentUser = await runWithAmplifyServerContext({ - nextServerContext: { request: req, response: res }, - operation: (contextSpec) => getCurrentUser(contextSpec) - }); - - return { props: { currentUser } }; -}; -``` - -#### In `getStaticProps` - -Similar to static rendering with the App Router, you can pass `null` as the value of the `nextServerContext` parameter to use the Amplify Storage API with guest access. - -```typescript -export async function getStaticProps() { - const splashUrl = await runWithAmplifyServerContext({ - nextServerContext: null, - operation: (contextSpec) => getUrl(contextSpec, { key: 'splash.png' }) - }); - - return { - props: { imageUrl: splashUrl.url.toString() }, - revalidate: (splashUrl.expiresAt.getTime() - Date.now()) / 1000 // in seconds - }; -} -``` - -## Supported APIs for Next.js server-side usage - -All APIs that support use on the server are exported from the `aws-amplify//server` sub paths. You **must** use these APIs for any server-side use cases. - -| Category | APIs | Server (Node.js) Amplify Hosting/Vercel | Vercel Edge Runtime (middleware) | -| --- | --- | --- | --- | -| Auth | `fetchAuthSession` | βœ… | βœ… | -| Auth | `fetchUserAttributes` | βœ… | βœ… | -| Auth | `getCurrentUser` | βœ… | βœ… | -| Data | `generateServerClientUsingCookies` | βœ… | | -| Data | `generateServerClientUsingReqRes` | βœ… | | -| Storage | `getUrl` | βœ… | | -| Storage | `getProperties` | βœ… | | -| Storage | `list` | βœ… | | -| Storage | `remove` | βœ… | | -| Storage | `copy` | βœ… | | - -> **Info:** Have a server-side use case that isn't currently supported in Amplify JS? Consider using the [AWS SDK for JavaScript](https://aws.amazon.com/sdk-for-javascript/). - ---- - ---- -title: "Next.js App Router (Server Components)" -section: "build-a-backend/server-side-rendering" -platforms: ["javascript", "nextjs"] -gen: 2 -last-updated: "2024-11-12T21:56:50.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/server-side-rendering/nextjs-app-router-server-components/" ---- - -This Quickstart guide will walk you through how to build a task list application with TypeScript, Next.js **App Router with Server Components**, and React. If you are new to these technologies, we recommend you go through the official [React](https://react.dev/learn/tutorial-tic-tac-toe), [Next.js](https://nextjs.org/docs/app/getting-started), and [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html) tutorials first. - -## Build UI - -Let's add UI that connects to the backend data and auth resources. - -### Configure Amplify Client Side - -First, install the Amplify UI component library: - -```bash showLineNumbers={false} -npm add @aws-amplify/ui-react -``` - -Next, create a `components` folder in the root of your project and copy the contents below to a file called `ConfigureAmplify.tsx`. - -```ts title="components/ConfigureAmplify.tsx" -// components/ConfigureAmplify.tsx -"use client"; - -import { Amplify } from "aws-amplify"; - -import outputs from "@/amplify_outputs.json"; - -Amplify.configure(outputs, { ssr: true }); - -export default function ConfigureAmplifyClientSide() { - return null; -} -``` - -Update `app/layout.tsx` to import and render ``. This client component will configure Amplify for client pages in our application. - -```ts title="app/layout.tsx" -// app/layout.tsx -import "@aws-amplify/ui-react/styles.css"; -import type { Metadata } from "next"; -import { Inter } from "next/font/google"; -import "./globals.css"; - -import ConfigureAmplifyClientSide from "@/components/ConfigureAmplify"; - -const inter = Inter({ subsets: ["latin"] }); - -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - - - {children} - - - ); -} -``` - -### Configure Amplify Server Side - -First, install the Amplify Next.js adapter: - -```bash showLineNumbers={false} -npm add @aws-amplify/adapter-nextjs -``` - -Next, create a `utils/amplify-utils.ts` file from the root of the project and paste the code below. `runWithAmplifyServerContext`, `cookiesClient`, and `AuthGetCurrentUserServer` are declared here and will be used to gain access to Amplify assets from the server. - -```ts title="utils/amplify-utils.ts" -// utils/amplify-utils.ts -import { cookies } from "next/headers"; - -import { createServerRunner } from "@aws-amplify/adapter-nextjs"; -import { generateServerClientUsingCookies } from "@aws-amplify/adapter-nextjs/api"; -import { getCurrentUser } from "aws-amplify/auth/server"; - -import { type Schema } from "@/amplify/data/resource"; -import outputs from "@/amplify_outputs.json"; - -export const { runWithAmplifyServerContext } = createServerRunner({ - config: outputs, -}); - -export const cookiesClient = generateServerClientUsingCookies({ - config: outputs, - cookies, -}); - -export async function AuthGetCurrentUserServer() { - try { - const currentUser = await runWithAmplifyServerContext({ - nextServerContext: { cookies }, - operation: (contextSpec) => getCurrentUser(contextSpec), - }); - return currentUser; - } catch (error) { - console.error(error); - } -} -``` - -### Add server authentication routes - -First, create a client-side `Login` component in the `components` folder that will be wrapped in `withAuthenticator`. If the user is logged in, they will be redirected to the index route; otherwise, the [Amplify UI Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator) will be rendered. - -```ts title="components/Login.tsx" -// components/Login.tsx -"use client"; - -import { withAuthenticator } from "@aws-amplify/ui-react"; -import { AuthUser } from "aws-amplify/auth"; -import { redirect } from "next/navigation"; -import { useEffect } from "react"; - -function Login({ user }: { user?: AuthUser }) { - useEffect(() => { - if (user) { - redirect("/"); - } - }, [user]); - return null; -} - -export default withAuthenticator(Login); -``` - -Next, create a new route under `app/login/page.tsx` to render the `Login` component. - -```ts title="app/login/page.tsx" -// app/login/page.tsx - -import Login from "@/components/Login"; - -export default function LoginPage() { - return ; -} -``` - -
    Custom example - -Some applications require more customization for the `` component. The following example shows how to add a custom Header to the ``. For this use, you will not need the `Login` file in the `components` folder, but can do everything through the `page` file in the `app/login/` folder. For more customization options, see [Authenticator Customization](https://ui.docs.amplify.aws/react/connected-components/authenticator/customization). - -```ts title="app/login/page.tsx" -// app/login/page.tsx - Custom - -"use client"; - -import { - Authenticator, - Text, - View, - useAuthenticator, -} from "@aws-amplify/ui-react"; -import { redirect } from "next/navigation"; -import { useEffect } from "react"; - -const components = { - Header() { - return ( - - Authenticator Header - - ); - }, -}; - -function CustomAuthenticator() { - const { user } = useAuthenticator((context) => [context.user]); - - useEffect(() => { - if (user) { - redirect("/"); - } - }, [user]); - - return ; -} - -export default function Login() { - return ( - - - - ); -} - -``` - -
    - -Finally, create a `Logout` component to be used later. - -```ts title="components/Logout.tsx" -// components/Logout.tsx - -"use client"; - -import { signOut } from "aws-amplify/auth"; -import { useRouter } from "next/navigation"; - -export default function Logout() { - const router = useRouter(); - - return ( - - ); -} -``` - -> **Info:** **Note**: If using [Amplify UI Social Providers](https://ui.docs.amplify.aws/react/connected-components/authenticator/configuration#social-providers), set the `callbackUrls` for the `/login` route when [configuring social sign-in for your Gen 2 backend](https://docs.amplify.aws/gen2/build-a-backend/auth/add-social-provider/#configure-social-sign-in-backend), as shown below. -> -> ```ts title="amplify/auth/resource.ts" -import { defineAuth, secret } from '@aws-amplify/backend'; - -export const auth = defineAuth({ - loginWith: { - externalProviders: { - // ... - callbackUrls: [ - 'http://localhost:3000/login', - 'https://mywebsite.com/login' - ], - logoutUrls: ['http://localhost:3000/logout', 'https://mywebsite.com/logout'] - } - } -}); -``` - -### Add middleware for server-side redirect - -Create `middleware.ts` in the root of the project with the contents below. - -This middleware runs `fetchAuthSession` wrapped in `runWithAmplifyServerContext` and will redirect to `/login` when a user is not logged in. - -```ts title="middleware.ts" -// middleware.ts -import { NextRequest, NextResponse } from "next/server"; - -import { fetchAuthSession } from "aws-amplify/auth/server"; - -import { runWithAmplifyServerContext } from "@/utils/amplify-utils"; - -export async function middleware(request: NextRequest) { - const response = NextResponse.next(); - - const authenticated = await runWithAmplifyServerContext({ - nextServerContext: { request, response }, - operation: async (contextSpec) => { - try { - const session = await fetchAuthSession(contextSpec, {}); - return session.tokens !== undefined; - } catch (error) { - console.log(error); - return false; - } - }, - }); - - if (authenticated) { - return response; - } - - return NextResponse.redirect(new URL("/login", request.url)); -} - -export const config = { - matcher: [ - /* - * Match all request paths except for the ones starting with: - * - api (API routes) - * - _next/static (static files) - * - _next/image (image optimization files) - * - favicon.ico (favicon file) - * - login - */ - "/((?!api|_next/static|_next/image|favicon.ico|login).*)", - ], -}; -``` - -Run your application with `npm run dev` and navigate to `http://localhost:3000`. You should now see the authenticator, which is already configured and ready for your first sign-up! Create a new user account, confirm the account through email, and then sign in. - -### View list of to-do items - -Now, let's display data on our app's frontend. - -The code below uses the `cookiesClient` to provide access to the `Todo` model defined in the backend. - -Modify your app's home page file, `app/page.tsx`, with the following code: - -```ts title="app/page.tsx" -// app/page.tsx - -import { cookiesClient } from "@/utils/amplify-utils"; - -async function App() { - const { data: todos } = await cookiesClient.models.Todo.list(); - - return ( - <> -

    Hello, Amplify πŸ‘‹

    -
      - {todos && todos.map((todo) =>
    • {todo.content}
    • )} -
    - - ); -} - -export default App; -``` - -Once you save the file and navigate back to `http://localhost:3000`, you should see "Hello, Amplify" with a blank page for now because you have only an empty list of to-dos. - -### Create a new to-do item - -Let's update the component to have a form for prompting the user for the title for creating a new to-do list item and run the `addTodo` method on form submission. In a production app, the additional fields of the `Todo` model would be added to the form. - -After creating a to-do, `revalidatePath` is run to clear the Next.js cache for this route to instantly update the results from the server without a full page reload. - -```ts title="app/page.tsx" -// app/page.tsx - -import { revalidatePath } from "next/cache"; - -import { AuthGetCurrentUserServer, cookiesClient } from "@/utils/amplify-utils"; - -import Logout from "@/components/Logout"; - -async function App() { - const user = await AuthGetCurrentUserServer(); - const { data: todos } = await cookiesClient.models.Todo.list(); - - async function addTodo(data: FormData) { - "use server"; - const title = data.get("title") as string; - await cookiesClient.models.Todo.create({ - content: title, - done: false, - priority: "medium", - }); - revalidatePath("/"); - } - - return ( - <> -

    Hello, Amplify πŸ‘‹

    - {user && } -
    - - -
    - -
      - {todos && todos.map((todo) =>
    • {todo.content}
    • )} -
    - - ); -} - -export default App; -``` - -### Terminate dev server - -Go to `localhost` in the browser to make sure you can now log in and create and list to-dos. You can end your development session by shutting down the frontend dev server and cloud sandbox. The sandbox prompts you to delete your backend resources. While you can retain your backend, we recommend deleting all resources so you can start clean again next time. - ---- - ---- -title: "Use Amplify categories APIs from Nuxt 3" -section: "build-a-backend/server-side-rendering" -platforms: ["javascript", "vue"] -gen: 2 -last-updated: "2025-03-12T00:03:13.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/server-side-rendering/nuxt/" ---- - -If you have not already done so, please read the introduction documentation, [Use Amplify Categories APIs in Server-Side Rendering](/gen1/[platform]/build-a-backend/server-side-rendering/), to learn about how to use Amplify categories' APIs in server-side rendering. - -This documentation provides a getting started guide to using the generic `runWithAmplifyServerContext` adapter (exported from `aws-amplify/adapter-core`) to enable Amplify in a Nuxt 3 project. The examples in this documentation may not present best practices for your Nuxt project. You are welcome to provide suggestions and contributions to improve this documentation or to create a Nuxt adapter package for Amplify and let others use it. - - - -**Note:** This guide assumes that you have deep knowledge of Nuxt 3. - - - -## Start using Amplify in your Nuxt 3 project - -You can install relevant Amplify libraries by following the [manual installation](/[platform]/start/manual-installation/) guide. - -## Set up the AmplifyAPIs plugin - -Nuxt 3 offers universal rendering by default, where your data fetching logic may be executed in both client and server runtimes. Amplify offers APIs that are capable of running within a server context to support use cases such as server-side rendering (SSR) and static site generation (SSG), though Amplify's client-side APIs and server-side APIs are slightly different. You can set up an `AmplifyAPIs` plugin to make your data fetching logic run smoothly across the client and server. - -1. If you haven’t already done so, create a `plugins` directory under the root of your Nuxt project -2. Create two files `01.amplifyApis.client.ts` and `01.amplifyApis.server.ts` under the `plugins` directory - - - -**Note:** the leading number in the filenames indicates the plugin loading order, details see https://nuxt.com/docs/guide/directory-structure/plugins#registration-order. The `.client` and `.server` indicate the runtime that the logic contained in the file will run on, client or server. For details see: https://nuxt.com/docs/guide/directory-structure/plugins - - - -In these files, you will register both client-specific and server-specific Amplify APIs that you will use in your Nuxt project as a plugin. You can then access these APIs via the `useNuxtApp` composable. - -### Implement `01.amplifyApis.client.ts` - -Example implementation: - -```ts title="plugins/01.amplifyApis.client.ts" -import type { Schema } from '~/amplify/data/resource'; -import { Amplify } from 'aws-amplify'; -import { - fetchAuthSession, - fetchUserAttributes, - signIn, - signOut -} from 'aws-amplify/auth'; -import { generateClient } from 'aws-amplify/api'; -import { list } from 'aws-amplify/storage'; - -import outputs from '../amplify_outputs.json'; - -const client = generateClient(); - -export default defineNuxtPlugin({ - name: 'AmplifyAPIs', - enforce: 'pre', - - setup() { - // This configures Amplify on the client side of your Nuxt app - Amplify.configure(outputs, { ssr: true }); - - return { - provide: { - // You can add the Amplify APIs that you will use on the client side - // of your Nuxt app here. - // - // You can call the API by via the composable `useNuxtApp()`. For example: - // `useNuxtApp().$Amplify.Auth.fetchAuthSession()` - Amplify: { - Auth: { - fetchAuthSession, - fetchUserAttributes, - signIn, - signOut - }, - Storage: { - list - }, - GraphQL: { - client - } - } - } - }; - } -}); -``` - - - -Make sure you call `Amplify.configure` as early as possible in your application’s lifecycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. Review the [Library Not Configured Troubleshooting guide](/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/) for possible causes of this issue. - - - -### Implement `01.amplifyApis.server.ts` - -Example implementation: - -```ts title="plugins/01.amplifyApis.server.ts" -import type { FetchAuthSessionOptions } from 'aws-amplify/auth'; -import type { ListPaginateWithPathInput } from 'aws-amplify/storage'; -import type { CookieRef } from 'nuxt/app'; -import type { Schema } from '~/amplify/data/resource'; - -import { - createAWSCredentialsAndIdentityIdProvider, - createKeyValueStorageFromCookieStorageAdapter, - createUserPoolsTokenProvider, - runWithAmplifyServerContext -} from 'aws-amplify/adapter-core'; -import { generateClient } from 'aws-amplify/api/server'; -import { - fetchAuthSession, - fetchUserAttributes, - getCurrentUser -} from 'aws-amplify/auth/server'; -import { list } from 'aws-amplify/storage/server'; -import { parseAmplifyConfig } from 'aws-amplify/utils'; - -import outputs from '../amplify_outputs.json'; - -// parse the content of `amplify_outputs.json` into the shape of ResourceConfig -const amplifyConfig = parseAmplifyConfig(outputs); - -// create the Amplify used token cookies names array -const userPoolClientId = amplifyConfig.Auth!.Cognito.userPoolClientId; -const lastAuthUserCookieName = `CognitoIdentityServiceProvider.${userPoolClientId}.LastAuthUser`; - -// create a GraphQL client that can be used in a server context -const gqlServerClient = generateClient({ config: amplifyConfig }); - -// extract the model operation function types for creating wrapper function later -type RemoveFirstParam = Params extends [infer _, ...infer Rest] ? Rest : never; -type TodoListInput = RemoveFirstParam>; -type TodoCreateInput = RemoveFirstParam>; -type TodoUpdateInput = RemoveFirstParam>; - -const getAmplifyAuthKeys = (lastAuthUser: string) => - ['idToken', 'accessToken', 'refreshToken', 'clockDrift'] - .map( - (key) => - `CognitoIdentityServiceProvider.${userPoolClientId}.${lastAuthUser}.${key}` - ) - .concat(lastAuthUserCookieName); - -// define the plugin -export default defineNuxtPlugin({ - name: 'AmplifyAPIs', - enforce: 'pre', - setup() { - // The Nuxt composable `useCookie` is capable of sending cookies to the - // client via the `SetCookie` header. If the `expires` option is left empty, - // it sets a cookie as a session cookie. If you need to persist the cookie - // on the client side after your end user closes your Web app, you need to - // specify an `expires` value. - // - // We use 30 days here as an example (the default Cognito refreshToken - // expiration time). - const expires = new Date(); - expires.setDate(expires.getDate() + 30); - - // Get the last auth user cookie value - // - // We use `sameSite: 'lax'` in this example, which allows the cookie to be - // sent to your Nuxt server when your end user gets redirected to your Web - // app from a different domain. You should choose an appropriate value for - // your own use cases. - const lastAuthUserCookie = useCookie(lastAuthUserCookieName, { - sameSite: 'lax', - expires, - secure: true - }); - - // Get all Amplify auth token cookie names - const authKeys = lastAuthUserCookie.value - ? getAmplifyAuthKeys(lastAuthUserCookie.value) - : []; - - // Create a key-value map of cookie name => cookie ref - // - // Using the composable `useCookie` here in the plugin setup prevents - // cross-request pollution. - const amplifyCookies = authKeys - .map((name) => ({ - name, - cookieRef: useCookie(name, { sameSite: 'lax', expires, secure: true }) - })) - .reduce>>( - (result, current) => ({ - ...result, - [current.name]: current.cookieRef - }), - {} - ); - - // Create a key value storage based on the cookies - // - // This key value storage is responsible for providing Amplify Auth tokens to - // the APIs that you are calling. - // - // If you implement the `set` method, when Amplify needed to refresh the Auth - // tokens on the server side, the new tokens would be sent back to the client - // side via `SetCookie` header in the response. Otherwise the refresh tokens - // would not be propagate to the client side, and Amplify would refresh - // the tokens when needed on the client side. - // - // In addition, if you decide not to implement the `set` method, you don't - // need to pass any `CookieOptions` to the `useCookie` composable. - const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter({ - get(name) { - const cookieRef = amplifyCookies[name]; - - if (cookieRef && cookieRef.value) { - return { name, value: cookieRef.value }; - } - - return undefined; - }, - getAll() { - return Object.entries(amplifyCookies).map(([name, cookieRef]) => { - return { name, value: cookieRef.value ?? undefined }; - }); - }, - set(name, value) { - const cookieRef = amplifyCookies[name]; - if (cookieRef) { - cookieRef.value = value; - } - }, - delete(name) { - const cookieRef = amplifyCookies[name]; - - if (cookieRef) { - cookieRef.value = null; - } - } - }); - - // Create a token provider - const tokenProvider = createUserPoolsTokenProvider( - amplifyConfig.Auth!, - keyValueStorage - ); - - // Create a credentials provider - const credentialsProvider = createAWSCredentialsAndIdentityIdProvider( - amplifyConfig.Auth!, - keyValueStorage - ); - - // Create the libraryOptions object - const libraryOptions = { - Auth: { - tokenProvider, - credentialsProvider - } - }; - - return { - provide: { - // You can add the Amplify APIs that you will use on the server side of - // your Nuxt app here. You must only use the APIs exported from the - // `aws-amplify//server` subpaths. - // - // You can call the API by via the composable `useNuxtApp()`. For example: - // `useNuxtApp().$Amplify.Auth.fetchAuthSession()` - // - // Recall that Amplify server APIs are required to be called in a isolated - // server context that is created by the `runWithAmplifyServerContext` - // function. - Amplify: { - Auth: { - fetchAuthSession: (options: FetchAuthSessionOptions) => - runWithAmplifyServerContext( - amplifyConfig, - libraryOptions, - (contextSpec) => fetchAuthSession(contextSpec, options) - ), - fetchUserAttributes: () => - runWithAmplifyServerContext( - amplifyConfig, - libraryOptions, - (contextSpec) => fetchUserAttributes(contextSpec) - ), - getCurrentUser: () => - runWithAmplifyServerContext( - amplifyConfig, - libraryOptions, - (contextSpec) => getCurrentUser(contextSpec) - ) - }, - Storage: { - list: (input: ListPaginateWithPathInput) => - runWithAmplifyServerContext( - amplifyConfig, - libraryOptions, - (contextSpec) => list(contextSpec, input) - ) - }, - GraphQL: { - client: { - models: { - Todo: { - list(...input: TodoListInput) { - return runWithAmplifyServerContext( - amplifyConfig, - libraryOptions, - (contextSpec) => gqlServerClient.models.Todo.list(contextSpec, ...input) - ) - }, - create(...input: TodoCreateInput) { - return runWithAmplifyServerContext( - amplifyConfig, - libraryOptions, - (contextSpec) => gqlServerClient.models.Todo.create(contextSpec, ...input) - ) - }, - update(...input: TodoUpdateInput) { - return runWithAmplifyServerContext( - amplifyConfig, - libraryOptions, - (contextSpec) => gqlServerClient.models.Todo.update(contextSpec, ...input) - ) - } - } - } - } - } - } - } - }; - } -}); -``` - -#### Usage example - -Using the Storage `list` API in `pages/storage-list.vue`: - -```ts title="pages/storage-list.vue" - -// `useAsyncData` and `useNuxtApp` are Nuxt composables -// `$Amplify` is generated by Nuxt according to the `provide` key in the plugins -// we've added above - - - -``` - -Using the GraphQL API in `pages/todos-list.vue`: - -```ts title="pages/todos-list.vue" - - - -``` - -The above two pages can be rendered on both the client and server by default. `useNuxtApp().$Amplify` will pick up the correct implementation of `01.amplifyApis.client.ts` and `01.amplifyApis.server.ts` to use, depending on the runtime. - -> **Warning:** Only a subset of Amplify APIs are usable on the server side, and as the libraries evolve, `amplify-apis.client` and `amplify-apis.server` may diverge further. You can guard your API calls to ensure an API is available in the context where you use it (e.g., you can use `if (process.client)` to ensure that a client-only API isn't executed on the server). - -## Set up Auth middleware to protect your routes - -The auth middleware will use the plugin configured in the previous step as a dependency; therefore you can add the auth middleware via another plugin that will be loaded after the previous one. - -1. Create a `02.authRedirect.ts` file under plugins directory - - - -**Note:** This file will run on both client and server, details see: https://nuxt.com/docs/guide/directory-structure/middleware#when-middleware-runs. The `02` name prefix ensures this plugin loads after the previous so `useNuxtApp().$Amplify` becomes available. - - - -### Implement `02.authRedirect.ts` - -Example implementation: - -```ts title="plugins/02.authRedirect.ts" -import { Amplify } from 'aws-amplify'; - -import outputs from '~/amplify_outputs.json'; - -// Amplify.configure() only needs to be called on the client side -if (process.client) { - Amplify.configure(outputs, { ssr: true }); -} - -export default defineNuxtPlugin({ - name: 'AmplifyAuthRedirect', - enforce: 'pre', - setup() { - addRouteMiddleware( - 'AmplifyAuthMiddleware', - defineNuxtRouteMiddleware(async (to) => { - try { - const session = await useNuxtApp().$Amplify.Auth.fetchAuthSession(); - - // If the request is not associated with a valid user session - // redirect to the `/sign-in` route. - // You can also add route match rules against `to.path` - if (session.tokens === undefined && to.path !== '/sign-in') { - return navigateTo('/sign-in'); - } - - if (session.tokens !== undefined && to.path === '/sign-in') { - return navigateTo('/'); - } - } catch (e) { - if (to.path !== '/sign-in') { - return navigateTo('/sign-in'); - } - } - }), - { global: true } - ); - } -}); -``` - - - -Make sure you call `Amplify.configure` as early as possible in your application’s life-cycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. Review the [Library Not Configured Troubleshooting guide](/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/) for possible causes of this issue. - - - -## Set Up Amplify for API Route Use Cases - -Following the specification of Nuxt, your API route handlers will live under `~/server`, which is a separate environment from other parts of your Nuxt app; hence, the plugins created in the previous sections are not usable here, and extra work is required. - -### Set up Amplify server context utility - -1. If you haven’t already done so, create a `utils` directory under the server directory of your Nuxt project -2. Create an `amplifyUtils.ts` file under the `utils` directory - -In this file, you will create a helper function to call Amplify APIs that are capable of running on the server side with context isolation. - -Example implementation: - -```ts title="utils/amplifyUtils.ts" -import type { H3Event, EventHandlerRequest } from 'h3'; - -import { - AmplifyServer, - CookieStorage, - createAWSCredentialsAndIdentityIdProvider, - createKeyValueStorageFromCookieStorageAdapter, - createUserPoolsTokenProvider, - runWithAmplifyServerContext, -} from 'aws-amplify/adapter-core'; -import { parseAmplifyConfig } from 'aws-amplify/utils'; - -import outputs from '~/amplify_outputs.json'; - -const amplifyConfig = parseAmplifyConfig(outputs); - -const createCookieStorageAdapter = ( - event: H3Event -): CookieStorage.Adapter => { - // `parseCookies`, `setCookie` and `deleteCookie` are Nuxt provided functions - const readOnlyCookies = parseCookies(event); - - return { - get(name) { - if (readOnlyCookies[name]) { - return { name, value: readOnlyCookies[name] }; - } - }, - set(name, value, options) { - setCookie(event, name, value, options); - }, - delete(name) { - deleteCookie(event, name); - }, - getAll() { - return Object.entries(readOnlyCookies).map(([name, value]) => { - return { name, value }; - }); - } - }; -}; - -const createLibraryOptions = ( - event: H3Event -) => { - const cookieStorage = createCookieStorageAdapter(event); - const keyValueStorage = - createKeyValueStorageFromCookieStorageAdapter(cookieStorage); - const tokenProvider = createUserPoolsTokenProvider( - amplifyConfig.Auth!, - keyValueStorage - ); - const credentialsProvider = createAWSCredentialsAndIdentityIdProvider( - amplifyConfig.Auth!, - keyValueStorage - ); - - return { - Auth: { - tokenProvider, - credentialsProvider - } - }; -}; - -export const runAmplifyApi = ( - // we need the event object to create a context accordingly - event: H3Event, - operation: ( - contextSpec: AmplifyServer.ContextSpec - ) => Result | Promise -) => { - return runWithAmplifyServerContext( - amplifyConfig, - createLibraryOptions(event), - operation - ); -}; -``` - -You can then use `runAmplifyApi` function to call Amplify APIs in an isolated server context. - -#### Usage example - -Take implementing an API route `GET /api/current-user` , in `server/api/current-user.ts`: - -```ts title="server/api/current-user.ts" -import { getCurrentUser } from 'aws-amplify/auth/server'; - -import { runAmplifyApi } from '~/server/utils/amplifyUtils'; - -export default defineEventHandler(async (event) => { - const user = await runAmplifyApi(event, (contextSpec) => - getCurrentUser(contextSpec) - ); - - return user; -}); -``` - -Then you can fetch data from this route, for example, `fetch('http://localhost:3000/api/current-user')`. - -## Set up server middleware to protect your API routes - -Similar to API routes, the previously added auth middleware are not usable under `/server`, hence extra work is required to set up a auth middleware to protect your routes. - -1. If you haven’t already done so, create a `middleware` directory under the `server` directory of your Nuxt project -2. Create an `amplifyAuthMiddleware.ts` file under the `middleware` directory - -This middleware will be executed before a request reach your API route. - -Example implementation: - -```ts title="middleware/amplifyAuthMiddleware.ts" -import { fetchAuthSession } from 'aws-amplify/auth/server'; - -export default defineEventHandler(async (event) => { - if (event.path.startsWith('/api/')) { - try { - const session = await runAmplifyApi(event, (contextSpec) => - fetchAuthSession(contextSpec) - ); - - // You can add extra logic to match the requested routes to apply - // the auth protection - if (session.tokens === undefined) { - setResponseStatus(event, 403); - return { - error: 'Access denied!' - }; - } - } catch (error) { - return { - error: 'Access denied!' - }; - } - } -}); -``` - -With this middleware, when executing `fetch('http://localhost:3000/api/current-user')` without signing in a user on the client side, the `fetch` will receive a 403 error, and the request won’t reach route `/api/current-user`. - ---- - ---- -title: "Add any AWS service" -section: "build-a-backend" -platforms: ["android", "angular", "flutter", "javascript", "nextjs", "react", "react-native", "swift", "vue"] -gen: 2 -last-updated: "2024-02-21T20:06:17.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/" ---- - - - ---- - ---- -title: "Analytics" -section: "build-a-backend/add-aws-services" -platforms: ["javascript", "react-native", "swift", "android", "flutter", "angular", "nextjs", "react", "vue"] -gen: 2 -last-updated: "2024-04-08T21:54:24.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/analytics/" ---- - - - ---- - ---- -title: "Set up Amplify Analytics" -section: "build-a-backend/add-aws-services/analytics" -platforms: ["javascript", "react-native", "swift", "android", "flutter", "angular", "nextjs", "react", "vue"] -gen: 2 -last-updated: "2025-11-13T16:29:27.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/analytics/set-up-analytics/" ---- - -Amplify enables you to collect analytics data for your app. In order to use Analytics, you will enable [Amazon Kinesis](https://aws.amazon.com/kinesis/) or [Amazon Pinpoint](https://aws.amazon.com/pinpoint/) using the AWS Cloud Development Kit (AWS CDK). The Analytics category uses [Amazon Cognito identity pools](https://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html) to _identify_ users in your app. Cognito allows you to receive data from authenticated, and unauthenticated users in your app. - - -## Prerequisites - -An application with Amplify libraries integrated and a minimum target of any of the following: -- **iOS 13.0**, using **Xcode 14.1** or later. -- **macOS 10.15**, using **Xcode 14.1** or later. -- **tvOS 13.0**, using **Xcode 14.3** or later. -- **watchOS 9.0**, using **Xcode 14.3** or later. -- **visionOS 1.0**, using **Xcode 15** or later. (Preview support - see below for more details.) - - - -visionOS support is currently in **preview** and can be used by using the latest [Amplify Release](https://github.com/aws-amplify/amplify-swift/releases). -As new Xcode and visionOS versions are released, the support will be updated with any necessary fixes on a best effort basis. - - - - - -## Prerequisites - -* An Android application targeting Android API level 24 (Android 7.0) or above - - - -Amplify Flutter requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Refer to [Flutter's supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) when targeting Web, Windows, or Linux. - -## Set up Analytics backend - -Use the [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/home.html) to create an analytics resource powered by [Amazon Pinpoint](https://aws.amazon.com/pinpoint/). - -```ts title="amplify/backend.ts" -import { defineBackend } from "@aws-amplify/backend" -import { auth } from "./auth/resource"; -import { data } from "./data/resource"; -import { Policy, PolicyStatement } from "aws-cdk-lib/aws-iam"; -import { CfnApp } from "aws-cdk-lib/aws-pinpoint"; -import { Stack } from "aws-cdk-lib/core"; - -const backend = defineBackend({ - auth, - data, - // additional resources -}); - -const analyticsStack = backend.createStack("analytics-stack"); - -// create a Pinpoint app -const pinpoint = new CfnApp(analyticsStack, "Pinpoint", { - name: "myPinpointApp", -}); - -// create an IAM policy to allow interacting with Pinpoint -const pinpointPolicy = new Policy(analyticsStack, "PinpointPolicy", { - policyName: "PinpointPolicy", - statements: [ - new PolicyStatement({ - actions: ["mobiletargeting:UpdateEndpoint", "mobiletargeting:PutEvents"], - resources: [pinpoint.attrArn + "/*"], - }), - ], -}); - -// apply the policy to the authenticated and unauthenticated roles -backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy(pinpointPolicy); -backend.auth.resources.unauthenticatedUserIamRole.attachInlinePolicy(pinpointPolicy); - -// patch the custom Pinpoint resource to the expected output configuration -backend.addOutput({ - analytics: { - amazon_pinpoint: { - app_id: pinpoint.ref, - aws_region: Stack.of(pinpoint).region, - } - }, -}); -``` - -## Install Amplify Libraries - - -First, install the `aws-amplify` library: - -```sh title="Terminal" showLineNumbers={false} -npm add aws-amplify -``` - - - -1. To install the Amplify Libraries in your application, open your project in Xcode and select **File > Add Packages...**. - -2. Enter the **Amplify Library for Swift** GitHub repo URL (`https://github.com/aws-amplify/amplify-swift`) into the search bar and click **Add Package**. - - - - **Note:** **Up to Next Major Version** should be selected from the **Dependency Rule** dropdown. - - -3. Lastly, add **AWSPinpointAnalyticsPlugin**, **AWSCognitoAuthPlugin**, and **Amplify** to your target. Then click **Add Package**. - - - -Expand **Gradle Scripts**, open **build.gradle.kts (Module :app)**. You will already have configured Amplify by following the steps in the quickstart guide. - -Add Analytics by adding these libraries into the dependencies block: -```kotlin title="app/build.gradle.kts" -android { - compileOptions { - // Support for modern Java features - isCoreLibraryDesugaringEnabled = true - } -} - -dependencies { - // Amplify API dependencies - // highlight-start - implementation("com.amplifyframework:aws-analytics-pinpoint:ANDROID_VERSION") - implementation("com.amplifyframework:aws-auth-cognito:ANDROID_VERSION") - // highlight-end - // ... other dependencies - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:ANDROID_DESUGAR_VERSION") -} -``` - -Click **Sync Now**. - - - -In your Flutter project directory, open **pubspec.yaml**. -Add Analytics by adding these libraries into your dependencies block: - -```yaml -dependencies: - amplify_analytics_pinpoint: ^2.0.0 - amplify_auth_cognito: ^2.0.0 - amplify_flutter: ^2.0.0 -``` - - -## Initialize Amplify Analytics - -Import and load the configuration file in your app. It's recommended you add the Amplify configuration step to your app's root entry point. - - -```js title="src/index.js" -import { Amplify } from 'aws-amplify'; -import outputs from '../amplify_outputs.json'; - -Amplify.configure(outputs); -``` - - - -```js title="pages/_app.tsx" -import { Amplify } from 'aws-amplify'; -import outputs from '@/amplify_outputs.json'; - -Amplify.configure(outputs); -``` - - - -> **Warning:** Make sure to generate the `amplify_outputs.json` file by running the following command: -> -> ```bash title="Terminal" showLineNumbers={false} -npx ampx sandbox -``` -> -> Next, move the file to your project. You can do this by dragging and dropping the file into your Xcode project. - -To use the Amplify Analytics and Authentication categories in your app, you need to create and configure their corresponding plugins by calling the `Amplify.add(plugin:)` and `Amplify.configure(with:)` methods. - -#### [SwiftUI] - -Add the following imports to the top of your main `App` file: - -```swift -import Amplify -import AWSCognitoAuthPlugin -import AWSPinpointAnalyticsPlugin -``` - -Add the following code to its initializer. If there is none, you can create a default `init`: - -```swift -init() { - do { - try Amplify.add(plugin: AWSCognitoAuthPlugin()) - try Amplify.add(plugin: AWSPinpointAnalyticsPlugin()) - try Amplify.configure(with: .amplifyOutputs) - print("Amplify configured with Auth and Analytics plugins") - } catch { - print("Failed to initialize Amplify with \(error)") - } -} -``` - -#### [UIKit] - -Add the following imports to the top of your `AppDelegate.swift` file: - -```swift -import Amplify -import AWSCognitoAuthPlugin -import AWSPinpointAnalyticsPlugin -``` - -Add the following code to the `application:didFinishLaunchingWithOptions` method: - -```swift -func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? -) -> Bool { - do { - try Amplify.add(plugin: AWSCognitoAuthPlugin()) - try Amplify.add(plugin: AWSPinpointAnalyticsPlugin()) - try Amplify.configure(with: .amplifyOutputs) - print("Amplify configured with Auth and Analytics plugins") - } catch { - print("Failed to initialize Amplify with \(error)") - } - - return true -} -``` - -Upon building and running this application you should see the following in your console window: - -```console -Amplify configured with Auth and Analytics plugin -``` - - - -Add the Auth and Analytics plugin, along with any other plugins you may have added as described in the **Project Setup** section; - -```dart -import 'package:amplify_analytics_pinpoint/amplify_analytics_pinpoint.dart'; -import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; - -import 'amplify_outputs.dart'; - -Future _configureAmplify() async { - // Add Pinpoint and Cognito Plugins, and any other plugins you want to use - final analyticsPlugin = AmplifyAnalyticsPinpoint(); - final authPlugin = AmplifyAuthCognito(); - await Amplify.addPlugins([analyticsPlugin, authPlugin]); -} -``` - - - -When running your app on macOS you will need to enable keychain sharing in Xcode, as described in the [Project setup guide](/gen1/[platform]/start/project-setup/platform-setup/#enable-keychain). - - - -Make sure that the amplify_outputs.dart file generated in the project setup is included and sent to Amplify.configure: - -```dart -import 'package:amplify_analytics_pinpoint/amplify_analytics_pinpoint.dart'; -import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:flutter/material.dart'; - -import 'amplify_outputs.dart'; - -Future _configureAmplify() async { - // ... - await Amplify.addPlugins([analyticsPlugin, authPlugin]); - - // Once Plugins are added, configure Amplify - // Note: Amplify can only be configured once. - try { - await Amplify.configure(amplifyConfig); - } on AmplifyAlreadyConfiguredException { - safePrint( - 'Tried to reconfigure Amplify; this can occur when your app restarts on Android.', - ); - } -} -``` - -Your class will look like this: - -```dart -import 'package:amplify_flutter/amplify_flutter.dart'; -import 'package:amplify_analytics_pinpoint/amplify_analytics_pinpoint.dart'; -import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; -import 'package:flutter/material.dart'; - -import 'amplify_outputs.dart'; - -Future _configureAmplify() async { - // Add any Amplify plugins you want to use - final analyticsPlugin = AmplifyAnalyticsPinpoint(); - final authPlugin = AmplifyAuthCognito(); - await Amplify.addPlugins([analyticsPlugin, authPlugin]); - - // Once Plugins are added, configure Amplify - // Note: Amplify can only be configured once. - try { - await Amplify.configure(amplifyConfig); - } on AmplifyAlreadyConfiguredException { - safePrint( - 'Tried to reconfigure Amplify; this can occur when your app restarts on Android.', - ); - } -} - -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - await _configureAmplify(); - runApp(const MyApp()); -} - -class MyApp extends StatefulWidget { - const MyApp({Key? key}): super(key: key); - - // ... -} -``` - - - -To initialize the Amplify Auth and Analytics categories you call `Amplify.addPlugin()` method for each category. To complete initialization call `Amplify.configure()`. - -Add the following code to your `onCreate()` method in your application class: - -> **Warning:** Before calling the `Amplify.configure` function, make sure to either download the `amplify_outputs.json` file from the console, or generate it with the following command: -> -> ```bash title="Terminal" showLineNumbers={false} -npx ampx generate outputs --app-id --branch main --out-dir app/src/main/res/raw -``` -> -> Next, be sure the file you generated or downloaded is in the appropriate resource directory for your application (for example, `app/src/main/res/raw`) in your Android project. Otherwise, you will not be able to compile your application. - -#### [Java] - -```java -import android.util.Log; -import com.amplifyframework.AmplifyException; -import com.amplifyframework.analytics.pinpoint.AWSPinpointAnalyticsPlugin; -import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin; -import com.amplifyframework.core.Amplify; -import com.amplifyframework.core.configuration.AmplifyOutputs; - -``` - -```java -Amplify.addPlugin(new AWSCognitoAuthPlugin()); -Amplify.addPlugin(new AWSPinpointAnalyticsPlugin()); -``` - -Your class will look like this: - -```java -public class MyAmplifyApp extends Application { - @Override - public void onCreate() { - super.onCreate(); - - try { - // Add these lines to add the AWSCognitoAuthPlugin and AWSPinpointAnalyticsPlugin plugins - Amplify.addPlugin(new AWSCognitoAuthPlugin()); - Amplify.addPlugin(new AWSPinpointAnalyticsPlugin()); - Amplify.configure(AmplifyOutputs.fromResource(R.raw.amplify_outputs), getApplicationContext()); - - Log.i("MyAmplifyApp", "Initialized Amplify"); - } catch (AmplifyException error) { - Log.e("MyAmplifyApp", "Could not initialize Amplify", error); - } - } -} -``` - -#### [Kotlin] - -```kotlin -import android.util.Log -import com.amplifyframework.AmplifyException -import com.amplifyframework.analytics.pinpoint.AWSPinpointAnalyticsPlugin -import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin -import com.amplifyframework.core.Amplify -import com.amplifyframework.core.configuration.AmplifyOutputs -``` - -```kotlin -Amplify.addPlugin(AWSCognitoAuthPlugin()) -Amplify.addPlugin(AWSPinpointAnalyticsPlugin()) -``` - -Your class will look like this: - -```kotlin -class MyAmplifyApp : Application() { - override fun onCreate() { - super.onCreate() - - try { - // Add these lines to add the AWSCognitoAuthPlugin and AWSPinpointAnalyticsPlugin plugins - Amplify.addPlugin(AWSCognitoAuthPlugin()) - Amplify.addPlugin(AWSPinpointAnalyticsPlugin()) - Amplify.configure(AmplifyOutputs.fromResource(R.raw.amplify_outputs), applicationContext) - - Log.i("MyAmplifyApp", "Initialized Amplify") - } catch (error: AmplifyException) { - Log.e("MyAmplifyApp", "Could not initialize Amplify", error) - } - } -} -``` - -#### [RxJava] - -```java -import android.util.Log; -import com.amplifyframework.AmplifyException; -import com.amplifyframework.analytics.pinpoint.AWSPinpointAnalyticsPlugin; -import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin; -import com.amplifyframework.core.configuration.AmplifyOutputs; -import com.amplifyframework.rx.RxAmplify; -``` - -```java -RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); -RxAmplify.addPlugin(new AWSPinpointAnalyticsPlugin()); -``` - -Your class will look like this: - -```java -public class MyAmplifyApp extends Application { - @Override - public void onCreate() { - super.onCreate(); - - try { - // Add these lines to add the AWSCognitoAuthPlugin and AWSPinpointAnalyticsPlugin plugins - RxAmplify.addPlugin(new AWSCognitoAuthPlugin()); - RxAmplify.addPlugin(new AWSPinpointAnalyticsPlugin()); - RxAmplify.configure(AmplifyOutputs.fromResource(R.raw.amplify_outputs), getApplicationContext()); - - Log.i("MyAmplifyApp", "Initialized Amplify"); - } catch (AmplifyException error) { - Log.e("MyAmplifyApp", "Could not initialize Amplify", error); - } - } -} -``` - - - -Next Steps: - -Congratulations! Now that you have Analytics' backend provisioned and Analytics library installed. Check out the following links to see Amplify Analytics use cases: - -- [Record Events](/[platform]/build-a-backend/add-aws-services/analytics/record-events/) -- [Track Sessions](/[platform]/build-a-backend/add-aws-services/analytics/auto-track-sessions/) -- [Identify User](/[platform]/build-a-backend/add-aws-services/analytics/identify-user/) - -### References - -[Amazon Pinpoint Construct Library](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_pinpoint-readme.html) - - -## Known Issues - -You may encounter the following error when starting the bundler when using Amazon Kinesis (`aws-amplify/analytics/kinesis`), Amazon Kinesis Data Firehose (`aws-amplify/analytics/kinesis-firehose`), Personalize Event (`aws-amplify/analytics/personalize`): - -> Error: Unable to resolve module stream from /path/to/node_modules/@aws-sdk/... This is a [known issue](https://github.com/aws/aws-sdk-js-v3/issues/4877). Please follow [the steps](https://github.com/aws/aws-sdk-js-v3/issues/4877#issuecomment-1656007484) outlined in the issue to resolve the error. - - ---- - ---- -title: "Record events" -section: "build-a-backend/add-aws-services/analytics" -platforms: ["javascript", "react-native", "swift", "android", "flutter", "angular", "nextjs", "react", "vue"] -gen: 2 -last-updated: "2025-06-26T13:57:52.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/analytics/record-events/" ---- - - -## Record event - -The Amplify Analytics plugin makes it easy to record custom events within an app. The plugin handles retry logic in the event that the device loses network connectivity, and it automatically batches requests to reduce network bandwidth. - -#### [Java] - -```java -AnalyticsEvent event = AnalyticsEvent.builder() - .name("PasswordReset") - .addProperty("Channel", "SMS") - .addProperty("Successful", true) - .addProperty("ProcessDuration", 792) - .addProperty("UserAge", 120.3) - .build(); - -Amplify.Analytics.recordEvent(event); -``` - -#### [Kotlin] - -```kotlin -val event = AnalyticsEvent.builder() - .name("PasswordReset") - .addProperty("Channel", "SMS") - .addProperty("Successful", true) - .addProperty("ProcessDuration", 792) - .addProperty("UserAge", 120.3) - .build() - -Amplify.Analytics.recordEvent(event) -``` - -#### [RxJava] - -```java -AnalyticsEvent event = AnalyticsEvent.builder() - .name("PasswordReset") - .addProperty("Channel", "SMS") - .addProperty("Successful", true) - .addProperty("ProcessDuration", 792) - .addProperty("UserAge", 120.3) - .build(); - -RxAmplify.Analytics.recordEvent(event); -``` - - - -The Amazon Pinpoint event count updates in minutes after recording your event. - -However, it can take upwards of 30 minutes for the event to display in the Filter section, and for its custom attributes to appear in Pinpoint. - - - -## Flush events - -Events have a default configuration to flush out to the network every 30 seconds. You can change this value by passing the `autoFlushEventsInterval` option to the `AWSPinpointAnalyticsPlugin`. The option value is measured in milliseconds. - -```kotlin -val options = AWSPinpointAnalyticsPlugin.Options { - autoFlushEventsInterval = 60_000 -} -Amplify.addPlugin(AWSPinpointAnalyticsPlugin(options)) -Amplify.configure(AmplifyOutputs(R.raw.amplify_outputs), this) -``` - -To manually flush events, call: - -#### [Java] - -```java -Amplify.Analytics.flushEvents(); -``` - -#### [Kotlin] - -```kotlin -Amplify.Analytics.flushEvents() -``` - -#### [RxJava] - -```java -RxAmplify.Analytics.flushEvents(); -``` - -When flushing events, a [Hub event](/[platform]/build-a-backend/auth/connect-your-frontend/listen-to-auth-events/) is sent containing the events which were successfully sent to the Pinpoint service. To receive a list of these events, subscribe to the `HubChannel.ANALYTICS` channel and handle an event of the type `AnalyticsChannelEventName.FLUSH_EVENTS`. - -## Global Properties - -You can register global properties which will be sent along with all invocations of `Amplify.Analytics.recordEvent`. - -#### [Java] - -```java -Amplify.Analytics.registerGlobalProperties( - AnalyticsProperties.builder() - .add("AppStyle", "DarkMode") - .build()); -``` - -#### [Kotlin] - -```kotlin -Amplify.Analytics.registerGlobalProperties( - AnalyticsProperties.builder() - .add("AppStyle", "DarkMode") - .build()) -``` - -#### [RxJava] - -```java -RxAmplify.Analytics.registerGlobalProperties( - AnalyticsProperties.builder() - .add("AppStyle", "DarkMode") - .build()); -``` - -To unregister a global property, call `Amplify.Analytics.unregisterGlobalProperties()`: - -#### [Java] - -```java -Amplify.Analytics.unregisterGlobalProperties("AppStyle", "OtherProperty"); -``` - -#### [Kotlin] - -```kotlin -Amplify.Analytics.unregisterGlobalProperties("AppStyle", "OtherProperty") -``` - -#### [RxJava] - -```java -RxAmplify.Analytics.unregisterGlobalProperties("AppStyle", "OtherProperty"); -``` - - - - -## Record event - -The Amplify analytics plugin also makes it easy to record custom events within the app. The plugin handles retry logic in the event the device looses network connectivity and automatically batches requests to reduce network bandwidth. - -```dart -Future recordCustomEvent() async { - final event = AnalyticsEvent('PasswordReset'); - - event.customProperties - ..addStringProperty('Channel', 'SMS') - ..addBoolProperty('Successful', true); - - // You can also add the properties one by one like the following - event.customProperties.addIntProperty('ProcessDuration', 792); - event.customProperties.addDoubleProperty('doubleKey', 120.3); - - await Amplify.Analytics.recordEvent(event: event); -} -``` - - - -The Amazon Pinpoint event count updates in minutes after recording your event. - -However, it can take upwards of 30 minutes for the event to display in the Filter section, and for its custom attributes to appear in Amazon Pinpoint. - - - -## Flush events - -Events have default configuration to flush out to the network every 30 seconds. If you would like to change this, update `amplify_outputs.dart` with the value in milliseconds you would like for `autoFlushEventsInterval`. This configuration will flush events every 10 seconds: - -```json -{ - "Version": "1.0", - "analytics": { - "plugins": { - "awsPinpointAnalyticsPlugin": { - "pinpointAnalytics": { - "appId": "", - "region": "" - }, - "pinpointTargeting": { - "region": "" - }, - "autoFlushEventsInterval": 10 - } - } - } -} -``` - -> **Note** -> -> Setting `autoFlushEventsInterval` to 0 will **disable** the automatic flush of events and you will be responsible for submitting them. - -To manually flush events, call: - -```dart -await Amplify.Analytics.flushEvents(); -``` - -## Global Properties - -You can register global properties which will be sent along with all invocations of `Amplify.Analytics.recordEvent`. - -```dart -Future registerGlobalProperties() async { - final properties = CustomProperties() - ..addStringProperty('AppStyle', 'DarkMode'); - await Amplify.Analytics.registerGlobalProperties( - globalProperties: properties, - ); -} -``` - -To unregister a global property, call `Amplify.Analytics.unregisterGlobalProperties()`: - -```dart -Future unregisterGlobalProperties() async { - await Amplify.Analytics.unregisterGlobalProperties( - propertyNames: ['AppStyle', 'OtherProperty'], - ); -} -``` - -Furthermore, you can remove all global properties by calling `unregisterGlobalProperties` without `propertyNames`: - -```dart -Future unregisterAllGlobalProperties() async { - await Amplify.Analytics.unregisterGlobalProperties(); -} -``` - - - -## Record Event - -The Amplify Analytics plugin provides a simple interface to record custom events within your app: - -```swift -let properties: AnalyticsProperties = [ - "eventPropertyStringKey": "eventPropertyStringValue", - "eventPropertyIntKey": 123, - "eventPropertyDoubleKey": 12.34, - "eventPropertyBoolKey": true -] - -let event = BasicAnalyticsEvent( - name: "eventName", - properties: properties -) - -Amplify.Analytics.record(event: event) -``` - - - -The Amazon Pinpoint event count updates in minutes after recording your event. - -However, it can take upwards of 30 minutes for the event to display in the Filter section, and for its custom attributes to appear in Pinpoint. - - - -## Flush Events - -By default, events are automatically flushed out to the network every 60 seconds. - -You can change this through the `options` parameter when initializing the plugin, by creating a `AWSPinpointAnalyticsPlugin.Options` instance and setting its `autoFlushEventsInterval` property to the desired value, expressed in seconds: - -```swift -let options = AWSPinpointAnalyticsPlugin.Options( - autoFlushEventsInterval: 60 -) -try Amplify.add(plugin: AWSPinpointAnalyticsPlugin(options: options)) -``` - -> **Note** -> -> Setting `autoFlushEventsInterval` to 0 will **disable** the automatic flush of events and you will be responsible for submitting them. - -To manually submit the recoded events to the backend, call: - -```swift -Amplify.Analytics.flushEvents() -``` - -The plugin automatically batches requests in order to reduce network bandwidth and handles the retry logic if the device loses connectivity. - -## Authentication events - -Indicate how frequently users authenticate with your application. - -On the **Analytics** page, the **Users** tab displays charts for **Sign-ins, Sign-ups, and Authentication failures**. - -To learn how frequently users authenticate with your app, update your application code so that Pinpoint receives the following standard event types for authentication: - - - `_userauth.sign_in` - - `_userauth.sign_up` - - `_userauth.auth_fail` - -You can report these events by doing the following: - -```swift -let event = BasicAnalyticsEvent( - name: "_userauth.sign_in" // Or any of the accepted values -) -Amplify.Analytics.record(event: event) -``` - -## Global Properties - -You can register properties which will be included across all `Amplify.Analytics.record(event:)` calls. - -```swift -let globalProperties: AnalyticsProperties = [ - "globalPropertyKey": "value" -] -Amplify.Analytics.registerGlobalProperties(globalProperties) -``` - -To unregister global properties, call `Amplify.Analytics.unregisterGlobalProperties()`: - -```swift -// When called with no arguments, it unregisters all global properties -Amplify.Analytics.unregisterGlobalProperties() - -// Or you can specify which properties to unregister -let globalProperties = ["globalPropertyKey1", "globalPropertyKey2"] -Amplify.Analytics.unregisterGlobalProperties(globalProperties) -``` - - - -## Recording Custom Events - -To record custom events call the `record` API: - -```javascript title="src/index.js" -import { record } from 'aws-amplify/analytics'; - -record({ - name: 'albumVisit', -}); -``` - - - -Analytics events are buffered in memory and periodically sent to the service and not saved locally between application sessions. If the session is ended before a buffered event is sent, it will be lost. Use the `flushEvents` API to manually send buffered events to the service. - - - -## Record a Custom Event with Attributes - -The `record` API lets you add additional attributes to an event. For example, to record _artist_ information with an _albumVisit_ event: - -```javascript title="src/index.js" -import { record } from 'aws-amplify/analytics'; - -record({ - name: 'albumVisit', - attributes: { genre: '', artist: '' }, -}); -``` - -Recorded events will be buffered and periodically sent to Amazon Pinpoint. - -## Record Engagement Metrics - -Metrics can also be added to an event: - -```javascript title="src/index.js" -import { record } from 'aws-amplify/analytics'; - -record({ - name: 'albumVisit', - metrics: { minutesListened: 30 }, -}); -``` - -Metric values must be a `Number` type such as a float or integer. - - - -The Amazon Pinpoint event count updates in minutes after recording your event. - -However, it can take upwards of 30 minutes for the event to display in the Filter section, and for its custom attributes to appear in Amazon Pinpoint. - - - -## Flush events - -The recorded events are saved in a buffer and sent to the remote server periodically. If needed, you have the option to manually clear all the events from the buffer by using the 'flushEvents' API. - -```javascript title="src/index.js" -import { flushEvents } from 'aws-amplify/analytics'; - -flushEvents(); -``` - - ---- - ---- -title: "Identify user" -section: "build-a-backend/add-aws-services/analytics" -platforms: ["swift", "android", "flutter", "javascript", "react-native", "angular", "nextjs", "react", "vue"] -gen: 2 -last-updated: "2024-05-03T20:06:21.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/analytics/identify-user/" ---- - - -This call sends information that you have specified about the user to Amazon Pinpoint. This could be for an unauthenticated or an authenticated user. - -In addition, `customProperties` and `userAttributes` can also be provided when invoking `identifyUser`. The Amazon Pinpoint console makes that data available as part of the criteria for segment creation. Attributes passed in via `customProperties` will appear under **Custom Endpoint Attributes**, while `userAttributes` will appear under **Custom User Attributes**. See the [Amazon Pinpoint documentation](https://docs.aws.amazon.com/pinpoint/latest/userguide/segments-building.html#choosecriteria) for more information on segment creation. - -You can get the current user's ID from the Amplify Auth category as shown below. Be sure you have it added and setup per the [Auth category documentation](/[platform]/build-a-backend/auth/set-up-auth/). - -If you have asked for location access and received permission, you can also provide that in `UserProfile.Location`. - -#### [Java] - -```java -UserProfile.Location location = UserProfile.Location.builder() - .latitude(47.606209) - .longitude(-122.332069) - .postalCode("98122") - .city("Seattle") - .region("WA") - .country("USA") - .build(); - -AnalyticsProperties customProperties = AnalyticsProperties.builder() - .add("property1", "Property value") - .build(); - -AnalyticsProperties userAttributes = AnalyticsProperties.builder() - .add("someUserAttribute", "User attribute value") - .build(); - -AWSPinpointUserProfile profile = AWSPinpointUserProfile.builder() - .name("test-user") - .email("user@test.com") - .plan("test-plan") - .location(location) - .customProperties(customProperties) - .userAttributes(userAttributes) - .build(); - -Amplify.Auth.getCurrentUser(authUser -> { - String userId = authUser.getUserId(); - Amplify.Analytics.identifyUser(userId, profile); -}, exception -> { - Log.e("MyAmplifyApp", "Error getting current user", exception); -}); - -``` - -#### [Kotlin] - -```kotlin -val location = UserProfile.Location.builder() - .latitude(47.606209) - .longitude(-122.332069) - .postalCode("98122") - .city("Seattle") - .region("WA") - .country("USA") - .build(); - -val customProperties = AnalyticsProperties.builder() - .add("property1", "Property value") - .build(); - -val userAttributes = AnalyticsProperties.builder() - .add("someUserAttribute", "User attribute value") - .build(); - -val profile = AWSPinpointUserProfile.builder() - .name("test-user") - .email("user@test.com") - .plan("test-plan") - .location(location) - .customProperties(customProperties) - .userAttributes(userAttributes) - .build(); - -Amplify.Auth.getCurrentUser({ authUser -> - Amplify.Analytics.identifyUser(authUser.userId, profile); -}, { exception -> - Log.e("MyAmplifyApp", "Error getting current user", exception) -}) -``` - -#### [RxJava] - -```java -UserProfile.Location location = UserProfile.Location.builder() - .latitude(47.606209) - .longitude(-122.332069) - .postalCode("98122") - .city("Seattle") - .region("WA") - .country("USA") - .build(); - -AnalyticsProperties customProperties = AnalyticsProperties.builder() - .add("property1", "Property value") - .build(); - -AnalyticsProperties userAttributes = AnalyticsProperties.builder() - .add("someUserAttribute", "User attribute value") - .build(); - -AWSPinpointUserProfile profile = AWSPinpointUserProfile.builder() - .name("test-user") - .email("user@test.com") - .plan("test-plan") - .location(location) - .customProperties(customProperties) - .userAttributes(userAttributes) - .build(); - -RxAmplify.Auth.getCurrentUser() - .subscribe( - result -> { - String userId = result.getUserId(); - RxAmplify.Analytics.identifyUser(userId, profile); - }, - error -> Log.e("AuthQuickStart", error.toString()) - ); -``` - - - - -This call sends information about the current user (which could be unauthenticated or authenticated) to Amazon Pinpoint. - -You can provide `name`, `email` and `plan`, as well as location information with `AnalyticsUserProfile.Location`. You can also send additional custom attributes using `AnalyticsProperties`. - -If the user is signed in through [Amplify.Auth.signIn](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/), then you can retrieve the current user's ID as shown below: - -```swift - -let user = try await Amplify.Auth.getCurrentUser() - -let location = AnalyticsUserProfile.Location( - latitude: 47.606209, - longitude: -122.332069, - postalCode: "98122", - city: "Seattle", - region: "WA", - country: "USA" -) - -let properties: AnalyticsProperties = [ - "phoneNumber": "+11234567890", - "age": 25 -] - -let userProfile = AnalyticsUserProfile( - name: "username", - email: "name@example.com", - plan: "plan", - location: location, - properties: properties -) - -Amplify.Analytics.identifyUser( - userId: user.userId, - userProfile: userProfile -) -``` - - - -This call sends information that you have specified about a user to Amazon Pinpoint. This could be for an unauthenticated (guest) or an authenticated user. - -You can get the current user's ID from the Amplify Auth category as shown per the Auth category documentation. Be sure to have it ready before you set it as shown below (Check out the [Authentication Getting Started](/[platform]/build-a-backend/auth/set-up-auth/) guide for detailed explanation). - -If you have asked for location access and received permission, you can also provide that in `UserProfileLocation` - - - -Breaking changes from v0 to v1: - -The Analytics- prefix of the original `AnalyticsUserProfile` and `AnalyticsUserProfileLocation` classes is removed. Furthermore, `AnalyticsProperties` is renamed to `CustomProperties`. - - - -```dart -Future addAnalyticsWithLocation({ - required String userId, - required String name, - required String email, - required String phoneNumber, - required int age, -}) async { - final userProfile = UserProfile( - name: name, - email: email, - location: const UserProfileLocation( - latitude: 47.606209, - longitude: -122.332069, - postalCode: '98122', - city: 'Seattle', - region: 'WA', - country: 'USA', - ), - customProperties: CustomProperties() - ..addStringProperty('phoneNumber', phoneNumber) - ..addIntProperty('age', age), - ); - - await Amplify.Analytics.identifyUser( - userId: userId, - userProfile: userProfile, - ); -} -``` - - - -This API sends information about the current user to Amazon Pinpoint. - -Additional information such as the user's name, email, location, and device can be included by specifying the `UserProfile`. Custom attributes can also be included by setting `UserProfile.customProperties`. - -If the user was signed in through [signIn](/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/) you can retrieve the current user's ID as shown below: - -```js title="src/index.js" -import { identifyUser } from 'aws-amplify/analytics'; -import { getCurrentUser } from 'aws-amplify/auth'; - -const location = { - latitude: 47.606209, - longitude: -122.332069, - postalCode: '98122', - city: 'Seattle', - region: 'WA', - country: 'USA' -}; - -const customProperties = { - plan: ['plan'], - phoneNumber: ['+11234567890'], - age: ['25'] -}; - -const userProfile = { - location, - name: 'username', - email: 'name@example.com', - customProperties -}; - -async function sendUserData() { - const user = await getCurrentUser(); - - identifyUser({ - userId: user.userId, - userProfile - }); -} -``` - - -Sending user information allows you to associate a user to their user profile and activities or actions in your app. The user's actions and attributes can also tracked across devices and platforms by using the same `userId`. - -Some scenarios for identifying a user and their associated app activities are: -* When a user completes app sign up -* When a user completes sign in process -* When a user launches your app -* When a user modifies or updates their user profile - ---- - ---- -title: "Automatically track sessions" -section: "build-a-backend/add-aws-services/analytics" -platforms: ["javascript", "react-native", "swift", "android", "flutter", "angular", "nextjs", "react", "vue"] -gen: 2 -last-updated: "2024-04-16T15:18:17.000Z" -url: "https://docs.amplify.aws/react/build-a-backend/add-aws-services/analytics/auto-track-sessions/" ---- - - -Analytics auto tracking helps you to automatically track user behaviors like sessions' start/stop, page view change and web events like clicking or mouseover. - -## Session Tracking - -You can track the session both in a web app or a React Native app by using Analytics. A web session can be defined in different ways. To keep it simple, we define a web session as being active when the page is not hidden and inactive when the page is hidden. A session in a React Native app is active when the app is in the foreground and inactive when the app is in the background. - -For example: - -```javascript title="src/index.js" -import { configureAutoTrack } from 'aws-amplify/analytics'; - -configureAutoTrack({ - // REQUIRED, turn on/off the auto tracking - enable: true, - // REQUIRED, the event type, it's one of 'event', 'pageView' or 'session' - type: 'session', - // OPTIONAL, additional options for the tracked event. - options: { - // OPTIONAL, the attributes of the event - attributes: { - customizableField: 'attr' - } - } -}); -``` - -By default, when the page/app transitions to the foreground, the Analytics module will send an event to the Amazon Pinpoint Service. - -```json -{ - "eventType": "_session_start", - "attributes": { - "customizableField": "attr" - } -} -``` - -This behavior can be disabled by calling `configureAutoTrack`: - -```javascript title="src/index.js" -import { configureAutoTrack } from 'aws-amplify/analytics'; - -configureAutoTrack({ - enable: false, - type: 'session' -}); -``` - -## Page View Tracking - -Use this feature to track the most frequently viewed page/url in your webapp. It automatically sends events containing url information when a page is visited. - -This behavior can be enabled by calling `configureAutoTrack`: -```javascript title="src/index.js" -import { configureAutoTrack } from 'aws-amplify/analytics'; - -configureAutoTrack({ - // REQUIRED, turn on/off the auto tracking - enable: true, - // REQUIRED, the event type, it's one of 'event', 'pageView' or 'session' - type: 'pageView', - // OPTIONAL, additional options for the tracked event. - options: { - // OPTIONAL, the attributes of the event - attributes: { - customizableField: 'attr' - }, - - // OPTIONAL, the event name. By default, this is 'pageView' - eventName: 'pageView', - - // OPTIONAL, the type of app under tracking. By default, this is 'multiPageApp'. - // You will need to change it to 'singlePage' if your app is a single-page app like React - appType: 'multiPageApp', - - // OPTIONAL, provide the URL for the event. - urlProvider: () => { - // the default function - return window.location.origin + window.location.pathname; - } - } -}); -``` - -This behavior can be disabled by calling `configureAutoTrack`: -```javascript title="src/index.js" -import { configureAutoTrack } from 'aws-amplify/analytics'; - -configureAutoTrack({ - enable: false, - type: 'pageView' -}); -``` - -## Page Event Tracking - -Use page event tracking to track user interactions with specific elements on a page. Attach the specified selectors to your DOM element and turn on the auto tracking. - -This behavior can be enabled by calling `configureAutoTrack`: -```javascript title="src/index.js" -import { configureAutoTrack } from 'aws-amplify/analytics'; - -configureAutoTrack({ - // REQUIRED, turn on/off the auto tracking - enable: true, - // REQUIRED, the event type, it's one of 'event', 'pageView' or 'session' - type: 'event', - // OPTIONAL, additional options for the tracked event. - options: { - // OPTIONAL, the attributes of the event - attributes: { - customizableField: 'attr' - }, - // OPTIONAL, events you want to track. By default, this is 'click' - events: ['click'], - - // OPTIONAL, the prefix of the selectors. By default, this is 'data-amplify-analytics-' - // Per https://www.w3schools.com/tags/att_global_data.asp, always start - // the prefix with 'data' to avoid collisions with the user agent - selectorPrefix: 'data-amplify-analytics-' - } -}); -``` - -For example: - -```html - -