Sandbox Seed
The Sandbox Seed feature allows you to populate your Amplify sandbox environment with initial data, making it easier to test and develop your application with realistic data.
Setting Up Your Seed Script
To get started, you need to create a seed script. This script will be executed when you deploy your sandbox environment and execute the ampx sandbox seed
command.
- Create a folder named
seed
under youramplify
directory - Create a file named
seed.ts
inside theseed
folder
amplify/ └── seed/ └── seed.ts
Setting up required permissions
In order to seed your sandbox environment you will need to add the necessary permissions to your AWS profile user or execution role. Amplify will generate the necessary permissions which you can attach to your permission set or role containing AmplifyBackendDeployFullAccess
.
- Have an active sandbox environment deployed. You can deploy your sandbox environment using
npx ampx sandbox
. - Generate the required permissions policy template
npx ampx sandbox seed generate-policy > seed-policy.json
- Then attach this policy to your role (example using AWS CLI):
aws iam put-role-policy --role-name <your-role-name> --policy-name <policy-name> --policy-document file://seed-policy.json
Writing your seed script
Once your permissions are set up, you can write your seed script. The @aws-amplify/seed
package provides a set of API's to help you seed your sandbox environment.
Auth
For example, if you like to seed your auth with a user and add them to the admin
group, lets start by creating an auth resource:
import { defineAuth } from "@aws-amplify/backend";
export const auth = defineAuth({ loginWith: { email: true, }, userAttributes: { locale: { required: false, mutable: true, }, }, groups: ["admin"],});
Create 2 secrets using npx ampx sandbox secret set
npx ampx sandbox secret set usernamenpx ampx sandbox secret set password
Now to create a user and add to group admin
, you can write the following script:
import { readFile } from "node:fs/promises";import { addToUserGroup, createAndSignUpUser, getSecret,} from "@aws-amplify/seed";import { Amplify } from "aws-amplify";
// this is used to get the amplify_outputs.json file as the file will not exist until sandbox is createdconst url = new URL("../../amplify_outputs.json", import.meta.url);const outputs = JSON.parse(await readFile(url, { encoding: "utf8" }));Amplify.configure(outputs);
const username = await getSecret("username");const password = await getSecret("password");
const user = await createAndSignUpUser({ username: username, password: password, signInAfterCreation: false, signInFlow: "Password", userAttributes: { locale: "en", },});
await addToUserGroup(user, "admin");
Run the seed script
npx ampx sandbox seed
This will create a user with the username and password you provided and add them to the admin
group.
The getSecret
function will fetch the secret you created using npx ampx sandbox secret set
. This allows you to avoid hardcoding credentials in your code. Alternatively, the username and password can be set directly as a string
in the createAndSignUpUser
function, but we highly recommend using getSecret
to avoid exposing sensitive information in your repository.
Multi-Factor Authentication (MFA)
Amplify seed supports multiple types of MFA including TOTP (Time-based One-Time Password), Email, and SMS. Below are examples of how to implement MFA in your seed script.
Auth with TOTP MFA
For example, if you would like to seed your auth with a user with TOTP MFA enabled, lets start by configuring the auth resource:
import { defineAuth } from "@aws-amplify/backend";
export const auth = defineAuth({ loginWith: { email: true, },
multifactor: { mode: "REQUIRED", totp: true, },});
To create and sign in a user with TOTP MFA enabled, you can write the following script:
For this example, we will use the otpauth
library to generate TOTP codes.
npm install otpauth
import type { ChallengeResponse } from "@aws-amplify/seed";import { readFile } from "node:fs/promises";import { createAndSignUpUser, getSecret,} from "@aws-amplify/seed";import { Amplify } from "aws-amplify";import * as auth from "aws-amplify/auth";import * as otpauth from "otpauth";
// this is used to get the amplify_outputs.json file as the file will not exist until sandbox is createdconst url = new URL("../../amplify_outputs.json", import.meta.url);const outputs = JSON.parse(await readFile(url, { encoding: "utf8" }));Amplify.configure(outputs);
const username = await getSecret("username");const password = await getSecret("password");let totpSecret: string;
const createTOTP = (secret: string) => { return new otpauth.TOTP({ secret, algorithm: "SHA1", digits: 6, period: 30, });};
const generateTOTPCode = (secret: string): string => { const totp = createTOTP(secret); return totp.generate();};
const challengeWithTOTP = async ( totpSetup: auth.SetUpTOTPOutput): Promise<ChallengeResponse> => { totpSecret = totpSetup.sharedSecret; const answer = generateTOTPCode(totpSetup.sharedSecret); return { challengeResponse: answer };};
const user = await createAndSignUpUser({ username, password, signInAfterCreation: false, signInFlow: "MFA", mfaPreference: "TOTP", totpSignUpChallenge: async (totpSetup) => { return await challengeWithTOTP(totpSetup); },});
console.log(`User ${user.username} was created`);
// Wait for a moment to get a fresh TOTP codeawait new Promise((resolve) => setTimeout(resolve, 35000));
const signIn = await signInUser({ username, password, signInFlow: "MFA", signInChallenge: async () => { const answer = generateTOTPCode(totpSecret); return { challengeResponse: answer }; },});
console.log(`User was signed in: ${signIn}`);
auth.signOut();
This will create a user with the username and password with TOTP MFA enabled. The TOTP code is generated using the otpauth
library. The user will then be signed in and the TOTP code will be generated.
The timeout ensures the previous TOTP code expires before generating a new code for sign-in. This prevents potential conflicts that could occur if the same TOTP code were used for both user creation and authentication.
Run the seed script
npx ampx sandbox seed
Auth with Email MFA
For example, if you would like to seed your auth with a user with Email MFA enabled:
import { readFile } from "node:fs/promises";import { createAndSignUpUser, getSecret, signInUser} from "@aws-amplify/seed";import { Amplify } from "aws-amplify";import * as auth from "aws-amplify/auth";
// this is used to get the amplify_outputs.json file as the file will not exist until sandbox is createdconst url = new URL("../../amplify_outputs.json", import.meta.url);const outputs = JSON.parse(await readFile(url, { encoding: "utf8" }));Amplify.configure(outputs);
const username = await getSecret("username");const password = await getSecret("password");
// Set mfaPreference to EMAIL when using email-only MFAconst user = await createAndSignUpUser({ username: username, password: password, signInAfterCreation: false, signInFlow: "MFA", mfaPreference: "EMAIL",});
// Sign in will prompt for MFA code in command lineawait signInUser({ username: username, password: password, signInFlow: "MFA",});
auth.signOut();
This will create a user with the username and password with Email MFA enabled. The user will then be signed in and prompted for the MFA code in the command line.
Run the seed script
npx ampx sandbox seed
SMS MFA follows the same pattern as Email MFA, using command line prompts for verification. Just replace mfaPreference: "EMAIL"
with mfaPreference: "SMS"
in your configuration. The command line experience will be identical, prompting for the SMS code instead of the email code.
Data
For example, if you like to seed your Data API, lets start by creating a GraphQL API with a Todo
model with authorization mode set to userPool
:
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.authenticated()]),});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({ name: "TodoApi", schema, authorizationModes: { defaultAuthorizationMode: "userPool", },});
Now to seed your todo table with a todo, you can write the following script:
import type { Schema } from "../data/resource";import { readFile } from "node:fs/promises";import { getSecret, signInUser } from "@aws-amplify/seed";import { Amplify } from "aws-amplify";import * as auth from "aws-amplify/auth";import { generateClient } from "aws-amplify/api";
// this is used to get the amplify_outputs.json file as the file will not exist until sandbox is createdconst url = new URL("../../amplify_outputs.json", import.meta.url);const outputs = JSON.parse(await readFile(url, { encoding: 'utf8' }));
Amplify.configure(outputs);
const dataClient = generateClient<Schema>();
const username = await getSecret("username");const password = await getSecret("password");
await signInUser({ username: username, password: password, signInFlow: "Password",});
const response = await dataClient.models.Todo.create( { content: `My first todo`, isDone: false, }, { authMode: "userPool", });if (response.errors && response.errors.length > 0) { throw response.errors;}
auth.signOut();
The script uses the Amplify Data API client to interact with the API tables and the signInUser
function to sign in the authenticated user.
Run the seed script
npx ampx sandbox seed
This will create a todo with the content My first todo
and set the isDone
field to false
using the authenticated user.
Storage
import { defineStorage } from "@aws-amplify/backend";
export const storage = defineStorage({ name: "myStorage", isDefault: true, access: (allow) => ({ "pictures/*": [allow.authenticated.to(["write", "list"])], }),});
Now to seed your storage with a picture, you can write the following script:
import { getSecret, signInUser } from "@aws-amplify/seed";import { Amplify } from "aws-amplify";import * as auth from "aws-amplify/auth";import * as storage from "aws-amplify/storage";import { readFile } from 'node:fs/promises';
// this is used to get the amplify_outputs.json file as the file will not exist until sandbox is createdconst url = new URL("../../amplify_outputs.json", import.meta.url);const outputs = JSON.parse(await readFile(url, { encoding: 'utf8' }));Amplify.configure(outputs);
const username = await getSecret("username");const password = await getSecret("password");
await signInUser({ username: username, password: password, signInFlow: "Password",});
const uploadTask = storage.uploadData({ data: `My first content created by ${username}`, path: `pictures/${Math.random().toString()}`,});
await uploadTask.result;
const s3Items = await storage.list({ path: "pictures/", options: { pageSize: 1000, },});
console.log(s3Items.items);
auth.signOut();
Run the seed script
npx ampx sandbox seed
This will create a picture with the content My first content created by ${username}
and save it to the pictures
folder in your storage.
Best Practices
Secure Credentials
When using usernames and passwords in your seed script, use Amplify sandbox secrets to securely store credentials:
- Set secrets using the CLI:
npx ampx sandbox secret set usernamenpx ampx sandbox secret set password
- Retrieve secrets in your seed script:
import { getSecret } from '@aws-amplify/seed'; const username = await getSecret('username');const password = await getSecret('password');
User Session Management
Always sign out users when you are with the operations, this is to avoid any potential issues with the user session:
import * as auth from "aws-amplify/auth";
auth.signOut();
When creating users with createAndSignUpUser
, the signInAfterCreation
parameter controls whether the user is automatically signed in after creation. This parameter has important implications for managing user sessions:
// Create and sign in first userconst user1 = await createAndSignUpUser({ username: 'user1@example.com', password: 'Password123!', signInAfterCreation: true});// At this point, user1 is signed in
// Create second userconst user2 = await createAndSignUpUser({ username: 'user2@example.com', password: 'Password123!', signInAfterCreation: true});// Now user2 is signed in, and user1 has been signed out
// Create third user without auto sign-inconst user3 = await createAndSignUpUser({ username: 'user3@example.com', password: 'Password123!', signInAfterCreation: false});// No user is signed in, as user2 was signed out during user3's creation// and user3 wasn't automatically signed in
Important behaviors to note:
- Setting
signInAfterCreation: true
will automatically sign in the newly created user and sign out any previously signed-in user. - If
signInAfterCreation: false
, the user will not be signed in after creation - Only one user can be signed in at a time
This behavior is particularly important when seeding multiple users in your application, as you'll need to carefully manage which user should be signed out at the end of your seeding process.
User Management Between Multiple Runs of Seed
Seed attempts to create a new user for every instance of createAndSignUpUser
every time it is run.
You may want to use the same user(s) over multiple runs of seed. In this case, using this would avoid any errors that may arise from createAndSignUpUser
attempting to create duplicate users:
const username = await getSecret('username');const password = await getSecret('password');
try { const user = await createAndSignUpUser({ username: username, password: password, signInFlow: 'Password', signInAfterCreation: true });} catch (err) { const error = err as Error; if (error.name === 'UsernameExistsError') { await signInUser({ username: username, password: password, signInFlow: 'Password' }); } else { throw err; }}
MFA Challenge Handling
-
For sign-up challenges, each MFA type has its own specific challenge callback:
- TOTP:
totpSignUpChallenge
- Email:
emailSignUpChallenge
- SMS:
smsSignUpChallenge
- TOTP:
-
For sign-in, there's a single universal
signInChallenge
callback that works with all MFA types (TOTP, Email, or SMS)
Important behaviors:
- Command line prompts work with all MFA types during sign-in
- During sign-up, command line will prompt for EMAIL and SMS MFA, but not for TOTP MFA
- When MFA is set to "Optional" in a user pool, users will be sent through the Password flow
TOTP Considerations
When working with TOTP MFA, be aware of these behaviors:
- Using the same TOTP setup secret multiple times for different TOTP instances will result in an error
- Using the same 6-digit passcode for both sign-up and sign-in (before it expires) will cause an error
- When creating multiple users or performing multiple sign-ins with TOTP:
- Wait for the previous passcode to expire before generating a new one
- The example includes a timeout to handle this:
await new Promise((resolve) => setTimeout(resolve, 35000));
Seed APIs
The @aws-amplify/seed
package provides a set of APIs that are compatible with the Amplify JS Auth APIs to help you seed your sandbox environment.
Secret APIs
Secret APIs use AWS Parameter Store and are compatible with ampx sandbox secret
commands.
-
getSecret - Takes in the name of a secret and returns the secret's value
const secretValue = await getSecret('secretName'); -
setSecret - Takes in the name of a secret and its value, returns the secret's name
const secretName = await setSecret('secretName', 'secretValue');
Auth APIs
Auth APIs allow you to create and manage users in your sandbox environment and are compatible with Amplify JS Auth APIs.
-
createAndSignUpUser - Creates a user based on the properties passed in
const user = await createAndSignUpUser({username: 'username',password: 'password',signInAfterCreation: true,signInFlow: 'Password',userAttributes?: StandardUserAttributes // Optional user attributes}); -
signInUser - Signs in a user using their username, password, and sign-in flow
await signInUser({username: 'username',password: 'password',signInFlow: 'Password' | 'MFA',signInChallenge?: () => Promise<ChallengeResponse> // Optional for MFA}); -
addToUserGroup - Adds a user to an existing user group
await addToUserGroup({username: 'username' // User to add to group}, 'GroupName');
Additional Types
The @aws-amplify/seed
package provides these essential types:
AuthSignUp
- Type for user sign-up configurationAuthUser
- Type for user sign inChallengeResponse
- Type for MFA challenge responsesStandardUserAttributes
- Type for managing user attributes during sign-upAuthOutputs
- Type for user sign-up output
MFA challenge callback types:
emailSignUpChallenge
- Handles Email MFA during sign-upsmsSignUpChallenge
- Handles SMS MFA during sign-uptotpSignUpChallenge
- Handles TOTP MFA during sign-upsignInChallenge
- Universal handler for all MFA types during sign-in
For information on using these APIs, refer to the Amplify JS Auth API documentation.