Rule Writing

CLAUDE.md for AWS Amplify

Amplify Gen 2 uses TypeScript backends — AI generates Gen 1 CLI patterns. Rules for amplify/backend, data models, auth, storage, and function definitions in code.

7 min read·July 1, 2025

Amplify Gen 2 = TypeScript backends. AI generates Gen 1 CLI patterns that don't exist.

Backend as code, data schema builder, Cognito auth, S3 storage rules, and Lambda functions

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
⚠️ No CLI Commands in Gen 2

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"]).'

💡 Auth Rules on Every Model

.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.'

ℹ️ Types Always in Sync

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