Fix: AWS Amplify Not Working — Gen 2 Backend, defineData, Auth, Storage, and Sandbox Deployments
Quick Answer
How to fix AWS Amplify Gen 2 errors — backend.ts file structure, defineData schema authorization, defineAuth flow, defineStorage bucket access, sandbox vs branch deploy, generated outputs, and Cognito triggers.
The Error
You scaffold a Gen 2 app and npx ampx sandbox errors:
Error: Cannot find module '@aws-amplify/backend'Or your data schema doesn’t generate types:
import { generateClient } from "aws-amplify/data";
import type { Schema } from "../amplify/data/resource";
const client = generateClient<Schema>();
const result = await client.models.Todo.list();
// Type error: Property 'Todo' does not existOr auth flow can’t sign in:
NotAuthorizedException: Incorrect username or password.Or storage upload fails with CORS:
Access to fetch at 'https://s3...' from origin 'https://app.com'
has been blocked by CORS policyWhy This Happens
Amplify Gen 2 (released 2024) is a complete rewrite — TypeScript-first, code-as-infrastructure. Key concepts:
amplify/backend.tsdefines your entire AWS backend. Resources (auth, data, storage) are TypeScript functions that compile to CloudFormation.- Sandbox is a per-developer cloud environment for fast iteration. Auto-deploys on save.
- Branch deploys are persistent environments mapped to Git branches.
- Generated outputs (
amplify_outputs.json) bridges backend → frontend config. - Authorization is declarative — each model field declares who can read/write.
Most issues map to: missing dependencies, wrong directory structure, mismatched generated types, or unconfigured auth flows.
Fix 1: Initialize the Project
npm create amplify@latest
# Or:
npm install -D @aws-amplify/backend @aws-amplify/backend-cli
npm install aws-amplifyProject structure:
my-app/
├── amplify/
│ ├── backend.ts # Entry point
│ ├── auth/
│ │ └── resource.ts # defineAuth
│ ├── data/
│ │ └── resource.ts # defineData (schema)
│ ├── storage/
│ │ └── resource.ts # defineStorage
│ └── functions/
│ └── my-fn/
│ ├── handler.ts
│ └── resource.ts
├── src/
│ └── ... your frontend ...
├── amplify_outputs.json # Generated — DO NOT COMMIT
└── package.jsonamplify/backend.ts:
import { defineBackend } from "@aws-amplify/backend";
import { auth } from "./auth/resource";
import { data } from "./data/resource";
import { storage } from "./storage/resource";
defineBackend({
auth,
data,
storage,
});Start the sandbox:
npx ampx sandboxThis deploys to a personal AWS environment (in your account, isolated by username). Stays in sync as you edit amplify/*.
Pro Tip: Add amplify_outputs.json and .amplify/ to .gitignore. The outputs file is environment-specific and shouldn’t be committed.
Fix 2: Configure the Data Schema
amplify/data/resource.ts:
import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
const schema = a.schema({
Todo: a
.model({
content: a.string().required(),
done: a.boolean().default(false),
ownerId: a.string(),
})
.authorization((allow) => [
allow.owner(), // Only owner can read/write
allow.authenticated().to(["read"]), // Authenticated users can read
]),
Comment: a
.model({
text: a.string().required(),
todoId: a.id().required(),
todo: a.belongsTo("Todo", "todoId"),
})
.authorization((allow) => [allow.authenticated()]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: "userPool",
},
});The authorization rules:
allow.owner()— only the record’s creator. Requires a Cognito user.allow.authenticated()— any logged-in user.allow.publicApiKey()— public read with an API key.allow.guest()— unauthenticated (must have Cognito identity pool)..to(['read', 'create', 'update', 'delete'])— limit operations.
Multiple rules combine — most permissive wins. allow.owner() + allow.authenticated().to(['read']) = owner does all, anyone reads.
Common Mistake: Forgetting to add authorization. Without it, no one can access the model at all — including the schema’s intended users.
Fix 3: Generated Types and Client
After defining your schema, types are generated automatically by the sandbox. To regenerate manually:
npx ampx generate outputs --branch=sandboxamplify_outputs.json is generated. Import it in your frontend:
// src/main.tsx
import { Amplify } from "aws-amplify";
import outputs from "../amplify_outputs.json";
Amplify.configure(outputs);Use the typed client:
import { generateClient } from "aws-amplify/data";
import type { Schema } from "../amplify/data/resource";
const client = generateClient<Schema>();
// Type-safe operations:
const { data: todos, errors } = await client.models.Todo.list();
const { data: newTodo } = await client.models.Todo.create({
content: "Buy milk",
});
const { data: updated } = await client.models.Todo.update({
id: "abc",
done: true,
});
await client.models.Todo.delete({ id: "abc" });
// Subscriptions:
const sub = client.models.Todo.observeQuery().subscribe({
next: ({ items, isSynced }) => {
console.log("todos:", items);
},
});
sub.unsubscribe();For React, the @aws-amplify/ui-react package wraps subscriptions:
import { useEffect, useState } from "react";
import { generateClient } from "aws-amplify/data";
import type { Schema } from "../amplify/data/resource";
const client = generateClient<Schema>();
export function TodoList() {
const [todos, setTodos] = useState<Array<Schema["Todo"]["type"]>>([]);
useEffect(() => {
const sub = client.models.Todo.observeQuery().subscribe({
next: ({ items }) => setTodos(items),
});
return () => sub.unsubscribe();
}, []);
return (
<ul>
{todos.map((t) => (
<li key={t.id}>{t.content}</li>
))}
</ul>
);
}observeQuery returns live updates — any change to the Todo table reflects in real-time.
Pro Tip: Use Schema["ModelName"]["type"] for the model’s TypeScript type. Saves you redefining shapes.
Fix 4: Auth Setup
amplify/auth/resource.ts:
import { defineAuth } from "@aws-amplify/backend";
export const auth = defineAuth({
loginWith: {
email: true,
externalProviders: {
google: {
clientId: secret("GOOGLE_CLIENT_ID"),
clientSecret: secret("GOOGLE_CLIENT_SECRET"),
},
callbackUrls: ["http://localhost:3000/", "https://app.example.com/"],
logoutUrls: ["http://localhost:3000/", "https://app.example.com/"],
},
},
multifactor: {
mode: "OPTIONAL",
sms: true,
totp: true,
},
userAttributes: {
email: { required: true, mutable: false },
"custom:role": { dataType: "String" },
},
});To use secrets (passwords, OAuth client secrets):
npx ampx sandbox secret set GOOGLE_CLIENT_ID
# Prompts for the value, stores in AWS Parameter Store / Secrets Manager.Sign-in code:
import { signIn, signUp, confirmSignUp, signOut, getCurrentUser } from "aws-amplify/auth";
await signUp({
username: "[email protected]",
password: "SuperSecret1!",
options: {
userAttributes: { email: "[email protected]" },
},
});
await confirmSignUp({
username: "[email protected]",
confirmationCode: "123456",
});
await signIn({
username: "[email protected]",
password: "SuperSecret1!",
});
const user = await getCurrentUser();
console.log(user.userId);For React auth UI:
import { Authenticator } from "@aws-amplify/ui-react";
import "@aws-amplify/ui-react/styles.css";
export default function App() {
return (
<Authenticator>
{({ signOut, user }) => (
<main>
<h1>Hello {user?.username}</h1>
<button onClick={signOut}>Sign out</button>
</main>
)}
</Authenticator>
);
}Authenticator handles sign-up / sign-in / forgot password / MFA UI out of the box.
Common Mistake: Adding social providers but not setting redirect URLs. Cognito rejects redirects to URLs not in the allowlist. Add every dev and prod URL to callbackUrls / logoutUrls.
Fix 5: Storage (S3)
amplify/storage/resource.ts:
import { defineStorage } from "@aws-amplify/backend";
export const storage = defineStorage({
name: "myAppStorage",
access: (allow) => ({
"uploads/{entity_id}/*": [
allow.entity("identity").to(["read", "write", "delete"]),
],
"public/*": [
allow.guest.to(["read"]),
allow.authenticated.to(["read", "write"]),
],
"private/{entity_id}/*": [
allow.entity("identity").to(["read", "write", "delete"]),
],
}),
});Three path patterns:
{entity_id}— owner’s identity ID, scoped per-user.- Static paths —
public/*,shared/*— shared across users.
Use:
import { uploadData, downloadData, list, remove } from "aws-amplify/storage";
// Upload:
const result = await uploadData({
path: "uploads/${user.identityId}/avatar.jpg",
data: file,
}).result;
// Download:
const downloaded = await downloadData({ path: "uploads/abc/avatar.jpg" }).result;
const blob = await downloaded.body.blob();
// List:
const files = await list({ path: "uploads/${user.identityId}/" });
// Delete:
await remove({ path: "uploads/abc/avatar.jpg" });The ${...} syntax is a template the storage system fills in. Cleaner than manually computing paths.
For getting a presigned URL:
import { getUrl } from "aws-amplify/storage";
const { url, expiresAt } = await getUrl({
path: "uploads/abc/avatar.jpg",
options: { expiresIn: 300 }, // 5 minutes
});
console.log(url.toString());Common Mistake: Forgetting that S3 requires CORS config for browser uploads. Amplify auto-configures CORS for the bucket — if you customize, preserve the patterns Amplify needs.
Fix 6: Sandbox vs Branch Deploy
Sandbox = per-developer cloud env:
npx ampx sandbox
# Deploys to a temporary stack. Outputs at amplify_outputs.json.
# Auto-redeploys on save.
npx ampx sandbox --once
# One-time deploy, then exit (for CI testing).
npx ampx sandbox delete
# Tear down the sandbox.Branch deploys are persistent — one per Git branch:
# Connect Git in the Amplify Hosting console:
# AWS Console → Amplify → New app → Host web app → connect repo.When you push to main, Amplify deploys a main environment. Pushing to feature/foo deploys a feature/foo environment.
For pull request preview environments (per-PR):
# amplify.yml
backend:
phases:
build:
commands:
- npm ci
- npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_IDBranch deploys are billable as full CloudFormation stacks. Use sandbox for dev; branch for staging/prod.
Pro Tip: Delete unused sandbox stacks (ampx sandbox delete) to avoid lingering AWS resources. Sandboxes don’t auto-clean.
Fix 7: Lambda Functions
For custom backend logic:
// amplify/functions/my-fn/resource.ts
import { defineFunction } from "@aws-amplify/backend";
export const myFn = defineFunction({
name: "my-fn",
entry: "./handler.ts",
environment: {
DB_URL: process.env.DB_URL!,
},
timeoutSeconds: 30,
});// amplify/functions/my-fn/handler.ts
export const handler = async (event: any) => {
console.log("event:", event);
return { statusCode: 200, body: JSON.stringify({ ok: true }) };
};Reference in backend.ts:
import { defineBackend } from "@aws-amplify/backend";
import { auth } from "./auth/resource";
import { data } from "./data/resource";
import { myFn } from "./functions/my-fn/resource";
defineBackend({
auth,
data,
myFn,
});To trigger from a GraphQL operation (custom resolver):
// amplify/data/resource.ts
import { myFn } from "../functions/my-fn/resource";
const schema = a.schema({
// ...
callMyFn: a
.query()
.arguments({ name: a.string() })
.returns(a.string())
.handler(a.handler.function(myFn))
.authorization((allow) => [allow.authenticated()]),
});Now client.queries.callMyFn({ name: "Alice" }) invokes the Lambda.
For Cognito triggers (pre-sign-up, post-confirmation):
// amplify/auth/resource.ts
import { postConfirmation } from "../functions/post-confirmation/resource";
export const auth = defineAuth({
// ...
triggers: {
postConfirmation,
},
});Fix 8: Frontend Configuration
After Amplify.configure(outputs), the SDK is ready. Common patterns:
// src/main.tsx
import { Amplify } from "aws-amplify";
import outputs from "../amplify_outputs.json";
Amplify.configure(outputs);
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")!).render(<App />);For Next.js App Router with SSR:
// app/layout.tsx
import ConfigureAmplifyClientSide from "@/components/ConfigureAmplifyClientSide";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<ConfigureAmplifyClientSide />
{children}
</body>
</html>
);
}// 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;
}{ ssr: true } enables server-side rendering compatibility. Required for Next.js / Remix / SvelteKit.
For Server Component data fetching:
// app/page.tsx
import { cookiesClient } from "@/utils/amplify-server";
export default async function Home() {
const { data: todos } = await cookiesClient.models.Todo.list();
return <ul>{todos.map((t) => <li key={t.id}>{t.content}</li>)}</ul>;
}The cookiesClient is a server-side authenticated client created via generateServerClientUsingCookies.
Still Not Working?
A few less-obvious failures:
- Sandbox deploy fails with
User not authorized. Your AWS credentials don’t have permission. Need IAM with sufficient access for CloudFormation, AppSync, Cognito, S3, Lambda. amplify_outputs.jsonnot found. Sandbox isn’t running, orgenerate outputshasn’t been called. Runnpx ampx sandboxand wait for “Deployment completed.”- Real-time subscriptions don’t fire. WebSocket port blocked. Check your network/firewall.
Schematypes empty. TypeScript hasn’t picked up the generated types. Restart your TS server in VS Code.- CORS errors despite proper auth. Custom resolvers / Lambda might need explicit CORS headers in the response.
- Cognito password policy too strict. Default requires 8+ chars with mixed case, number, symbol. Configure via
passwordPolicyindefineAuthfor laxer dev rules. Cannot read properties of undefinedafterAmplify.configure. Configure ran before outputs loaded. Ensure top-level import order (Amplify first, then anything using it).- Hosted UI redirect loop. Callback URL not in allowlist or hosted UI domain not set. Check Cognito User Pool → App integration.
For related AWS and full-stack issues, see AWS Lambda timeout, Auth.js not working, GraphQL error handling not working, and Next.js server action not working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Pulumi Not Working — Output<T>, Stack References, Secrets, State Backend, and Preview vs Up
How to fix Pulumi errors — Output<T> can't be unwrapped synchronously, stack reference not found, secret leaks in stack outputs, state backend lock, ResourceOptions parent missing, and refresh drift.
Fix: AWS CDK Not Working — Bootstrap Error, ROLLBACK_COMPLETE, and Deploy Failures
How to fix AWS CDK errors — cdk bootstrap required, stack in ROLLBACK_COMPLETE, asset bundling failed, CLI/library version mismatch, VPC lookup failing, and cross-stack export conflicts.
Fix: Analog Not Working — Routes Not Loading, API Endpoints Failing, or Vite Build Errors
How to fix Analog (Angular meta-framework) issues — file-based routing, API routes with Nitro, content collections, server-side rendering, markdown pages, and deployment.
Fix: Angular SSR Not Working — Hydration Failing, Window Not Defined, or Build Errors
How to fix Angular Server-Side Rendering issues — @angular/ssr setup, hydration, platform detection, transfer state, route-level rendering, and deployment configuration.