Create AWS Kendra Search Indexes

The aws:kendra/index:Index resource, part of the Pulumi AWS provider, provisions an Amazon Kendra index that stores searchable documents and their metadata. This guide focuses on three capabilities: basic index creation with edition selection, capacity scaling for query and storage, and encryption and access control configuration.

Kendra indexes require IAM roles for CloudWatch and S3 access, and may reference KMS keys or AWS SSO for encryption and access control. The examples are intentionally small. Combine them with your own data sources, document ingestion pipelines, and application authentication.

Create a basic index with name and IAM role

Most deployments start by creating an index with a name, edition, and IAM role for CloudWatch and S3 permissions.

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

const example = new aws.kendra.Index("example", {
    name: "example",
    description: "example",
    edition: "DEVELOPER_EDITION",
    roleArn: _this.arn,
    tags: {
        Key1: "Value1",
    },
});
import pulumi
import pulumi_aws as aws

example = aws.kendra.Index("example",
    name="example",
    description="example",
    edition="DEVELOPER_EDITION",
    role_arn=this["arn"],
    tags={
        "Key1": "Value1",
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := kendra.NewIndex(ctx, "example", &kendra.IndexArgs{
			Name:        pulumi.String("example"),
			Description: pulumi.String("example"),
			Edition:     pulumi.String("DEVELOPER_EDITION"),
			RoleArn:     pulumi.Any(this.Arn),
			Tags: pulumi.StringMap{
				"Key1": pulumi.String("Value1"),
			},
		})
		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.Kendra.Index("example", new()
    {
        Name = "example",
        Description = "example",
        Edition = "DEVELOPER_EDITION",
        RoleArn = @this.Arn,
        Tags = 
        {
            { "Key1", "Value1" },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.kendra.Index;
import com.pulumi.aws.kendra.IndexArgs;
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 Index("example", IndexArgs.builder()
            .name("example")
            .description("example")
            .edition("DEVELOPER_EDITION")
            .roleArn(this_.arn())
            .tags(Map.of("Key1", "Value1"))
            .build());

    }
}
resources:
  example:
    type: aws:kendra:Index
    properties:
      name: example
      description: example
      edition: DEVELOPER_EDITION
      roleArn: ${this.arn}
      tags:
        Key1: Value1

The name property identifies your index. The edition property determines capacity and features: use DEVELOPER_EDITION for testing, ENTERPRISE_EDITION for production, or GEN_AI_ENTERPRISE_EDITION for generative AI applications. Once set, the edition cannot be changed. The roleArn grants Kendra permissions to write CloudWatch logs and read documents from S3 during ingestion.

Scale query and storage capacity with capacity units

As query volume or document count grows, you can add capacity beyond the base allocation.

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

const example = new aws.kendra.Index("example", {
    name: "example",
    edition: "DEVELOPER_EDITION",
    roleArn: _this.arn,
    capacityUnits: {
        queryCapacityUnits: 2,
        storageCapacityUnits: 2,
    },
});
import pulumi
import pulumi_aws as aws

example = aws.kendra.Index("example",
    name="example",
    edition="DEVELOPER_EDITION",
    role_arn=this["arn"],
    capacity_units={
        "query_capacity_units": 2,
        "storage_capacity_units": 2,
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := kendra.NewIndex(ctx, "example", &kendra.IndexArgs{
			Name:    pulumi.String("example"),
			Edition: pulumi.String("DEVELOPER_EDITION"),
			RoleArn: pulumi.Any(this.Arn),
			CapacityUnits: &kendra.IndexCapacityUnitsArgs{
				QueryCapacityUnits:   pulumi.Int(2),
				StorageCapacityUnits: 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.Kendra.Index("example", new()
    {
        Name = "example",
        Edition = "DEVELOPER_EDITION",
        RoleArn = @this.Arn,
        CapacityUnits = new Aws.Kendra.Inputs.IndexCapacityUnitsArgs
        {
            QueryCapacityUnits = 2,
            StorageCapacityUnits = 2,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.kendra.Index;
import com.pulumi.aws.kendra.IndexArgs;
import com.pulumi.aws.kendra.inputs.IndexCapacityUnitsArgs;
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 Index("example", IndexArgs.builder()
            .name("example")
            .edition("DEVELOPER_EDITION")
            .roleArn(this_.arn())
            .capacityUnits(IndexCapacityUnitsArgs.builder()
                .queryCapacityUnits(2)
                .storageCapacityUnits(2)
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:kendra:Index
    properties:
      name: example
      edition: DEVELOPER_EDITION
      roleArn: ${this.arn}
      capacityUnits:
        queryCapacityUnits: 2
        storageCapacityUnits: 2

The capacityUnits block controls scaling. The queryCapacityUnits property increases concurrent query throughput; storageCapacityUnits adds document storage capacity. Each unit provides incremental capacity; consult AWS documentation for specific limits per edition.

Encrypt indexed data with a KMS key

Organizations with compliance requirements can encrypt indexed content using customer-managed KMS keys.

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

const example = new aws.kendra.Index("example", {
    name: "example",
    roleArn: thisAwsIamRole.arn,
    serverSideEncryptionConfiguration: {
        kmsKeyId: _this.arn,
    },
});
import pulumi
import pulumi_aws as aws

example = aws.kendra.Index("example",
    name="example",
    role_arn=this_aws_iam_role["arn"],
    server_side_encryption_configuration={
        "kms_key_id": this["arn"],
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := kendra.NewIndex(ctx, "example", &kendra.IndexArgs{
			Name:    pulumi.String("example"),
			RoleArn: pulumi.Any(thisAwsIamRole.Arn),
			ServerSideEncryptionConfiguration: &kendra.IndexServerSideEncryptionConfigurationArgs{
				KmsKeyId: pulumi.Any(this.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.Kendra.Index("example", new()
    {
        Name = "example",
        RoleArn = thisAwsIamRole.Arn,
        ServerSideEncryptionConfiguration = new Aws.Kendra.Inputs.IndexServerSideEncryptionConfigurationArgs
        {
            KmsKeyId = @this.Arn,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.kendra.Index;
import com.pulumi.aws.kendra.IndexArgs;
import com.pulumi.aws.kendra.inputs.IndexServerSideEncryptionConfigurationArgs;
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 Index("example", IndexArgs.builder()
            .name("example")
            .roleArn(thisAwsIamRole.arn())
            .serverSideEncryptionConfiguration(IndexServerSideEncryptionConfigurationArgs.builder()
                .kmsKeyId(this_.arn())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:kendra:Index
    properties:
      name: example
      roleArn: ${thisAwsIamRole.arn}
      serverSideEncryptionConfiguration:
        kmsKeyId: ${this.arn}

The serverSideEncryptionConfiguration block specifies the KMS key ARN. Kendra encrypts all indexed data with this key. Note that this configuration is immutable; you cannot change the encryption key after index creation.

Integrate with AWS SSO for access control

When search results need to respect organizational access controls, Kendra can fetch user and group information from AWS SSO.

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

const example = new aws.kendra.Index("example", {
    name: "example",
    roleArn: _this.arn,
    userGroupResolutionConfiguration: {
        userGroupResolutionMode: "AWS_SSO",
    },
});
import pulumi
import pulumi_aws as aws

example = aws.kendra.Index("example",
    name="example",
    role_arn=this["arn"],
    user_group_resolution_configuration={
        "user_group_resolution_mode": "AWS_SSO",
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := kendra.NewIndex(ctx, "example", &kendra.IndexArgs{
			Name:    pulumi.String("example"),
			RoleArn: pulumi.Any(this.Arn),
			UserGroupResolutionConfiguration: &kendra.IndexUserGroupResolutionConfigurationArgs{
				UserGroupResolutionMode: pulumi.String("AWS_SSO"),
			},
		})
		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.Kendra.Index("example", new()
    {
        Name = "example",
        RoleArn = @this.Arn,
        UserGroupResolutionConfiguration = new Aws.Kendra.Inputs.IndexUserGroupResolutionConfigurationArgs
        {
            UserGroupResolutionMode = "AWS_SSO",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.kendra.Index;
import com.pulumi.aws.kendra.IndexArgs;
import com.pulumi.aws.kendra.inputs.IndexUserGroupResolutionConfigurationArgs;
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 Index("example", IndexArgs.builder()
            .name("example")
            .roleArn(this_.arn())
            .userGroupResolutionConfiguration(IndexUserGroupResolutionConfigurationArgs.builder()
                .userGroupResolutionMode("AWS_SSO")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:kendra:Index
    properties:
      name: example
      roleArn: ${this.arn}
      userGroupResolutionConfiguration:
        userGroupResolutionMode: AWS_SSO

The userGroupResolutionConfiguration block enables SSO integration. Setting userGroupResolutionMode to AWS_SSO tells Kendra to fetch group memberships from your SSO identity source. Search results are then filtered based on the authenticated user’s group access.

Configure JWT-based user authentication

Applications that authenticate users with JSON Web Tokens can pass user identity and group membership to Kendra.

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

const example = new aws.kendra.Index("example", {
    name: "example",
    roleArn: _this.arn,
    userTokenConfigurations: {
        jsonTokenTypeConfiguration: {
            groupAttributeField: "groups",
            userNameAttributeField: "username",
        },
    },
});
import pulumi
import pulumi_aws as aws

example = aws.kendra.Index("example",
    name="example",
    role_arn=this["arn"],
    user_token_configurations={
        "json_token_type_configuration": {
            "group_attribute_field": "groups",
            "user_name_attribute_field": "username",
        },
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := kendra.NewIndex(ctx, "example", &kendra.IndexArgs{
			Name:    pulumi.String("example"),
			RoleArn: pulumi.Any(this.Arn),
			UserTokenConfigurations: &kendra.IndexUserTokenConfigurationsArgs{
				JsonTokenTypeConfiguration: &kendra.IndexUserTokenConfigurationsJsonTokenTypeConfigurationArgs{
					GroupAttributeField:    pulumi.String("groups"),
					UserNameAttributeField: pulumi.String("username"),
				},
			},
		})
		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.Kendra.Index("example", new()
    {
        Name = "example",
        RoleArn = @this.Arn,
        UserTokenConfigurations = new Aws.Kendra.Inputs.IndexUserTokenConfigurationsArgs
        {
            JsonTokenTypeConfiguration = new Aws.Kendra.Inputs.IndexUserTokenConfigurationsJsonTokenTypeConfigurationArgs
            {
                GroupAttributeField = "groups",
                UserNameAttributeField = "username",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.kendra.Index;
import com.pulumi.aws.kendra.IndexArgs;
import com.pulumi.aws.kendra.inputs.IndexUserTokenConfigurationsArgs;
import com.pulumi.aws.kendra.inputs.IndexUserTokenConfigurationsJsonTokenTypeConfigurationArgs;
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 Index("example", IndexArgs.builder()
            .name("example")
            .roleArn(this_.arn())
            .userTokenConfigurations(IndexUserTokenConfigurationsArgs.builder()
                .jsonTokenTypeConfiguration(IndexUserTokenConfigurationsJsonTokenTypeConfigurationArgs.builder()
                    .groupAttributeField("groups")
                    .userNameAttributeField("username")
                    .build())
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:kendra:Index
    properties:
      name: example
      roleArn: ${this.arn}
      userTokenConfigurations:
        jsonTokenTypeConfiguration:
          groupAttributeField: groups
          userNameAttributeField: username

The userTokenConfigurations block defines how Kendra extracts user information from JWTs. The groupAttributeField and userNameAttributeField properties specify which JWT claims contain group membership and username. Kendra uses these claims to filter search results per user.

Beyond these examples

These snippets focus on specific index-level features: index creation and edition selection, capacity scaling and encryption, and access control integration. They’re intentionally minimal rather than full search applications.

The examples may reference pre-existing infrastructure such as IAM roles with CloudWatch and S3 permissions, KMS keys for encryption, and AWS SSO configuration for user group resolution. They focus on configuring the index rather than provisioning everything around it.

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

  • Document metadata field configuration (documentMetadataConfigurationUpdates)
  • User context policies (userContextPolicy)
  • Custom metadata fields for search faceting and relevance tuning
  • Data source connections and document ingestion

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

Let's create AWS Kendra Search Indexes

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Immutability & Limitations
Can I change the index edition after creation?
No, the edition property is immutable. Choose DEVELOPER_EDITION for development/testing/POC, ENTERPRISE_EDITION for production (default), or GEN_AI_ENTERPRISE_EDITION for generative AI applications before creating the index.
Can I use asymmetric KMS keys for encryption?
No, Amazon Kendra only supports symmetric KMS customer managed keys (CMKs). Configure serverSideEncryptionConfiguration with a symmetric KMS key ARN.
Can I delete metadata fields after adding them?
No, index fields cannot be deleted once added. Plan your metadata schema carefully, as documentMetadataConfigurationUpdates blocks are permanent.
Metadata Configuration
What happens if I don't specify all predefined metadata fields?
If you configure documentMetadataConfigurationUpdates, you must define all 14 predefined fields, even if using defaults. Omitting any will cause configuration errors.
What are the predefined metadata fields I must include?
The 14 required predefined fields are: _authors, _category, _created_at, _data_source_id, _document_title, _excerpt_page_number, _faq_id, _file_type, _language_code, _last_updated_at, _source_uri, _tenant_id, _version, and _view_count.
How do I add custom metadata fields to my index?
Add custom fields to documentMetadataConfigurationUpdates alongside the 14 predefined fields. Specify the field type (STRING_VALUE, LONG_VALUE, STRING_LIST_VALUE, or DATE_VALUE) and configure search and relevance settings.
What's the limit for metadata fields?
You can configure a minimum of 0 and maximum of 500 items in documentMetadataConfigurationUpdates.
Capacity & Scaling
How do I scale my index capacity?
Configure capacityUnits with queryCapacityUnits and storageCapacityUnits to add document storage and query capacity beyond the base allocation.
User Context & Access Control
What's the default user context policy?
The userContextPolicy defaults to ATTRIBUTE_FILTER. You can also set it to USER_TOKEN for token-based access control.
How do I configure AWS SSO for user group resolution?
Set userGroupResolutionConfiguration with userGroupResolutionMode set to AWS_SSO to fetch access levels from AWS Single Sign-On.
Troubleshooting
How do I check if my index creation failed?
Check the status output field. When status is FAILED, the errorMessage field contains details about why the operation failed.

Using a different cloud?

Explore analytics guides for other cloud providers: