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 may reference Cognito User Pools, Lambda authorizers, or IAM roles that must exist separately. The examples are intentionally small. Combine them with your own resolvers, data sources, and client applications.

Authenticate with API keys for public access

Many GraphQL APIs start with API key authentication for simple, public-facing endpoints where you want to 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. This works well for public APIs where you control access through key distribution and rotation rather than individual user accounts.

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 credentials. This integrates naturally with Lambda functions, EC2 instances, and other AWS services that already have IAM roles. No additional credential management is needed.

Authenticate users through Cognito User Pools

Consumer-facing applications typically authenticate end users through Cognito User Pools, which handle sign-up, sign-in, and token management.

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 determines what happens when a user’s token is valid but they lack explicit permissions (ALLOW or DENY). The userPoolId points to your existing Cognito User Pool.

Implement custom authorization with Lambda

Teams with complex authorization logic often use Lambda authorizers to evaluate custom rules, check external systems, or enforce business-specific access policies.

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}

The lambdaAuthorizerConfig property points to a Lambda function that receives request context and returns an authorization decision. The lambda.Permission resource grants AppSync permission to invoke your authorizer function. This enables authorization logic that goes beyond what IAM policies or Cognito groups can express.

Support multiple authentication methods simultaneously

APIs serving different client types often need multiple authentication methods, such as API keys for public access 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 define secondary authentication methods beyond the primary authenticationType. Clients can choose which method to use based on their context. This example combines API_KEY for public clients with AWS_IAM for internal services.

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 language format. This defines your API’s type system: what queries clients can make, what data they receive, and how types relate. AppSync uses this schema to validate requests and route them to resolvers.

Send API logs to CloudWatch

Production APIs need observability into request patterns, errors, and performance. AppSync can send logs to CloudWatch for monitoring and debugging.

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 logging. The cloudwatchLogsRoleArn points to an IAM role that grants AppSync permission to write logs. The fieldLogLevel controls verbosity: ERROR logs only failures, while ALL logs every field resolution. The example creates the necessary IAM role with the AWS-managed policy for CloudWatch Logs.

Limit query complexity and control 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, or 0 for unlimited). The resolverCountLimit caps how many resolvers execute per request (1-10000, or 0 for the default 10000). The introspectionConfig property controls whether clients can query the schema itself (ENABLED or DISABLED). These limits prevent resource exhaustion from malicious or poorly-written queries.

Beyond these examples

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

The examples may reference pre-existing infrastructure such as Cognito User Pools for user authentication, Lambda functions for custom authorization, and IAM roles and policies for logging. They focus on configuring the API rather than provisioning everything around it.

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

  • WAF integration for request filtering (shown but not explained)
  • X-ray tracing (xrayEnabled)
  • Enhanced metrics configuration
  • Merged API configuration (apiType: MERGED)
  • Visibility controls (GLOBAL vs PRIVATE)

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

Authentication & Authorization
What authentication types does AppSync support?
AppSync supports five authentication types: API_KEY, AWS_IAM, AMAZON_COGNITO_USER_POOLS, OPENID_CONNECT, and AWS_LAMBDA.
How do I set up Lambda authorizer authentication?
Set authenticationType to AWS_LAMBDA and configure lambdaAuthorizerConfig with your Lambda ARN. You must also create an aws.lambda.Permission resource with principal set to appsync.amazonaws.com and sourceArn pointing to your API’s ARN.
Can I use multiple authentication methods on the same API?
Yes, configure a primary authenticationType and add additional methods using additionalAuthenticationProviders.
API Configuration & Immutability
What properties can't I change after creating the API?
The apiType and visibility properties are immutable and cannot be changed after the API is created. Modifying these requires recreating the API.
What's the difference between GRAPHQL and MERGED API types?
GRAPHQL is a standard AppSync API, while MERGED combines multiple source APIs. The MERGED type requires mergedApiExecutionRoleArn to be set.
What are the default configuration values?
By default, introspectionConfig is ENABLED, visibility is GLOBAL (public), and xrayEnabled is false.
What are the query depth and resolver count limits?
queryDepthLimit defaults to 0 (no limit) with a range of 1-75 nested levels. resolverCountLimit defaults to 0 (which sets it to 10000) with a range of 1-10000. Both produce errors if operations exceed the configured limits.
Schema Management
Why isn't Pulumi detecting changes to my GraphQL schema?
Pulumi cannot perform drift detection on the schema property. You’ll need to manually verify if the schema changes outside of Pulumi.
Can I define my GraphQL schema inline?
Yes, use the schema property with GraphQL schema language format as shown in the examples.
Logging & Monitoring
How do I enable CloudWatch logging?
Configure logConfig with cloudwatchLogsRoleArn pointing to an IAM role that has the AWSAppSyncPushToCloudWatchLogs managed policy attached.
How do I protect my API with AWS WAF?
Create an aws.wafv2.WebAclAssociation resource with resourceArn set to your API’s ARN and webAclArn pointing to your Web ACL.

Using a different cloud?

Explore integration guides for other cloud providers: