LEARN · DEBUGGING GUIDE

Debugging AWS AppSync Resolver Mapping Errors

Resolver mapping errors in AppSync often stem from VTL syntax, data shape mismatches, or timeout limits. This guide shows you how to identify and fix them in production.

AdvancedCloud7 min read

What this usually means

AppSync resolver mapping errors occur when the VTL (Velocity Template Language) or JavaScript mapping template fails to execute correctly. This can be due to syntax errors, referencing undefined variables, type mismatches between the expected and actual data shape from the data source, or exceeding the 5-second template execution timeout. The error manifests at runtime because templates are evaluated per-request, so valid templates in the console can fail with real data. Common sources include: referencing `$ctx.args` fields that don't exist, expecting an array but receiving null, or misusing `$util.qr` for side effects. The error message in `errors[0].errorType` is key—look for `MappingTemplate`, `Template`, or `UnsupportedOperation`.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Run a simple query with known good data from AppSync console's Query editor — isolate if issue is data-dependent
  • 2Enable detailed CloudWatch logs for the resolver: set `appsync:resolver` log level to `ALL` in the API settings
  • 3Examine the log stream for the failed request; filter on `logType: "RequestMapping"` and `logType: "ResponseMapping"`
  • 4In the log, check `context` and `templateResult` fields — the raw template output before JSON parse reveals truncation or null references
  • 5Use `$util.log($ctx.args)` inside the template to debug — output appears in CloudWatch as `util-log` entries
  • 6Validate template syntax locally using `aws appsync evaluate-mapping-template` CLI command with a sample context
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchAWS AppSync console → API → Resolvers → select resolver → Request/Response Mapping tab
  • searchCloudWatch Logs → Log groups → `/aws/appsync/apis/<api-id>` → search for `errorType`
  • searchCloudWatch Logs Insights query: `fields @timestamp, @message | filter @message like /Template/ | sort @timestamp desc`
  • searchAWS CLI: `aws appsync evaluate-mapping-template --template "..." --context "{\"arguments\":{}}"`
  • searchGit repository where resolver templates are stored (e.g., Amplify, CDK, Serverless Framework)
  • searchData source logs (DynamoDB, Lambda, RDS) to compare input vs. expected schema
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningVTL syntax error: missing `#` directive, unclosed `#if`, or mismatched `#end` blocks
  • warningReferencing `$ctx.arguments.field` that is not present in the GraphQL query or mutation
  • warningData source returns `null` or different shape than expected (e.g., DynamoDB item missing attribute)
  • warningUsing `$util.qr` incorrectly — it doesn't return a value but silences output; often misused to assign variables
  • warningTemplate exceeds 5-second execution timeout due to large loops or complex string operations
  • warningResponse mapping template expects a list but receives a single object, or vice versa — common with DynamoDB `Query` vs `GetItem`
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildAdd explicit null checks: `#if($ctx.result.items && !$ctx.result.items.isEmpty()) ... #end` before iterating
  • buildReplace `$util.qr($foo = "bar")` with `#set($foo = "bar")` — `$util.qr` is for side effects, not assignment
  • buildFor DynamoDB, use `$ctx.result.items` for `Query` and `Scan`, but `$ctx.result` for `GetItem`; check the response mapping template matches
  • buildSimplify complex templates: break large VTL into smaller functions or move logic to the data source (e.g., Lambda function)
  • buildIncrease timeout if genuinely needed (max 5s), but better to paginate or reduce data size
  • buildUse `$util.defaultIfNull($ctx.source.field, "")` to prevent null references in response mapping
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedRun the same query from AppSync console and check the response has no errors and expected data
  • verifiedCheck CloudWatch logs for the resolver — `logType: "ResponseMapping"` should show `templateResult` as valid JSON
  • verifiedUse `aws appsync evaluate-mapping-template` with the exact context from the failing request to confirm fix
  • verifiedAdd a unit test for the resolver template using the amplify-cli or a custom script that simulates context
  • verifiedMonitor `ResolverTemplateErrors` CloudWatch metric; should drop to zero after deployment
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningDon't rely solely on the AppSync console validation — it only checks syntax, not runtime data compatibility
  • warningDon't ignore `errors` array in the response — `null` fields without errors are a different issue (resolver not found, etc.)
  • warningDon't use `$util.qr` to assign variables — it's a common pitfall; use `#set` instead
  • warningDon't hardcode partition keys in VTL; always use `$ctx.args` to keep resolvers generic
  • warningDon't forget that `$ctx.result` in response mapping can be null if the request mapping failed; check request logs first
  • warningDon't skip enabling detailed CloudWatch logs in production — you need them to debug
( 07 )War story

The Case of the Null Product List

Senior Backend EngineerAWS AppSync, DynamoDB, VTL, Node.js Lambda

Timeline

  1. 09:14PagerDuty alert: 'Product query returning null' for production API
  2. 09:17Run query `{ products { id name price } }` from AppSync console — returns `data: null` with error `MappingTemplate`
  3. 09:20Check CloudWatch logs: RequestMapping log shows `templateResult: "{\"version\":\"2018-05-29\",\"operation\":\"Scan\"}"` — looks fine
  4. 09:24Check ResponseMapping log: `errorType: "Template"` with message `"$ctx.result.items" is null`
  5. 09:28Realize the DynamoDB table had been recreated with a different name; data source points to old table
  6. 09:32Update data source configuration to new table name; redeploy resolver
  7. 09:35Re-run query — returns data correctly; monitoring shows no further errors

