Skip to content

How to Programmatically Tag Untagged AWS Resources

7 minute read
Content level: Intermediate
0

This article shows how to find and bulk-tag AWS resources that have no tags, programmatically at scale using the AWS CLI. Consistent tagging supports cost allocation, access control, automation, and operational grouping. The approach pairs AWS Resource Explorer (discovery) with the Resource Groups Tagging API (tagging).

Introduction

Organizations need AWS resources tagged for a consistent metadata standard — for example, applying a service or cost-center tag to support cost allocation, access control, or operational grouping. After an initial tagging effort, organizations often find resources that were never tagged at creation. For cost allocation specifically, this shows up as spend still attributed to untagged resources in Cost Explorer. At scale, this takes real effort on two fronts — discovery and volume: finding every untagged resource across regions and accounts, then tagging potentially thousands of them.

Approach Overview

For interactive tagging through console, you can use AWS Resource Explorer and Tag Editor. You can search, filter, and apply tags through GUI.

The article focuses on the programmatic (CLI) approach, which is more efficient when you need to tag thousands of resources, operate across multiple accounts, or automate tagging.

Resource Groups Tagging GetResources API only returns resources that already have at least one tag. You can use AWS Resource Explorer with tag:none query to find resources with no tags. Resource Groups Tagging TagResources API can then be used for tagging the resources. This article uses both APIs — resource-explorer-2 (discovery) and resourcegroupstaggingapi (tagging).

Note: Resource Explorer must be configured in your account before you can query it. See Setting up and configuring Resource Explorer.

The CLI Approach

This article uses service as the example tag key, but the same approach applies to any tag you need to apply at scale. The workflow is:

  1. Discover untagged resources with AWS Resource Explorer
  2. Apply the tags with the Resource Groups Tagging API
  3. Activate the tag for cost allocation and backfill historical data
  4. Prevent recurrence with governance controls

Note: The CLI commands below are examples to demonstrate the approach. You can adapt them into your own scripts, automation pipelines, or SDK-based tooling (Python/Boto3, etc.) as suits your environment.

Step 1 — Discover Untagged Resources with Resource Explorer

AWS Resource Explorer can find resources with no tags using the tag:none filter. The resourcetype.supports:tags filters for resources which support tagging.

Find all resources with no tags that support tagging (Discovery only):

aws resource-explorer-2 list-resources \
  --filters FilterString="tag:none resourcetype.supports:tags" \
  --region us-east-1 \
  --output json | jq -r '.Resources[].Arn' > no-tags-discovery-only.csv

Review the no-tags-discovery-only.csv to see what's untagged across regions/accounts.

Find resources missing a specific key (they may have other tags, just not service):

aws resource-explorer-2 list-resources \
  --filters FilterString="-tag.key:service" \
  --region us-east-1 \
  --output json | jq -r '.Resources[].Arn'

Narrow to specific resource types, region and account (e.g. volumes):

aws resource-explorer-2 list-resources \
  --filters FilterString="tag:none resourcetype:ec2:volume region:us-east-1 accountid:123456789012" \
  --region us-east-1 \
  --output json | jq -r '.Resources[].Arn' > untagged.csv

Replace accountid with the appropriate value. Review the untagged.csv to confirm the resources are the ones you scoped for and expect to tag in next step.

Note: Point --region at the region that hosts your aggregator index — its Resource Explorer view spans all regions and includes tags. The region: and accountid: filters narrow results to one region and one account, matching the region-scoped tag-resources calls that follow. Scoping the query by resource type, region, and account helps with batching and gives you tighter control over the tagging process.

Step 2 — Apply the Tags

If every resource is getting the same tag value, you can batch up to 20 ARNs per API call. The TagResources API supports a maximum of 20 ARNs per call, and xargs -n 20 batches the ARNs accordingly:

cat untagged.csv | xargs -n 20 | while read -r arns; do
  aws resourcegroupstaggingapi tag-resources \
    --region us-east-1 \
    --resource-arn-list $arns \
    --tags "service=MyApplication" \
    --output json
done

Replace MyApplication with the appropriate value.

Applying different values per resource: When resources need different values (e.g., mapping each resource to its owning team), you can export the untagged ARNs, open the CSV, and fill in the values in second column of CSV.

Updated CSV output should look like below (ARN + value):

"arn:aws:ec2:us-east-1:123456789012:volume/vol-abc","payments"
"arn:aws:ec2:us-east-1:123456789012:volume/vol-def","identity"

Then read each ARN and value per row and tag them individually (one API call per resource):

while IFS=, read -r arn value; do
  arn=$(echo "$arn" | tr -d '"')
  value=$(echo "$value" | tr -d '"')
  aws resourcegroupstaggingapi tag-resources \
    --region us-east-1 \
    --resource-arn-list "$arn" \
    --tags "service=$value" \
    --output json
done < untagged.csv

Verify the tag was applied:

aws resourcegroupstaggingapi get-resources \
  --region us-east-1 \
  --tag-filters 'Key=service' \
  --output json | jq -r '
  .ResourceTagMappingList[] as $r
  | $r.Tags[]
  | select(.Key == "service")
  | [$r.ResourceARN, .Value] | @csv' > tagged-verify.csv

The resources with service tag will appear in GetResources output — confirming the tagging operation succeeded.

Step 3 — Activate the Tag for Cost Allocation

Activate the tag as a cost allocation tag in the management (payer) account under Billing and Cost Management → Cost allocation tags. It can take up to 24 hours to appear in Cost Explorer and the Cost and Usage Report (CUR). For the full process, see Activating user-defined cost allocation tags.

Note: Cost allocation tags apply only going forward. Resources you tag now (because they were previously untagged) will show under the tag from the tagging date onward — their earlier usage carried no tag and cannot be attributed retroactively. Cost allocation tag backfill only activates a tag for the resources that already carried it; it does not add tags to historical usage.

Step 4 — Prevent Recurrence

Tagging existing resources is reactive. To keep new resources compliant:

Considerations

  • API Limits: TagResources and UntagResources are limited to 5 calls per second and GetResources to 15 calls per second. See Resource Groups and Tagging endpoints and quotas. Configure AWS CLI retries as needed.
  • Per-region, per-account: The APIs operate per-region and per-account. For multi-region or multi-account environments, extend these commands into your existing automation or scripting workflows as needed.
  • Resource type support: Resource Explorer and the Tagging API support most, but not all, resource types. See supported resource types to confirm coverage.
  • Free-form search: For deterministic results in automation, we used --filters above. You can use resource-explorer-2 search for free-form text search.

Conclusion

AWS Resource Explorer and the Resource Groups Tagging API work together to tag resources programmatically at scale — Resource Explorer discovers resources, and the Tagging API applies tags in bulk with tag-resources. Once tags are applied and activated as cost allocation tags, you can track costs by tag in Cost Explorer and the Cost and Usage Report (CUR). Pair this with tagging at creation, Tag Policies, SCPs, and AWS Config rules so new resources stay compliant from creation.

AWS
EXPERT

published 18 days ago76 views