Why Amplify Needs Gen 2-Specific Rules
AWS Amplify Gen 2 is a complete rewrite — backends are defined in TypeScript code (amplify/backend.ts), not through the CLI wizard. Data models use TypeScript schema builders, auth uses configuration objects, and functions are Lambda handlers defined in code. AI assistants generate Gen 1 patterns: amplify add api, GraphQL schema files, and CLI-driven configuration that doesn't exist in Gen 2.
Gen 2's 'backend as code' approach means everything is: version-controlled (TypeScript files in git), type-safe (TypeScript compiler catches configuration errors), and reviewable (backend changes are code changes in PRs). AI that generates Gen 1 CLI workflows bypasses all three benefits.
These rules target Amplify Gen 2 with TypeScript. If you're on Gen 1, these rules don't apply — but consider migrating, as Gen 2 is Amplify's future.
Rule 1: Backend as TypeScript Code
The rule: 'Define the entire backend in amplify/backend.ts: import { defineBackend } from "@aws-amplify/backend"; const backend = defineBackend({ auth, data, storage, functions }). Each resource is defined in its own file: amplify/auth/resource.ts, amplify/data/resource.ts, amplify/storage/resource.ts. Never use the CLI (amplify add, amplify push) — backend changes are code changes.'
For the sandbox: 'Use npx ampx sandbox for development — it deploys a personal sandbox environment tied to your git branch. Changes to TypeScript files hot-deploy to the sandbox. Use npx ampx sandbox delete to clean up. The sandbox is isolated — each developer gets their own AWS resources.'
For deployment: 'Connect your git repo to Amplify Hosting for CI/CD. Push to main deploys to production. Push to a feature branch deploys to a preview environment. The backend deploys automatically with the frontend — no separate deployment step.'
- amplify/backend.ts: defineBackend({ auth, data, storage, functions })
- Each resource in its own file: auth/resource.ts, data/resource.ts
- Never CLI commands: no amplify add, no amplify push — code only
- npx ampx sandbox for dev — personal, branch-isolated environment
- Git-connected CI/CD: push to main = production deploy
amplify add api, amplify push, amplify pull — none exist in Gen 2. Backend is defined in TypeScript files. Changes are code changes committed to git. AI generates Gen 1 CLI workflows that produce errors.
Rule 2: Data Models with Schema Builder
The rule: 'Define data models with Amplify's TypeScript schema builder: const schema = a.schema({ Todo: a.model({ content: a.string().required(), isDone: a.boolean().default(false), owner: a.string() }).authorization(allow => [allow.owner()]) }). This generates: DynamoDB tables, AppSync GraphQL API, and typed client library. Use authorization rules on every model — never leave models unprotected.'
For relationships: 'Use a.hasMany, a.belongsTo, a.hasOne for relations: Post: a.model({ comments: a.hasMany("Comment") }); Comment: a.model({ post: a.belongsTo("Post") }). Relations generate efficient GraphQL resolvers — no N+1. Use the typed client: const { data: posts } = await client.models.Post.list({ includes: { comments: true } }).'
For authorization: 'Define auth rules per model and per field: .authorization(allow => [allow.owner(), allow.groups(["admin"]), allow.authenticated().to(["read"])]). Owner-based: only the creator can CRUD. Group-based: specific Cognito groups. Authenticated: any logged-in user (read-only). Public: use sparingly with allow.guest().to(["read"]).'
.authorization(allow => [allow.owner()]) on every data model. Without it, the model is unprotected — any authenticated user can CRUD any record. Owner-based auth is the most common pattern: only the creator can access their data.
Rule 3: Auth Configuration
The rule: 'Define auth in amplify/auth/resource.ts: export const auth = defineAuth({ loginWith: { email: true }, multifactor: { mode: "OPTIONAL", sms: true, totp: true } }). Amplify uses Amazon Cognito — you get: user pools, hosted UI, social sign-in, MFA, email verification, and password policies. Never build custom auth — Cognito is configured through the TypeScript definition.'
For social sign-in: 'Add OAuth providers: loginWith: { email: true, externalProviders: { google: { clientId: secret("GOOGLE_CLIENT_ID"), clientSecret: secret("GOOGLE_CLIENT_SECRET") }, callbackUrls: ["http://localhost:3000/"], logoutUrls: ["http://localhost:3000/"] } }. Use secret() for sensitive values — they're stored in AWS Parameter Store, not in code.'
For the client: 'Use Amplify's auth client: import { signIn, signUp, signOut, getCurrentUser } from "aws-amplify/auth". The client handles: token management, refresh, and session persistence. Auth state integrates with data and storage — authenticated requests include the JWT automatically.'
Rule 4: Storage and Functions
The rule: 'Define storage in amplify/storage/resource.ts: export const storage = defineStorage({ name: "uploads", access: (allow) => ({ "profile-pictures/{entity_id}/*": [allow.entity("identity").to(["read", "write", "delete"])], "public/*": [allow.guest.to(["read"]), allow.authenticated.to(["read", "write"])] }) }). Access rules use path patterns — entity_id is the authenticated user's identity ID.'
For functions: 'Define Lambda functions in amplify/functions/: export const myFunction = defineFunction({ name: "process-order", entry: "./handler.ts" }). The handler is a standard Lambda handler: export const handler = async (event) => { ... }. Connect to data: backend.addOutput({ custom: { functionArn: myFunction.resources.lambda.functionArn } }). Use functions for: webhooks, scheduled tasks, and custom server-side logic.'
For custom resources: 'Use AWS CDK constructs for resources Amplify doesn't cover natively: backend.addOutput({ custom: { ... } }). Import CDK constructs directly — Amplify Gen 2 is built on CDK. Use this for: SQS queues, SNS topics, Step Functions, and any AWS service not covered by Amplify's high-level APIs.'
- Storage with path-based access rules — entity_id for user-scoped files
- Functions: defineFunction with entry handler — standard Lambda
- CDK constructs for custom AWS resources — Amplify Gen 2 is built on CDK
- secret() for sensitive config — stored in Parameter Store, not code
- Functions connect to data/auth/storage through backend references
Rule 5: Typed Client and Frontend Integration
The rule: 'Use Amplify's generated typed client: import { generateClient } from "aws-amplify/data"; const client = generateClient<Schema>(). All CRUD operations are typed: client.models.Todo.create({ content: "Buy milk" }), client.models.Todo.list(), client.models.Todo.update({ id, isDone: true }). Types are generated from your data schema — always in sync.'
For React integration: 'Use Amplify UI components: <Authenticator> for drop-in auth UI, <StorageManager> for file uploads, <AccountSettings> for user settings. Configure Amplify in your app entry: Amplify.configure(outputs) where outputs comes from amplify_outputs.json (auto-generated by the sandbox/deployment).'
For realtime: 'Subscribe to data changes: client.models.Todo.observeQuery().subscribe({ next: ({ items }) => setTodos(items) }). Subscriptions are WebSocket-based through AppSync — live updates without polling. Use for: collaborative features, live dashboards, and chat.'
generateClient<Schema>() creates a typed client from your data schema. Change a model field, the client type updates. No code generation step, no separate interfaces — TypeScript inference keeps everything synchronized.
Complete AWS Amplify Rules Template
Consolidated rules for AWS Amplify Gen 2 projects.
- Backend as TypeScript: amplify/backend.ts — defineBackend, never CLI commands
- Data models: a.schema with TypeScript builder — authorization on every model
- Auth: defineAuth with Cognito — social sign-in, MFA — never custom auth
- Storage: defineStorage with path-based access rules — entity_id for user scoping
- Functions: defineFunction with Lambda handlers — CDK for custom AWS resources
- Typed client: generateClient<Schema>() — all CRUD type-safe from data schema
- npx ampx sandbox for dev — git-connected CI/CD for deployment
- Amplify UI components: Authenticator, StorageManager — observeQuery for realtime