I was on-call when the alert came in: the product listing page was blank. The mobile team reported that the GraphQL query for products returned `null` with an error. I opened the AppSync console and ran the same query — sure enough, `data: null` and `errors: [ { "errorType": "MappingTemplate", ... } ]`. This was a resolver mapping error, but which resolver? The query involved multiple resolvers.

I enabled detailed CloudWatch logs for the API and reproduced the request. Logs showed the request mapping template executed fine — it ran a DynamoDB Scan. But the response mapping template failed: `"$ctx.result.items" is null`. That meant the DynamoDB response didn't have an `items` key. I checked the DynamoDB table directly — it had items. Then I noticed the table ARN in the data source configuration pointed to an old table that had been deleted and recreated earlier that day during a schema migration.

I updated the AppSync data source to point to the new table ARN and redeployed the resolver. The query returned data immediately. The root cause was a configuration drift: the data source was not updated after the table recreation. I added a CloudFormation hook to prevent this in the future, and we updated our deployment checklist to verify data source ARNs after any table change.

Root cause

AppSync data source pointed to a deleted DynamoDB table; the response mapping template expected `items` from a Scan but got null because the target table didn't exist.

The fix

Updated the data source configuration to reference the correct DynamoDB table ARN and redeployed the resolver.

The lesson

Always verify data source references after infrastructure changes. Use CloudWatch logs to pinpoint which template failed and the exact null reference.

( 08 )Understanding VTL Template Execution in AppSync

AppSync resolvers use Apache Velocity Template Language (VTL) or JavaScript to transform GraphQL requests into data source operations and back. The request mapping template runs first, producing a JSON payload sent to the data source. The response mapping template then transforms the data source result into the GraphQL response shape.

Both templates execute in a sandbox with a 5-second timeout. Common pitfalls include forgetting that `$ctx.args` contains only the arguments for the current field, not the whole query. Also, `$util.qr` is a quiet return — it evaluates its argument for side effects but returns an empty string, so it cannot be used for assignment. Use `#set` instead.

( 09 )Using CloudWatch Logs to Diagnose Mapping Errors

To get detailed logs, set the API's log level to `ALL` with field-level logging. Then examine log streams for the specific request. Filter on `logType: "RequestMapping"` and `logType: "ResponseMapping"`. The `templateResult` field shows the rendered template output before JSON parsing — this is where truncation or syntax issues appear.

If the error is `Template`, the `message` field often contains the exact line number or null reference. For example, `"$ctx.result.items" is null` tells you exactly what variable is missing. Use `$util.log("debug", $ctx)` in your template to print the full context object — these logs appear as `util-log` entries.

( 10 )Common VTL Mistakes and How to Avoid Them

One of the most frequent mistakes is using `$util.qr` for variable assignment. `$util.qr($foo = "bar")` does NOT assign `$foo`; it evaluates the expression for side effects and returns empty. The correct way is `#set($foo = "bar")`. Another is forgetting that `$ctx.result` in response mapping is null if the request mapping failed — check request logs first.

Array handling: DynamoDB `Query` returns `$ctx.result.items`, but `GetItem` returns `$ctx.result` directly. Mixing these up causes null references. Always verify the data source operation type in the request mapping and adjust response mapping accordingly.

( 11 )Testing Resolver Templates Locally with AWS CLI

You can test templates before deploying using `aws appsync evaluate-mapping-template`. First, save your template to a file, e.g., `template.vtl`. Then construct a context JSON that mimics the actual request. For example: `--context "{\"arguments\":{\"id\":\"123\"},\"source\":{},\"identity\":{}}"`. The command outputs the rendered template or any errors.

This is faster than deploying and checking CloudWatch. I recommend integrating this into your CI pipeline to catch template errors early. You can also use the AppSync console's "Test" button, but it doesn't simulate all edge cases like null data from the source.

Frequently asked questions

Why does my response mapping template work in the AppSync console test but fail in production?

The console test uses a static context you provide. In production, the context comes from actual data source responses, which may have different shapes or null values. For example, a DynamoDB `Query` might return an empty `items` list, but your template expects a non-empty list. Always test with realistic data, including edge cases like missing attributes.

What does the error `Template: UnsupportedOperation` mean?

This error occurs when the request mapping template produces an operation that the data source doesn't support. For example, using `Scan` on a Lambda data source when only `Invoke` is allowed. Check the data source type and ensure your operation string matches one of the supported operations: `GetItem`, `PutItem`, `Query`, `Scan`, `Invoke`, etc.

How can I debug a mapping template that times out?

First, enable detailed CloudWatch logs. The timeout error message includes the template stage (Request or Response). Common causes: infinite loops due to `#foreach` on a large list, complex string operations, or calling external services via `$util.http` without a timeout. Optimize by moving heavy logic to a Lambda function or paginating results.

Is it possible to use JavaScript instead of VTL for AppSync resolvers?

Yes, AppSync now supports JavaScript resolvers (runtime version `2018-05-29` JS). They are easier to debug because you can use `console.log` statements that appear in CloudWatch. However, the same principles apply: check the context shape, handle nulls, and ensure the data source response matches expectations. JavaScript resolvers are recommended for new projects.