Create AWS AppSync GraphQL APIs

The aws:appsync/graphQLApi:GraphQLApi resource, part of the Pulumi AWS provider, defines an AppSync GraphQL API: its authentication methods, schema, and operational controls. This guide focuses on three capabilities: authentication configuration (API keys, IAM, Cognito, Lambda), schema definition, and logging and query complexity limits.

GraphQL APIs reference Cognito User Pools, Lambda authorizers, and IAM roles for authentication. Data sources and resolvers are configured separately via companion resources. The examples are intentionally small. Combine them with your own data sources, resolvers, and monitoring infrastructure.

Authenticate with API keys for public access

Many APIs start with API key authentication for simple, public-facing endpoints where you control access through key rotation rather than user identity.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.appsync.GraphQLApi("example", {
    authenticationType: "API_KEY",
    name: "example",
});
import pulumi
import pulumi_aws as aws

example = aws.appsync.GraphQLApi("example",
    authentication_type="API_KEY",
    name="example")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appsync"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := appsync.NewGraphQLApi(ctx, "example", &appsync.GraphQLApiArgs{
			AuthenticationType: pulumi.String("API_KEY"),
			Name:               pulumi.String("example"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.AppSync.GraphQLApi("example", new()
    {
        AuthenticationType = "API_KEY",
        Name = "example",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appsync.GraphQLApi;
import com.pulumi.aws.appsync.GraphQLApiArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new GraphQLApi("example", GraphQLApiArgs.builder()
            .authenticationType("API_KEY")
            .name("example")
            .build());

    }
}
resources:
  example:
    type: aws:appsync:GraphQLApi
    properties:
      authenticationType: API_KEY
      name: example

The authenticationType property determines how clients prove their identity. API_KEY authentication generates a key that clients include in request headers. AppSync rotates keys automatically based on your expiration settings.

Authenticate with IAM for AWS service integration

Applications running on AWS often use IAM authentication to leverage existing roles and policies, avoiding separate credential management.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.appsync.GraphQLApi("example", {
    authenticationType: "AWS_IAM",
    name: "example",
});
import pulumi
import pulumi_aws as aws

example = aws.appsync.GraphQLApi("example",
    authentication_type="AWS_IAM",
    name="example")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appsync"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := appsync.NewGraphQLApi(ctx, "example", &appsync.GraphQLApiArgs{
			AuthenticationType: pulumi.String("AWS_IAM"),
			Name:               pulumi.String("example"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.AppSync.GraphQLApi("example", new()
    {
        AuthenticationType = "AWS_IAM",
        Name = "example",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appsync.GraphQLApi;
import com.pulumi.aws.appsync.GraphQLApiArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new GraphQLApi("example", GraphQLApiArgs.builder()
            .authenticationType("AWS_IAM")
            .name("example")
            .build());

    }
}
resources:
  example:
    type: aws:appsync:GraphQLApi
    properties:
      authenticationType: AWS_IAM
      name: example

When authenticationType is AWS_IAM, clients sign requests using AWS Signature Version 4. This integrates with IAM roles attached to Lambda functions, EC2 instances, or other AWS services, eliminating the need to manage separate credentials.

Authenticate users through Cognito User Pools

User-facing applications typically need to authenticate end users through identity providers. Cognito User Pools provide user registration, sign-in, and access control.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.appsync.GraphQLApi("example", {
    authenticationType: "AMAZON_COGNITO_USER_POOLS",
    name: "example",
    userPoolConfig: {
        awsRegion: current.region,
        defaultAction: "DENY",
        userPoolId: exampleAwsCognitoUserPool.id,
    },
});
import pulumi
import pulumi_aws as aws

example = aws.appsync.GraphQLApi("example",
    authentication_type="AMAZON_COGNITO_USER_POOLS",
    name="example",
    user_pool_config={
        "aws_region": current["region"],
        "default_action": "DENY",
        "user_pool_id": example_aws_cognito_user_pool["id"],
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appsync"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := appsync.NewGraphQLApi(ctx, "example", &appsync.GraphQLApiArgs{
			AuthenticationType: pulumi.String("AMAZON_COGNITO_USER_POOLS"),
			Name:               pulumi.String("example"),
			UserPoolConfig: &appsync.GraphQLApiUserPoolConfigArgs{
				AwsRegion:     pulumi.Any(current.Region),
				DefaultAction: pulumi.String("DENY"),
				UserPoolId:    pulumi.Any(exampleAwsCognitoUserPool.Id),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.AppSync.GraphQLApi("example", new()
    {
        AuthenticationType = "AMAZON_COGNITO_USER_POOLS",
        Name = "example",
        UserPoolConfig = new Aws.AppSync.Inputs.GraphQLApiUserPoolConfigArgs
        {
            AwsRegion = current.Region,
            DefaultAction = "DENY",
            UserPoolId = exampleAwsCognitoUserPool.Id,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appsync.GraphQLApi;
import com.pulumi.aws.appsync.GraphQLApiArgs;
import com.pulumi.aws.appsync.inputs.GraphQLApiUserPoolConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new GraphQLApi("example", GraphQLApiArgs.builder()
            .authenticationType("AMAZON_COGNITO_USER_POOLS")
            .name("example")
            .userPoolConfig(GraphQLApiUserPoolConfigArgs.builder()
                .awsRegion(current.region())
                .defaultAction("DENY")
                .userPoolId(exampleAwsCognitoUserPool.id())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:appsync:GraphQLApi
    properties:
      authenticationType: AMAZON_COGNITO_USER_POOLS
      name: example
      userPoolConfig:
        awsRegion: ${current.region}
        defaultAction: DENY
        userPoolId: ${exampleAwsCognitoUserPool.id}

The userPoolConfig block connects your API to a Cognito User Pool. The defaultAction property controls what happens when a user’s token is valid but they lack explicit permissions; DENY requires explicit authorization rules. Clients include Cognito JWT tokens in request headers.

Implement custom authorization with Lambda

Complex authorization logic that doesn’t fit standard identity providers can be implemented in Lambda functions, giving you full control over access decisions.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.appsync.GraphQLApi("example", {
    authenticationType: "AWS_LAMBDA",
    name: "example",
    lambdaAuthorizerConfig: {
        authorizerUri: "arn:aws:lambda:us-east-1:123456789012:function:custom_lambda_authorizer",
    },
});
const appsyncLambdaAuthorizer = new aws.lambda.Permission("appsync_lambda_authorizer", {
    statementId: "appsync_lambda_authorizer",
    action: "lambda:InvokeFunction",
    "function": "custom_lambda_authorizer",
    principal: "appsync.amazonaws.com",
    sourceArn: example.arn,
});
import pulumi
import pulumi_aws as aws

example = aws.appsync.GraphQLApi("example",
    authentication_type="AWS_LAMBDA",
    name="example",
    lambda_authorizer_config={
        "authorizer_uri": "arn:aws:lambda:us-east-1:123456789012:function:custom_lambda_authorizer",
    })
appsync_lambda_authorizer = aws.lambda_.Permission("appsync_lambda_authorizer",
    statement_id="appsync_lambda_authorizer",
    action="lambda:InvokeFunction",
    function="custom_lambda_authorizer",
    principal="appsync.amazonaws.com",
    source_arn=example.arn)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appsync"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/lambda"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := appsync.NewGraphQLApi(ctx, "example", &appsync.GraphQLApiArgs{
			AuthenticationType: pulumi.String("AWS_LAMBDA"),
			Name:               pulumi.String("example"),
			LambdaAuthorizerConfig: &appsync.GraphQLApiLambdaAuthorizerConfigArgs{
				AuthorizerUri: pulumi.String("arn:aws:lambda:us-east-1:123456789012:function:custom_lambda_authorizer"),
			},
		})
		if err != nil {
			return err
		}
		_, err = lambda.NewPermission(ctx, "appsync_lambda_authorizer", &lambda.PermissionArgs{
			StatementId: pulumi.String("appsync_lambda_authorizer"),
			Action:      pulumi.String("lambda:InvokeFunction"),
			Function:    pulumi.Any("custom_lambda_authorizer"),
			Principal:   pulumi.String("appsync.amazonaws.com"),
			SourceArn:   example.Arn,
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.AppSync.GraphQLApi("example", new()
    {
        AuthenticationType = "AWS_LAMBDA",
        Name = "example",
        LambdaAuthorizerConfig = new Aws.AppSync.Inputs.GraphQLApiLambdaAuthorizerConfigArgs
        {
            AuthorizerUri = "arn:aws:lambda:us-east-1:123456789012:function:custom_lambda_authorizer",
        },
    });

    var appsyncLambdaAuthorizer = new Aws.Lambda.Permission("appsync_lambda_authorizer", new()
    {
        StatementId = "appsync_lambda_authorizer",
        Action = "lambda:InvokeFunction",
        Function = "custom_lambda_authorizer",
        Principal = "appsync.amazonaws.com",
        SourceArn = example.Arn,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appsync.GraphQLApi;
import com.pulumi.aws.appsync.GraphQLApiArgs;
import com.pulumi.aws.appsync.inputs.GraphQLApiLambdaAuthorizerConfigArgs;
import com.pulumi.aws.lambda.Permission;
import com.pulumi.aws.lambda.PermissionArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new GraphQLApi("example", GraphQLApiArgs.builder()
            .authenticationType("AWS_LAMBDA")
            .name("example")
            .lambdaAuthorizerConfig(GraphQLApiLambdaAuthorizerConfigArgs.builder()
                .authorizerUri("arn:aws:lambda:us-east-1:123456789012:function:custom_lambda_authorizer")
                .build())
            .build());

        var appsyncLambdaAuthorizer = new Permission("appsyncLambdaAuthorizer", PermissionArgs.builder()
            .statementId("appsync_lambda_authorizer")
            .action("lambda:InvokeFunction")
            .function("custom_lambda_authorizer")
            .principal("appsync.amazonaws.com")
            .sourceArn(example.arn())
            .build());

    }
}
resources:
  example:
    type: aws:appsync:GraphQLApi
    properties:
      authenticationType: AWS_LAMBDA
      name: example
      lambdaAuthorizerConfig:
        authorizerUri: arn:aws:lambda:us-east-1:123456789012:function:custom_lambda_authorizer
  appsyncLambdaAuthorizer:
    type: aws:lambda:Permission
    name: appsync_lambda_authorizer
    properties:
      statementId: appsync_lambda_authorizer
      action: lambda:InvokeFunction
      function: custom_lambda_authorizer
      principal: appsync.amazonaws.com
      sourceArn: ${example.arn}

When authenticationType is AWS_LAMBDA, AppSync invokes your Lambda function for each request. The authorizerUri points to the function that evaluates the request and returns an authorization decision. The Lambda permission grants AppSync the ability to invoke your function.

Support multiple authentication methods simultaneously

APIs serving different client types often need to accept multiple authentication methods, such as API keys for public clients and IAM for internal services.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.appsync.GraphQLApi("example", {
    authenticationType: "API_KEY",
    name: "example",
    additionalAuthenticationProviders: [{
        authenticationType: "AWS_IAM",
    }],
});
import pulumi
import pulumi_aws as aws

example = aws.appsync.GraphQLApi("example",
    authentication_type="API_KEY",
    name="example",
    additional_authentication_providers=[{
        "authentication_type": "AWS_IAM",
    }])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appsync"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := appsync.NewGraphQLApi(ctx, "example", &appsync.GraphQLApiArgs{
			AuthenticationType: pulumi.String("API_KEY"),
			Name:               pulumi.String("example"),
			AdditionalAuthenticationProviders: appsync.GraphQLApiAdditionalAuthenticationProviderArray{
				&appsync.GraphQLApiAdditionalAuthenticationProviderArgs{
					AuthenticationType: pulumi.String("AWS_IAM"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.AppSync.GraphQLApi("example", new()
    {
        AuthenticationType = "API_KEY",
        Name = "example",
        AdditionalAuthenticationProviders = new[]
        {
            new Aws.AppSync.Inputs.GraphQLApiAdditionalAuthenticationProviderArgs
            {
                AuthenticationType = "AWS_IAM",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appsync.GraphQLApi;
import com.pulumi.aws.appsync.GraphQLApiArgs;
import com.pulumi.aws.appsync.inputs.GraphQLApiAdditionalAuthenticationProviderArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new GraphQLApi("example", GraphQLApiArgs.builder()
            .authenticationType("API_KEY")
            .name("example")
            .additionalAuthenticationProviders(GraphQLApiAdditionalAuthenticationProviderArgs.builder()
                .authenticationType("AWS_IAM")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:appsync:GraphQLApi
    properties:
      authenticationType: API_KEY
      name: example
      additionalAuthenticationProviders:
        - authenticationType: AWS_IAM

The additionalAuthenticationProviders array lets you specify secondary authentication methods beyond the primary authenticationType. Clients choose which method to use by including the appropriate credentials in their requests. This enables public API access via keys while allowing internal services to use IAM.

Define the GraphQL schema inline

GraphQL APIs require a schema that defines types, queries, and mutations. You can provide this schema directly in the API resource.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.appsync.GraphQLApi("example", {
    authenticationType: "AWS_IAM",
    name: "example",
    schema: `schema {
\\tquery: Query
}
type Query {
  test: Int
}
`,
});
import pulumi
import pulumi_aws as aws

example = aws.appsync.GraphQLApi("example",
    authentication_type="AWS_IAM",
    name="example",
    schema="""schema {
\tquery: Query
}
type Query {
  test: Int
}
""")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appsync"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := appsync.NewGraphQLApi(ctx, "example", &appsync.GraphQLApiArgs{
			AuthenticationType: pulumi.String("AWS_IAM"),
			Name:               pulumi.String("example"),
			Schema: pulumi.String(`schema {
\tquery: Query
}
type Query {
  test: Int
}
`),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.AppSync.GraphQLApi("example", new()
    {
        AuthenticationType = "AWS_IAM",
        Name = "example",
        Schema = @"schema {
\tquery: Query
}
type Query {
  test: Int
}
",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appsync.GraphQLApi;
import com.pulumi.aws.appsync.GraphQLApiArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new GraphQLApi("example", GraphQLApiArgs.builder()
            .authenticationType("AWS_IAM")
            .name("example")
            .schema("""
schema {
\tquery: Query
}
type Query {
  test: Int
}
            """)
            .build());

    }
}
resources:
  example:
    type: aws:appsync:GraphQLApi
    properties:
      authenticationType: AWS_IAM
      name: example
      schema: |
        schema {
        \tquery: Query
        }
        type Query {
          test: Int
        }        

The schema property accepts GraphQL Schema Definition Language (SDL). This defines your type system, queries, mutations, and subscriptions. AppSync validates incoming requests against this schema. Note that Pulumi cannot detect drift in schema configuration.

Send API logs to CloudWatch

Production APIs need observability into request patterns, errors, and performance. CloudWatch Logs provides centralized logging for debugging and monitoring.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const assumeRole = aws.iam.getPolicyDocument({
    statements: [{
        effect: "Allow",
        principals: [{
            type: "Service",
            identifiers: ["appsync.amazonaws.com"],
        }],
        actions: ["sts:AssumeRole"],
    }],
});
const example = new aws.iam.Role("example", {
    name: "example",
    assumeRolePolicy: assumeRole.then(assumeRole => assumeRole.json),
});
const exampleRolePolicyAttachment = new aws.iam.RolePolicyAttachment("example", {
    policyArn: "arn:aws:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs",
    role: example.name,
});
const exampleGraphQLApi = new aws.appsync.GraphQLApi("example", {logConfig: {
    cloudwatchLogsRoleArn: example.arn,
    fieldLogLevel: "ERROR",
}});
import pulumi
import pulumi_aws as aws

assume_role = aws.iam.get_policy_document(statements=[{
    "effect": "Allow",
    "principals": [{
        "type": "Service",
        "identifiers": ["appsync.amazonaws.com"],
    }],
    "actions": ["sts:AssumeRole"],
}])
example = aws.iam.Role("example",
    name="example",
    assume_role_policy=assume_role.json)
example_role_policy_attachment = aws.iam.RolePolicyAttachment("example",
    policy_arn="arn:aws:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs",
    role=example.name)
example_graph_ql_api = aws.appsync.GraphQLApi("example", log_config={
    "cloudwatch_logs_role_arn": example.arn,
    "field_log_level": "ERROR",
})
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appsync"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		assumeRole, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
			Statements: []iam.GetPolicyDocumentStatement{
				{
					Effect: pulumi.StringRef("Allow"),
					Principals: []iam.GetPolicyDocumentStatementPrincipal{
						{
							Type: "Service",
							Identifiers: []string{
								"appsync.amazonaws.com",
							},
						},
					},
					Actions: []string{
						"sts:AssumeRole",
					},
				},
			},
		}, nil)
		if err != nil {
			return err
		}
		example, err := iam.NewRole(ctx, "example", &iam.RoleArgs{
			Name:             pulumi.String("example"),
			AssumeRolePolicy: pulumi.String(assumeRole.Json),
		})
		if err != nil {
			return err
		}
		_, err = iam.NewRolePolicyAttachment(ctx, "example", &iam.RolePolicyAttachmentArgs{
			PolicyArn: pulumi.String("arn:aws:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs"),
			Role:      example.Name,
		})
		if err != nil {
			return err
		}
		_, err = appsync.NewGraphQLApi(ctx, "example", &appsync.GraphQLApiArgs{
			LogConfig: &appsync.GraphQLApiLogConfigArgs{
				CloudwatchLogsRoleArn: example.Arn,
				FieldLogLevel:         pulumi.String("ERROR"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var assumeRole = Aws.Iam.GetPolicyDocument.Invoke(new()
    {
        Statements = new[]
        {
            new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
            {
                Effect = "Allow",
                Principals = new[]
                {
                    new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
                    {
                        Type = "Service",
                        Identifiers = new[]
                        {
                            "appsync.amazonaws.com",
                        },
                    },
                },
                Actions = new[]
                {
                    "sts:AssumeRole",
                },
            },
        },
    });

    var example = new Aws.Iam.Role("example", new()
    {
        Name = "example",
        AssumeRolePolicy = assumeRole.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
    });

    var exampleRolePolicyAttachment = new Aws.Iam.RolePolicyAttachment("example", new()
    {
        PolicyArn = "arn:aws:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs",
        Role = example.Name,
    });

    var exampleGraphQLApi = new Aws.AppSync.GraphQLApi("example", new()
    {
        LogConfig = new Aws.AppSync.Inputs.GraphQLApiLogConfigArgs
        {
            CloudwatchLogsRoleArn = example.Arn,
            FieldLogLevel = "ERROR",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.iam.Role;
import com.pulumi.aws.iam.RoleArgs;
import com.pulumi.aws.iam.RolePolicyAttachment;
import com.pulumi.aws.iam.RolePolicyAttachmentArgs;
import com.pulumi.aws.appsync.GraphQLApi;
import com.pulumi.aws.appsync.GraphQLApiArgs;
import com.pulumi.aws.appsync.inputs.GraphQLApiLogConfigArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        final var assumeRole = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
            .statements(GetPolicyDocumentStatementArgs.builder()
                .effect("Allow")
                .principals(GetPolicyDocumentStatementPrincipalArgs.builder()
                    .type("Service")
                    .identifiers("appsync.amazonaws.com")
                    .build())
                .actions("sts:AssumeRole")
                .build())
            .build());

        var example = new Role("example", RoleArgs.builder()
            .name("example")
            .assumeRolePolicy(assumeRole.json())
            .build());

        var exampleRolePolicyAttachment = new RolePolicyAttachment("exampleRolePolicyAttachment", RolePolicyAttachmentArgs.builder()
            .policyArn("arn:aws:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs")
            .role(example.name())
            .build());

        var exampleGraphQLApi = new GraphQLApi("exampleGraphQLApi", GraphQLApiArgs.builder()
            .logConfig(GraphQLApiLogConfigArgs.builder()
                .cloudwatchLogsRoleArn(example.arn())
                .fieldLogLevel("ERROR")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:iam:Role
    properties:
      name: example
      assumeRolePolicy: ${assumeRole.json}
  exampleRolePolicyAttachment:
    type: aws:iam:RolePolicyAttachment
    name: example
    properties:
      policyArn: arn:aws:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs
      role: ${example.name}
  exampleGraphQLApi:
    type: aws:appsync:GraphQLApi
    name: example
    properties:
      logConfig:
        cloudwatchLogsRoleArn: ${example.arn}
        fieldLogLevel: ERROR
variables:
  assumeRole:
    fn::invoke:
      function: aws:iam:getPolicyDocument
      arguments:
        statements:
          - effect: Allow
            principals:
              - type: Service
                identifiers:
                  - appsync.amazonaws.com
            actions:
              - sts:AssumeRole

The logConfig block enables CloudWatch Logs integration. The cloudwatchLogsRoleArn grants AppSync permission to write logs. The fieldLogLevel controls verbosity; ERROR captures only failures, while ALL includes all field resolutions. This example creates the IAM role and attaches the AWS-managed policy for CloudWatch Logs access.

Limit query complexity and introspection

APIs exposed to untrusted clients need protection against expensive queries that could consume excessive resources or expose schema details.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.appsync.GraphQLApi("example", {
    authenticationType: "AWS_IAM",
    name: "example",
    introspectionConfig: "ENABLED",
    queryDepthLimit: 2,
    resolverCountLimit: 2,
});
import pulumi
import pulumi_aws as aws

example = aws.appsync.GraphQLApi("example",
    authentication_type="AWS_IAM",
    name="example",
    introspection_config="ENABLED",
    query_depth_limit=2,
    resolver_count_limit=2)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/appsync"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := appsync.NewGraphQLApi(ctx, "example", &appsync.GraphQLApiArgs{
			AuthenticationType:  pulumi.String("AWS_IAM"),
			Name:                pulumi.String("example"),
			IntrospectionConfig: pulumi.String("ENABLED"),
			QueryDepthLimit:     pulumi.Int(2),
			ResolverCountLimit:  pulumi.Int(2),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.AppSync.GraphQLApi("example", new()
    {
        AuthenticationType = "AWS_IAM",
        Name = "example",
        IntrospectionConfig = "ENABLED",
        QueryDepthLimit = 2,
        ResolverCountLimit = 2,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.appsync.GraphQLApi;
import com.pulumi.aws.appsync.GraphQLApiArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new GraphQLApi("example", GraphQLApiArgs.builder()
            .authenticationType("AWS_IAM")
            .name("example")
            .introspectionConfig("ENABLED")
            .queryDepthLimit(2)
            .resolverCountLimit(2)
            .build());

    }
}
resources:
  example:
    type: aws:appsync:GraphQLApi
    properties:
      authenticationType: AWS_IAM
      name: example
      introspectionConfig: ENABLED
      queryDepthLimit: 2
      resolverCountLimit: 2

The queryDepthLimit property restricts how many nested levels a query can traverse (1-75, default unlimited). The resolverCountLimit caps how many resolvers execute per request (1-10000, default 10000). The introspectionConfig property controls whether clients can query the schema structure; DISABLED prevents schema discovery attacks.

Beyond these examples

These snippets focus on specific API-level features: authentication methods (API keys, IAM, Cognito, OIDC, Lambda), schema definition and logging, and query complexity controls. They’re intentionally minimal rather than full GraphQL applications.

The examples may reference pre-existing infrastructure such as Cognito User Pools for user authentication, Lambda functions for custom authorization, and IAM roles for CloudWatch Logs access. They focus on configuring the API rather than provisioning data sources and resolvers.

To keep things focused, common API patterns are omitted, including:

  • Data sources and resolvers (configured via companion resources)
  • X-Ray distributed tracing (xrayEnabled)
  • Enhanced metrics configuration (enhancedMetricsConfig)
  • WAF integration for request filtering
  • Merged API configuration (apiType: MERGED)

These omissions are intentional: the goal is to illustrate how each API feature is wired, not provide drop-in GraphQL modules. See the AppSync GraphQL API resource reference for all available configuration options.

Let's create AWS AppSync GraphQL APIs

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Immutability & Lifecycle
What properties can't I change after creating the API?
apiType and visibility are immutable and cannot be changed once the API is created. Modifying these requires replacing the resource.
Why doesn't Pulumi detect changes to my GraphQL schema?
Pulumi cannot perform drift detection on the schema property. You’ll need to manually track schema changes or use external tooling.
Authentication & Authorization
What authentication types are available?
AppSync supports five authentication types: API_KEY, AWS_IAM, AMAZON_COGNITO_USER_POOLS, OPENID_CONNECT, and AWS_LAMBDA.
Can I use multiple authentication methods?
Yes, configure additionalAuthenticationProviders to add secondary authentication methods beyond the primary authenticationType.
Why isn't my Lambda authorizer working?
Lambda authorizers require a separate aws.lambda.Permission resource granting AppSync invoke access. Set principal to appsync.amazonaws.com and sourceArn to the API’s ARN.
API Configuration & Limits
Is introspection enabled by default?
Yes, introspectionConfig defaults to ENABLED. Set it to DISABLED to prevent introspection, which will produce errors if clients attempt to use it.
What are the query depth limits?
queryDepthLimit defaults to 0 (no limit). You can set it between 1 and 75 nested levels. Queries exceeding the limit will produce errors.
What are the resolver count limits?
resolverCountLimit defaults to 0, which sets the limit to 10000. You can specify a value between 1 and 10000. Requests exceeding the limit will produce errors.
What's required for a MERGED API type?
When apiType is set to MERGED, you must also provide mergedApiExecutionRoleArn with the ARN of the execution role.
Logging & Monitoring
How do I enable CloudWatch logging?
Configure logConfig with cloudwatchLogsRoleArn and fieldLogLevel. The IAM role must have the AWSAppSyncPushToCloudWatchLogs managed policy attached.
Is X-ray tracing enabled by default?
No, xrayEnabled defaults to false. Set it to true to enable X-ray tracing.

Using a different cloud?

Explore integration guides for other cloud providers: