Page updated Mar 19, 2024

Preview: AWS Amplify's new code-first DX (Gen 2)

The next generation of Amplify's backend building experience with a TypeScript-first DX.

Get started

Set up custom queries and mutations

You are currently viewing the new GraphQL transformer v2 docs Looking for legacy docs?

Define your custom business logic in a Lambda function resolver, HTTP resolver, or an AppSync JavaScript or VTL resolver and expose them in a GraphQL query or mutation. Extend or override Amplify-generated GraphQL resolvers to optimize for your specific use cases.

Create a custom query or mutation

While @model automatically generates dedicated "create", "read", "update", "delete", and "subscription" queries or mutations for you, there are some cases where you want to define a stand-alone query or mutation.

  1. Define your custom query or mutation
1type Mutation {
2 myCustomMutation(args: String): String # your custom mutations here
3}
4
5type Query {
6 myCustomQuery(args: String): String # your custom queries here
7}
  1. Use one of these resolver choices to handle the query or mutation request:

  2. Secure your custom query or mutation with field-level authorization rules

    • Note: Dynamic authorization rules are not supported on a custom query or mutation.

Lambda function resolver

The @function directive allows you to quickly & easily configure a AWS Lambda resolvers with your GraphQL API. You can use any AWS Lambda functions that was created with the Amplify CLI, AWS CDK or reference an existing AWS Lambda function created with any other means.

For example, use amplify add function to add a Lambda function called "echofunction" with the following handler:

1exports.handler = async function (event, context) {
2 return event.arguments.msg;
3};

To connect an AWS Lambda resolver to the GraphQL API, add the @function directive to a field in your schema.

1type Query {
2 echo(msg: String): String @function(name: "echofunction-${env}")
3}

The Amplify CLI provides support for maintaining multiple environments. When you deploy a function via amplify add function, it will automatically add the environment suffix to your Lambda function name. For example, if you create a function named echofunction using amplify add function in the dev environment, the deployed function will be named echofunction-dev. The @function directive allows you to use ${env} to reference the current Amplify CLI environment.

First, create your Lambda function in CDK with your logic and set the functionName parameter.

1const echoLambda = new lambda.Function(this, 'EchoLambda', {
2 functionName: 'echofunction', // MAKE SURE THIS MATCHES THE @function's "name" PARAMETER
3 code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/echo')),
4 handler: 'index.handler',
5 runtime: lambda.Runtime.NODEJS_18_X
6});
7
8const amplifyApi = new AmplifyGraphqlApi(this, 'AmplifyCdkGraphQlApi', {
9 definition: AmplifyGraphqlDefinition.fromFiles(
10 path.join(__dirname, 'schema.graphql')
11 ),
12 authorizationModes: {
13 defaultAuthorizationMode: 'API_KEY',
14 apiKeyConfig: {
15 expires: cdk.Duration.days(30)
16 }
17 }
18});

To connect an AWS Lambda resolver to the GraphQL API, add the @function directive to a field in your GraphQL schema.

1type Query {
2 echo(msg: String): String @function(name: "echofunction")
3}

Optionally, if you don't want to hard-code the function name into the GraphQL schema, you can also set an arbitrary name in the GraphQL schema and then map a function within CDK to the function name.

1const coolLambdaFunction = new lambda.Function(this, 'MyCoolLambdaFunction', {
2 code: lambda.Code.fromAsset(path.join(__dirname, 'handlers/echo')),
3 handler: 'index.handler',
4 runtime: lambda.Runtime.NODEJS_18_X
5});
6
7const amplifyApi = new AmplifyGraphqlApi(this, 'AmplifyCdkGraphQlApi', {
8 definition: AmplifyGraphqlDefinition.fromFiles(
9 path.join(__dirname, 'schema.graphql')
10 ),
11 authorizationModes: {
12 defaultAuthorizationMode: 'API_KEY',
13 apiKeyConfig: {
14 expires: cdk.Duration.days(30)
15 }
16 },
17 functionNameMap: {
18 echofunction: coolLambdaFunction // Remap the function name to any function you define or reference within CDK.
19 }
20});

If you deployed your Lambda function without Amplify CLI then you must provide the full Lambda function name in the name parameter. If you deployed the same function with the name echofunction then you would have:

1type Query {
2 echo(msg: String): String @function(name: "echofunction")
3}

Structure of the function event

When writing Lambda functions that are connected via the @function directive, you can expect the following structure for the AWS Lambda event object.

KeyDescription
typeNameThe name of the parent object type of the field being resolved.
fieldNameThe name of the field being resolved.
argumentsA map containing the arguments passed to the field being resolved.
identityA map containing identity information for the request. Contains a nested key 'claims' that will contains the JWT claims if they exist.
sourceWhen resolving a nested field in a query, the source contains parent value at runtime. For example when resolving Post.comments, the source will be the Post object.
requestThe AppSync request object. Contains header information.
prevWhen using pipeline resolvers, this contains the object returned by the previous function. You can return the previous value for auditing use cases.

Your function should follow the Lambda handler guidelines for your specific language. See the developer guides from the AWS Lambda documentation for your chosen language. If you choose to use structured types, your type should serialize the AWS Lambda event object outlined above. For example, if using Golang, you should define a struct with the above fields.

Calling functions in different regions

By default, you expect the function to be in the same region as the Amplify project. If you need to call a function in a different or a specific region, you can provide the region argument.

1type Query {
2 echo(msg: String): String @function(name: "echofunction", region: "us-east-1")
3}

Calling functions in different AWS accounts is not supported via the @function directive but is supported by AWS AppSync.

Chaining functions

You can chain together multiple @function resolvers such that they are invoked in series when your field's resolver is invoked. To create a pipeline resolver that calls to multiple AWS Lambda functions in series, use multiple @function directives on the field. Similarly, @function can be combined with field-level @auth. When combining these field directives, the ordering in the schema matches the ordering in the pipeline resolver. You can choose to have functions before and/or after field level authorization is applied.

Note: Be careful when using @auth directives on a field in a root type. @auth directives on field definitions use the source object to perform authorization logic and the source will be an empty object for fields on root types. Static group authorization should perform as expected.

1type Mutation {
2 doSomeWork(msg: String): String
3 @function(name: "worker-function")
4 @function(name: "audit-function")
5}

In the example above when you run a mutation that calls the Mutation.doSomeWork field, the worker-function will be invoked first then the audit-function will be invoked with an event that contains the results of the worker-function under the event.prev.result key. The audit-function would need to return event.prev.result if you want the result of worker-function to be returned for the field.

How it works

Definition of @function directive:

1directive @function(name: String!, region: String) on FIELD_DEFINITION

Under the hood, Amplify creates an AppSync::FunctionConfiguration for each unique instance of @function in a document and a pipeline resolver containing a pointer to a function for each @function on a given field.

The @function directive generates these resources as necessary:

  1. An AWS IAM role that has permission to invoke the function as well as a trust policy with AWS AppSync.
  2. An AWS AppSync data source that registers the new role and existing function with your AppSync API.
  3. An AWS AppSync pipeline function that prepares the lambda event and invokes the new data source.
  4. An AWS AppSync resolver that attaches to the GraphQL field and invokes the new pipeline functions.

HTTP resolver

The @http directive allows you to quickly configure HTTP resolvers within your GraphQL API.

To connect to an endpoint, add the @http directive to a field in your GraphQL schema. The directive allows you to define URL path parameters, and specify a query string and/or specify a request body. For example, given the definition of a Post type:

1type Post {
2 id: ID!
3 title: String
4 description: String
5 views: Int
6}
7
8type Query {
9 listPosts: [Post] @http(url: "https://www.example.com/posts")
10}

Amplify generates the definition below that sends a request to the url when the listPosts query is used.

1type Query {
2 listPosts: [Post]
3}

Request headers

The @http directive generates resolvers that can handle XML and JSON responses. If an HTTP method is not defined, GET is used. You can specify a list of static headers to be passed with the HTTP requests to your backend in your directive definition.

1type Query {
2 listPosts: [Post]
3 @http(
4 url: "https://www.example.com/posts"
5 headers: [{ key: "X-Header", value: "X-Header-Value" }]
6 )
7}

Path parameters

You can create dynamic paths by specifying parameters in the directive URL by using the special :<parameter> notation. Your set of parameters can then be specified in the params input object of the query. Note that path parameters are not added to the request body or query string. You can define multiple parameters.

1type Query {
2 getPost: Post @http(url: "https://www.example.com/posts/:id")
3}

In the example above, the :id parameter will generate the appropriate query input as shown below:

1type Query {
2 getPost(params: QueryGetPostParamsInput!): Post
3}
4
5input QueryGetPostParamsInput {
6 id: String!
7}

You can fetch a specific post by enclosing the id in the params input object.

1query post {
2 getPost(params: { id: "POST_ID" }) {
3 id
4 title
5 }
6}

This executes the following request:

1GET /posts/POST_ID
2Host: www.example.com

Query String

You can send a query string with your request by specifying variables for your query. The query string is supported with all request methods.

Given the definition

1type Query {
2 listPosts(sort: String!, from: String!, limit: Int!): Post
3 @http(url: "https://www.example.com/posts")
4}

Amplify generates

1type Query {
2 listPosts(query: QueryListPostsQueryInput!): [Post]
3}
4
5input QueryListPostsQueryInput {
6 sort: String!
7 from: String!
8 limit: Int!
9}

You can query for posts using the query input object

1query posts {
2 listPosts(query: { sort: "DESC", from: "last-week", limit: 5 }) {
3 id
4 title
5 description
6 }
7}

which sends the following request:

1GET /posts?sort=DESC&from=last-week&limit=5
2Host: www.example.com

Request Body

The @http directive also allows you to specify the body of a request, which is used for POST, PUT, and PATCH requests. To create a new post, you can define the following.

1type Mutation {
2 addPost(title: String!, description: String!, views: Int): Post
3 @http(method: POST, url: "https://www.example.com/post")
4}

Amplify generates the addPost query field with the query and body input objects since this type of request also supports a query string. The generated resolver verifies that non-null arguments (e.g.: the title and description) are passed in at least one of the input objects; if not, an error is returned.

1type Mutation {
2 addPost(query: QueryAddPostQueryInput, body: QueryAddPostBodyInput): Post
3}
4
5input QueryAddPostQueryInput {
6 title: String
7 description: String
8 views: Int
9}
10
11input QueryAddPostBodyInput {
12 title: String
13 description: String
14 views: Int
15}

You can add a post by using the body input object:

1mutation add {
2 addPost(body: { title: "new post", description: "fresh content" }) {
3 id
4 }
5}

which will send

1POST /post
2Host: www.example.com
3{
4 title: "new post"
5 description: "fresh content"
6}

Reference Amplify environment name

The @http directive allows you to use ${env} to reference the current Amplify CLI environment.

1type Query {
2 listPosts: Post @http(url: "https://www.example.com/${env}/posts")
3}

which, in the DEV environment, will send

1GET /DEV/posts
2Host: www.example.com

Combining the different components

You can use a combination of parameters, query, body, headers, and environments in your @http directive definition.

Given the definition

1type Post {
2 id: ID!
3 title: String
4 description: String
5 views: Int
6 comments: [Comment]
7}
8
9type Comment {
10 id: ID!
11 content: String
12}
13
14type Mutation {
15 updatePost(
16 title: String!
17 description: String!
18 views: Int
19 withComments: Boolean
20 ): Post
21 @http(
22 method: PUT
23 url: "https://www.example.com/${env}/posts/:id"
24 headers: [{ key: "X-Header", value: "X-Header-Value" }]
25 )
26}

you can update a post with

1mutation update {
2 updatePost(
3 body: { title: "new title", description: "updated description", views: 100 }
4 params: { id: "EXISTING_ID" }
5 query: { withComments: true }
6 ) {
7 id
8 title
9 description
10 comments {
11 id
12 content
13 }
14 }
15}

which, in the DEV environment, will send

1PUT /DEV/posts/EXISTING_ID?withComments=true
2Host: www.example.com
3X-Header: X-Header-Value
4{
5 title: "new title"
6 description: "updated description"
7 views: 100
8}

Reference existing field data

In some cases, you may want to send a request based on existing field data. Take a scenario where you have a post and want to fetch comments associated with the post in a single query. Let's use the previous definition of Post and Comment.

1type Post {
2 id: ID!
3 title: String
4 description: String
5 views: Int
6 comments: [Comment]
7}
8
9type Comment {
10 id: ID!
11 content: String
12}

A post can be fetched at /posts/:id and a post's comments at /posts/:id/comments. You can fetch the comments based on the post id with the following updated definition. $ctx.source is a map that contains the resolution of the parent field (Post) and gives access to id.

1type Post {
2 id: ID!
3 title: String
4 description: String
5 views: Int
6 comments: [Comment]
7 @http(url: "https://www.example.com/posts/${ctx.source.id}/comments")
8}
9
10type Comment {
11 id: ID!
12 content: String
13}
14
15type Query {
16 getPost: Post @http(url: "https://www.example.com/posts/:id")
17}

You can retrieve the comments of a specific post with the following query and selection set.

1query post {
2 getPost(params: { id: "POST_ID" }) {
3 id
4 title
5 description
6 comments {
7 id
8 content
9 }
10 }
11}

Assuming that getPost retrieves a post with the id POST_ID, the comments field is resolved by sending this request to the endpoint

1GET /posts/POST_ID/comments
2Host: www.example.com

Note that there is no check to ensure that the reference variable (here the post ID) exists. When using this technique, it is recommended to make sure the referenced field is non-null.

How it works

Definition of @http directive:

1directive @http(
2 method: HttpMethod
3 url: String!
4 headers: [HttpHeader]
5) on FIELD_DEFINITION
6enum HttpMethod {
7 PUT
8 POST
9 GET
10 DELETE
11 PATCH
12}
13input HttpHeader {
14 key: String
15 value: String
16}

The @http transformer will create one HTTP datasource for each identified base URL. For example, if multiple HTTP resolvers are created that interact with the "https://www.example.com" endpoint, only a single datasource is created. Each directive generates one resolver. Depending on the definition, the appropriate body, params, and query input types are created. Note that @http transformer does not support calling other AWS services where Signature Version 4 signing process is required.

AppSync JavaScript or VTL resolver

You can use AWS Cloud Development Kit (CDK) to define custom AppSync resolvers for your GraphQL API. @auth directives are not supported for custom queries or mutations that are connected to a JavaScript or VTL resolver. This is because you are replacing Amplify's auto-generated capabilities for that particular query or mutation with a custom-defined cloud resources.

1amplify add custom
1? How do you want to define this custom resource?
2❯ AWS CDK
3? Provide a name for your custom resource
4❯ MyCustomResolvers

Next, install the AppSync dependencies for your custom resource:

1cd amplify/backend/custom/MyCustomResolvers
2npm i @aws-cdk/aws-appsync@~1.172.0

Note: Installations using the '~' character do not modify the package.json. To use '~' for default npm configurations, make sure your package.json reflects the right dependency to avoid compatibility errors in CDK.

Finally, add your custom resolvers into the cdk-stack.ts file. You can either add the JavaScript or VTL inline into your cdk-stack.ts file.

Unit Resolver

Review the AppSync JavaScript resolver tutorial for JavaScript resolver examples for different data sources.

