All skills
aws avatar

/aws-amplify

@7898a91

Build and deploy full-stack web and mobile apps with AWS Amplify Gen2 (TypeScript code-first). Covers auth (Cognito), data (AppSync/DynamoDB), storage (S3), functions, APIs, and AI (Amplify AI Kit with Bedrock). Supports React, Next.js, Vue, Angular, React Native, Flutter, Swift, and Android. Always use this skill for Amplify Gen2 topics — even for questions you think you know — it contains validated, version-specific patterns that prevent common mistakes. TRIGGER when: user mentions Amplify Gen2; project has amplify/ directory or amplify_outputs; code imports @aws-amplify packages; user asks about defineBackend, defineAuth, defineData, defineStorage, defineFunction, or npx ampx. SKIP: Amplify Gen1 (amplify CLI v6), standalone SAM/CDK without Amplify (use aws-serverless), direct Bedrock without Amplify AI Kit (use bedrock).

Use this Skill: https://skilld.dev/gh/aws/agent-toolkit-for-aws/aws-amplify

This session only. Nothing lands on disk.

referencesfunctions-and-api.md

≈3.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Functions & API

Prerequisites: Backend defined in amplify/backend.ts with defineBackend({ auth, data }).

Lambda Functions

Define a function in amplify/functions/<name>/resource.ts:

import { defineFunction } from '@aws-amplify/backend';

export const myFunc = defineFunction({
  name: 'my-func',
  entry: './handler.ts',
  timeoutSeconds: 30, // default 3, max 900
  memoryMB: 512, // default 512
  runtime: 22, // Node.js version (18, 20, 22, 24); default 22
  environment: {
    TABLE_NAME: 'my-table',
    REGION: 'us-east-1',
  },
});

Create the handler at amplify/functions/<name>/handler.ts:

import type { Handler } from 'aws-lambda';
import { env } from '$amplify/env/my-func';

export const handler: Handler = async (event) => {
  const table = env.TABLE_NAME; // typed, from defineFunction environment
  return { statusCode: 200, body: JSON.stringify({ table }) };
};

Import into amplify/backend.ts:

import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { myFunc } from './functions/my-func/resource';
defineBackend({ auth, myFunc });

Sharing Code Between Functions

Each Lambda function is bundled independently from its own directory. Importing from a shared directory (amplify/shared/utils.ts) fails at build time.

Options:

  1. Duplicate the shared code in each function directory
  2. Symlink: ln -s ../../shared/utils.ts amplify/functions/my-fn/utils.ts
  3. Package: Create a local npm package and install it in each function's package.json

Handler Return Types

Handler Type Import Returns
S3Handler @types/aws-lambda void — async event, no response expected
APIGatewayProxyHandler @types/aws-lambda { statusCode, headers?, body }
APIGatewayProxyHandlerV2 @types/aws-lambda { statusCode, headers?, body }

Common mistake: Writing an S3 handler like an API handler. S3Handler returns void, not a response object.

Environment Variables & Secrets

You SHOULD import environment variables from $amplify/env/<function-name> — this provides type-safe access to values defined in defineFunction. Values are also available at runtime via process.env.VAR_NAME, but the $amplify/env import is preferred because it gives you compile-time type checking and autocompletion.

For sensitive values, use secret():

import { defineFunction, secret } from '@aws-amplify/backend';

export const myFunc = defineFunction({
  name: 'my-func',
  entry: './handler.ts',
  environment: {
    API_KEY: secret('MY_API_KEY'),
  },
});

Set secrets via CLI: echo -n "<value>" | npx ampx sandbox secret set MY_API_KEY.

Important: Use echo -n (no trailing newline) when piping values to secret set.

Important: The ampx sandbox secret set command is for local/sandbox development only. For apps deployed to Amplify Hosting, secrets must be created via the Amplify console (NOT ampx sandbox secret — that's local only) — sandbox secrets are NOT available in hosted environments. See: https://docs.amplify.aws/react/deploy-and-host/fullstack-branching/secrets-and-vars/#set-secrets

Environment Variables in Lambda

Recommended (type-safe):

import { env } from '$amplify/env/my-function';
const tableName = env.TABLE_NAME;

Fallback (if $amplify/env causes esbuild bundling errors):

const tableName = process.env.TABLE_NAME!;

Scheduled Functions

Use schedule to invoke a function on a cron or natural-language schedule:

import { defineFunction } from '@aws-amplify/backend';

export const cronJob = defineFunction({
  name: 'cron-job',
  entry: './handler.ts',
  schedule: 'every 1h', // natural-language shorthand
  // Valid shorthands: 'every 5m', 'every 1h', 'every day', 'every week', 'every month', 'every year'
  // OR: schedule: '0 */1 * * ? *', // cron expression — same property
});

The handler must use EventBridgeHandler type:

import type { EventBridgeHandler } from 'aws-lambda';
export const handler: EventBridgeHandler<'Scheduled Event', void, void> = async () => {
  // scheduled logic
};

Resource Access

Grant a function access to other Amplify resources:

const backend = defineBackend({ auth, data, storage, myFunc });

// Grant function access to auth, data, and storage
backend.myFunc.resources.lambda.addEnvironment(
  'USER_POOL_ID', backend.auth.resources.userPool.userPoolId
);
backend.data.resources.tables['Todo'].grantReadData(backend.myFunc.resources.lambda);
backend.storage.resources.bucket.grantReadWrite(backend.myFunc.resources.lambda);

For data schema access, use allow.resource() in authorization rules:

const schema = a.schema({
  Todo: a.model({
    content: a.string(),
  }).authorization(allow => [allow.resource(myFunc)]),
});

Lambda + API Gateway + Data Access

When a Lambda both accesses data tables AND is exposed via API Gateway, use resourceGroupName to avoid circular dependencies:

export const myFunction = defineFunction({
  name: 'my-function',
  resourceGroupName: 'data', // Places in data stack to avoid circular dep
});

Custom Queries and Mutations

Use a.query() and a.mutation() with .handler() to add custom server-side logic through AppSync (no API Gateway needed):

// amplify/data/resource.ts
const schema = a.schema({
  // Custom query with Lambda handler
  summarize: a.query()
    .arguments({ text: a.string().required() })
    .returns(a.string())
    .handler(a.handler.function(summarizeHandler))
    .authorization(allow => [allow.authenticated()]),

  // Custom mutation with Lambda handler
  processOrder: a.mutation()
    .arguments({ orderId: a.string().required() })
    .returns(a.json())
    .handler(a.handler.function(processOrderHandler))
    .authorization(allow => [allow.authenticated()]),
});

How .handler() works: .handler() grants AppSync the permission to invoke the Lambda (AppSync→Lambda). The Lambda IS the resolver — it receives the GraphQL event directly. If the Lambda also needs to call the Data API or access DynamoDB tables for side effects, add allow.resource(fn) to the model with resourceGroupName: 'data' on the function to avoid circular dependencies.

// ❌ CIRCULAR DEPENDENCY — manual table grant in backend.ts
backend.data.resources.tables["Model"].grantReadData(backend.myFn.resources.lambda);

// ✅ Use resourceGroupName to co-locate the function in the data stack
const myFn = defineFunction({ name: 'my-fn', resourceGroupName: 'data' });
// Then in the schema: allow.resource(myFn) on the model

Gap: The Lambda resolver receives the GraphQL event but does NOT automatically get TABLE_NAME as an environment variable. Your Lambda must either:

  1. Use the Amplify data client (generateClient()) which discovers tables automatically
  2. Explicitly set env vars: myFunction.addEnvironment('TABLE_NAME', backend.data.resources.tables['Todo'].tableName)

When to use which:

  • a.query() / a.mutation() with .handler() — AppSync-native, type-safe, uses the data schema. Preferred for most custom logic.
  • API Gateway + Lambda — Use when you need REST endpoints, webhooks, or third-party integrations that require a specific URL.

REST API (API Gateway)

Create a REST API using CDK in amplify/backend.ts:

import { defineBackend } from '@aws-amplify/backend';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import { myFunc } from './functions/my-func/resource';

const backend = defineBackend({ auth, myFunc });
const apiStack = backend.createStack('RestApiStack');

const api = new apigateway.RestApi(apiStack, 'MyRestApi', {
  restApiName: 'my-rest-api',
  deployOptions: { stageName: 'prod' },
});
api.root.addResource('items').addMethod(
  'GET', new apigateway.LambdaIntegration(backend.myFunc.resources.lambda)
);

backend.addOutput({ custom: { restApiUrl: api.url } });

The handler must use APIGatewayProxyHandler type for REST API (v1):

import type { APIGatewayProxyHandler } from 'aws-lambda';

HTTP API (API Gateway v2)

For a lightweight HTTP API:

import type { APIGatewayProxyHandlerV2 } from 'aws-lambda';
import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2';
import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';

const httpApi = new apigwv2.HttpApi(apiStack, 'MyHttpApi', {
  corsPreflight: { allowOrigins: ['*'], allowMethods: [apigwv2.CorsHttpMethod.GET] },
});
httpApi.addRoutes({
  path: '/items',
  methods: [apigwv2.HttpMethod.GET],
  integration: new HttpLambdaIntegration('GetItems', backend.myFunc.resources.lambda),
});

backend.addOutput({ custom: { httpApiUrl: httpApi.url! } });

The handler must use APIGatewayProxyHandlerV2 type for HTTP API (v2).

Backend Outputs

Use backend.addOutput() to expose custom values to the frontend via amplify_outputs.json:

backend.addOutput({ custom: { apiUrl: api.url, region: 'us-east-1' } });

Frontend reads custom outputs from the configured Amplify outputs.

Calling from Client

For custom queries and mutations defined via a.query() or a.mutation(), call them from the client:

const { data } = await client.queries.summarize({ text: '...' });

For REST/HTTP API outputs added via backend.addOutput(), read the endpoint URL from amplify_outputs.json and use standard HTTP clients.

Pitfalls

  • runtime must be an integer: Use runtime: 22, NOT runtime: "nodejs22.x". String format causes build errors.

  • Wrong handler type: REST API (v1) requires APIGatewayProxyHandler with event.httpMethod; HTTP API (v2) requires APIGatewayProxyHandlerV2 with event.requestContext.http.method. Mixing them causes malformed responses. Both return { statusCode, body }.

  • Missing resource access: A function without explicit grants cannot access auth, data, or storage resources — add grants in backend.ts.

  • Secrets in plain environment: Sensitive values must use secret(), not string literals.

  • createStack name collision: Stack names passed to backend.createStack() must be unique across the backend. Duplicate names cause deployment failures.

  • Missing @types/node: Lambda functions require @types/node in devDependencies. Without it, process.env and Node.js globals cause TypeScript errors. Install: npm install --save-dev @types/node

  • @types/aws-lambda: Lambda handlers (S3Handler, PreSignUpTriggerHandler, etc.) need this package for TypeScript types. Install at project root or in the function's directory if it has its own package.json.

  • AppSync identity typing: event.identity in custom handlers has varying types depending on auth mode. Use type assertion:

    const identity = event.identity as { username?: string; sub?: string };
    const userId = identity?.username || identity?.sub || 'unknown';
  • dataSource: 'NONE': Using a.handler.custom({ dataSource: 'NONE' }) causes "Data source not found" during deployment. Use a Lambda handler instead, or create the NONE data source explicitly via CDK.

  • Lambda error types lost: Custom error classes thrown in Lambda arrive at the frontend as generic Error with only the message preserved. Error name, stack, and custom properties are stripped by AppSync. Return structured error data in the response instead.

Links

Source: SKILL.md on GitHub

No alerts16d3 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides comprehensive instructions for building applications with AWS Amplify Gen2. It includes security considerations such as the use of command-line tools for cloud resource management and the ingestion of user-provided data for infrastructure generation. These patterns are standard for developer assistant tools and are supported by best-practice recommendations within the skill, such as secure secret management.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

Signed by skilld at 7898a91. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 3 months ago

README badge

README badge for aws/agent-toolkit-for-aws/aws-amplify