1import * as cdk from 'aws-cdk-lib';
2import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper';
3import * as appsync from 'aws-cdk-lib/aws-appsync';
4import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref';
5import { Construct } from 'constructs';
6
7const jsResolverTemplate = `
8export function request(ctx) {
9 return {
10 payload: null
11 }
12}
13
14export function response(ctx) {
15 return ctx.arguments.message
16}
17`
18
19export class cdkStack extends cdk.Stack {
20 constructor(
21 scope: Construct,
22 id: string,
23 props?: cdk.StackProps,
24 amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps
25 ) {
26 super(scope, id, props);
27 /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
28 new cdk.CfnParameter(this, 'env', {
29 type: 'String',
30 description: 'Current Amplify CLI env name'
31 });
32
33 // Access other Amplify Resources
34 const retVal: AmplifyDependentResourcesAttributes =
35 AmplifyHelpers.addResourceDependency(
36 this,
37 amplifyResourceProps.category,
38 amplifyResourceProps.resourceName,
39 [
40 {
41 category: 'api',
42 resourceName: '<YOUR-API-NAME>'
43 }
44 ]
45 );
46
47 const resolver = new appsync.CfnResolver(this, 'CustomResolver', {
48 // apiId: retVal.api.new.GraphQLAPIIdOutput,
49 // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887
50 // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack.
51 // Previously the ApiId is the variable Name which is wrong , it should be variable value as below
52 apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput),
53 fieldName: 'echo',
54 typeName: 'Query', // Query | Mutation | Subscription
55 code: jsResolverTemplate,
56 dataSourceName: 'NONE_DS', // DataSource name
57 runtime: {
58 name: 'APPSYNC_JS',
59 runtimeVersion: '1.0.0'
60 }
61 });
62 }
63}

Review the Resolver Mapping Template Programming Guide to learn more about the VTL template.

1import * as cdk from 'aws-cdk-lib';
2import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper';
3import * as appsync from 'aws-cdk-lib/aws-appsync';
4import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref';
5import { Construct } from 'constructs';
6
7const requestVTL = `
8<YOUR CUSTOM VTL REQUEST MAPPING TEMPLATE HERE>
9`;
10const responseVTL = `
11<YOUR CUSTOM VTL RESPONSE MAPPING TEMPLATE HERE>
12`;
13
14export class cdkStack extends cdk.Stack {
15 constructor(
16 scope: Construct,
17 id: string,
18 props?: cdk.StackProps,
19 amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps
20 ) {
21 super(scope, id, props);
22 /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
23 new cdk.CfnParameter(this, 'env', {
24 type: 'String',
25 description: 'Current Amplify CLI env name'
26 });
27
28 // Access other Amplify Resources
29 const retVal: AmplifyDependentResourcesAttributes =
30 AmplifyHelpers.addResourceDependency(
31 this,
32 amplifyResourceProps.category,
33 amplifyResourceProps.resourceName,
34 [
35 {
36 category: 'api',
37 resourceName: '<YOUR-API-NAME>'
38 }
39 ]
40 );
41
42 const resolver = new appsync.CfnResolver(this, 'custom-resolver', {
43 // apiId: retVal.api.new.GraphQLAPIIdOutput,
44 // https://github.com/aws-amplify/amplify-cli/issues/9391#event-5843293887
45 // If you use Amplify you can access the parameter via Ref since it's a CDK parameter passed from the root stack.
46 // Previously the ApiId is the variable Name which is wrong , it should be variable value as below
47 apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput),
48 fieldName: 'querySomething',
49 typeName: 'Query', // Query | Mutation | Subscription
50 requestMappingTemplate: requestVTL,
51 responseMappingTemplate: responseVTL,
52 dataSourceName: 'TodoTable' // DataSource name
53 });
54 }
55}

Pipeline Resolver

1import * as cdk from 'aws-cdk-lib';
2import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper';
3import * as appsync from 'aws-cdk-lib/aws-appsync';
4import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref';
5import { Construct } from 'constructs';
6
7const beforeMappingVTL = `
8<YOUR CUSTOM VTL REQUEST MAPPING TEMPLATE HERE>
9`;
10const afterMappingVTL = `
11<YOUR CUSTOM VTL RESPONSE MAPPING TEMPLATE HERE>
12`;
13const function1requestVTL = `
14<YOUR CUSTOM VTL FUNCTION 1 REQUEST MAPPING TEMPLATE HERE>
15`;
16const function1responseVTL = `
17<YOUR CUSTOM VTL FUNCTION 1 RESPONSE MAPPING TEMPLATE HERE>
18`;
19const function2requestVTL = `
20<YOUR CUSTOM VTL FUNCTION 2 REQUEST MAPPING TEMPLATE HERE>
21`;
22const function2responseVTL = `
23<YOUR CUSTOM VTL FUNCTION 2 RESPONSE MAPPING TEMPLATE HERE>
24`;
25export class cdkStack extends cdk.Stack {
26 constructor(
27 scope: Construct,
28 id: string,
29 props?: cdk.StackProps,
30 amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps
31 ) {
32 super(scope, id, props);
33 /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
34 new cdk.CfnParameter(this, 'env', {
35 type: 'String',
36 description: 'Current Amplify CLI env name'
37 });
38
39 // Access other Amplify Resources
40 const retVal: AmplifyDependentResourcesAttributes =
41 AmplifyHelpers.addResourceDependency(
42 this,
43 amplifyResourceProps.category,
44 amplifyResourceProps.resourceName,
45 [
46 {
47 category: 'api',
48 resourceName: '<YOUR-API-NAME>'
49 }
50 ]
51 );
52
53 const function1 = new appsync.CfnFunctionConfiguration(this, 'function1', {
54 apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput),
55 dataSourceName: 'NONE_DS', // DataSource name
56 functionVersion: '2018-05-29',
57 name: 'function1',
58 requestMappingTemplate: function1requestVTL,
59 responseMappingTemplate: function1responseVTL
60 });
61
62 const function2 = new appsync.CfnFunctionConfiguration(this, 'function2', {
63 apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput),
64 dataSourceName: 'TodoTable', // DataSource name
65 functionVersion: '2018-05-29',
66 name: 'function2',
67 requestMappingTemplate: function2requestVTL,
68 responseMappingTemplate: function2responseVTL
69 });
70
71 const resolver = new appsync.CfnResolver(this, 'pipeline-resolver', {
72 apiId: cdk.Fn.ref(retVal.api.replaceWithAPIName.GraphQLAPIIdOutput),
73 fieldName: 'querySomething',
74 typeName: 'Query', // Query | Mutation | Subscription
75 kind: 'PIPELINE',
76 pipelineConfig: {
77 functions: [function1.attrFunctionId, function2.attrFunctionId]
78 },
79 requestMappingTemplate: beforeMappingVTL,
80 responseMappingTemplate: afterMappingVTL
81 });
82 }
83}

Note: Users moving from ElasticSearch to OpenSearch will need to change the datasource name from ElasticSearchDomain to OpenSearchDataSource if the upgrade process changes the source name. For new @searchable models the datasource name will default to OpenSearchDataSource.

You can alternatively define the VTL templates in another file such as Query.querySomething.req.vtl or Query.querySomething.res.vtl in amplify/backend/custom/MyCustomResolvers/. Then use the following code snippets to retrieve them:

1requestMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, "..", "Query.testColin.req.vtl")).renderTemplate(),
2responseMappingTemplate: appsync.MappingTemplate.fromFile(path.join(__dirname, "..", "Query.testColin.res.vtl")).renderTemplate(),

Note: the .. is added to the path because the path is always relative to the build folder of the custom resource.

Add authorization rules to custom queries and mutations

Authorization rules can be applied with the @auth directive in the same way as field-level authorization rules. See Field-level authorization rules for details.

In the example below, myCustomMutation can only be executed by signed-in customers who are authenticated with IAM:

1type Mutation {
2 myCustomMutation(args: String): String
3 @auth(rules: [{ allow: private, provider: iam }])
4}

Known limitation: You can't combine the @auth directive with a custom query or mutation that is using a VTL resolver.

Override Amplify-generated resolvers

Amplify generates AWS AppSync pipeline resolver for your queries and mutations. The resolvers are listed the following API resource's folder amplify/backend/api/<resource_name>/build/resolvers/.

To override an Amplify-generated resolver:

  1. Find the resolver file name you want to override under build/resolvers
  2. Place a .vtl with the same file name the resource's resolvers/ (not under build/)
  3. Upon the next amplify api gql-compile or amplify push the Amplify-generated resolver file will be replaced with your overwritten resolver file
1amplify/backend/api/<resource_name>
2├── build
3│   ├── ...
4│   ├── resolvers
5│   │   ├── ...
6│   │   ├── Query.searchTodos.req.vtl # Find resolver file name
7│   │   └── ...
8| ...
9├── resolvers
10│   └── Query.searchTodos.req.vtl # Place resolver overrides with the same file name here

The example above shows how the Query.searchTodos.req.vtl is overwritten with a custom resolver. Review the Resolver Mapping Template Programming Guide to learn more about the VTL template.

Extend Amplify-generated resolvers

Amplify generates AWS AppSync pipeline resolvers for your queries and mutations. You can "slot" in your custom business logic between Amplify-generated resolvers. You can find Amplify-generated resolvers under your API resources' build/resolvers/ folder. The resolver functions file name determines its placement within the slot sequence.

1File name convention:
2 [Query|Mutation|Subscription].[field name].[slot name].[slot placement].[req|res].vtl
3Example:
4 Mutation.createTodo.postAuth.1.req.vtl

To extend an Amplify-generated resolver:

  1. Find the resolver slot you want to add your custom business logic to
  2. Place a .vtl file with the correct the file naming convention into resolvers/ (not under build/)
  3. Upon the next amplify api gql-compile or amplify push the Amplify-generated resolver file will be replaced within the desired slot within the resolver sequence.
1amplify/backend/api/<resource_name>
2├── build
3│   ├── ...
4│   ├── resolvers
5│   │   ├── ...
6│   │   ├── Mutation.createTodo.postAuth.1.req.vtl # Amplify-generated resolvers
7│   │   └── ...
8| ...
9├── resolvers
10│   └── Mutation.createTodo.postAuth.2.req.vtl # Custom resolver slotted in after postAuth.1 resolver

For example, the a resolver function file named Mutation.createTodo.postAuth.2.req.vtl will be slotted in right after the Mutation.createTodo.postAuth.1.req.vtl resolver. Review the Resolver Mapping Template Programming Guide to learn more about the VTL template.

Supported resolver slots

Query

SequenceSlot nameDescription
1initInitial resolvers that are run. Usually used for initializing default values.
2preAuthResolvers that are intended to run before authorization rule checks are applied.
3authResolvers that implement authorization rule checks.
4postAuthResolvers that are run after authorization rule checks.
5preDataLoadResolvers to configure values to make a request to the data source.
6postDataLoadResolvers for post-processing after request to data source.
7finishFinal set of resolvers before response is returned to client. Typically used for clean-up.

Mutation

SequenceSlot nameDescription
1initInitial resolvers that are run. Usually used for initializing default values.
2preAuthResolvers that are intended to run before authorization rule checks are applied.
3authResolvers that implement authorization rule checks.
4postAuthResolvers that are run after authorization rule checks.
5preUpdateResolvers to configure values to make a request to the data source.
6postUpdateResolvers for post-processing after request to data source.
7finishFinal set of resolvers before response is returned to client. Typically used for clean-up.

Subscription

SequenceSlot nameDescription
1initInitial resolvers that are run. Usually used for initializing default values.
2preAuthResolvers that are intended to run before authorization rule checks are applied.
3authResolvers that implement authorization rule checks.
4postAuthResolvers that are run after authorization rule checks.
5preSubscribeResolver slot that executes after auth but before the subscription returns