Cognito is one of those services nobody budgets for. The user pool goes in early, the free tier covers development, and for a while the line item on the bill is zero. Then the product grows, or someone runs a migration, or a batch job starts touching every user, and the Cognito charge becomes a number that needs explaining.
Usually that number has little to do with traffic. It comes from decisions made once, early, by whoever created the pool: which tier it runs on, what the application does with tokens, how users were imported, and which jobs read user data. This post goes through the billing model, the places where it surprises people, and the settings that keep it predictable.
All prices below come from the Amazon Cognito pricing page, checked on October 12, 2026. Every dollar figure is calculated from those published rates, and the inputs are shown so you can redo the math with your own numbers. Rates change, so check the page before you quote any of this in a planning document.
How Cognito Bills
User pools are billed per monthly active user (MAU), and the rate depends on the pool's feature tier. There are three.
| Tier | Price per MAU above the free tier | Free tier | What it adds |
|---|---|---|---|
| Lite | $0.0055 up to 100,000 MAU, then $0.0046 | 10,000 MAU | Password sign-in, social and SAML/OIDC federation, MFA with SMS or authenticator apps, Lambda triggers |
| Essentials | $0.015 | 10,000 MAU | Managed login with the visual editor, passwordless and passkeys, email MFA, access token customization, refresh token rotation |
| Plus | $0.020 | None | Threat protection: adaptive authentication, compromised credential detection, exportable user activity logs |
A few things sit outside that table:
- Federated users who sign in through SAML 2.0 or OIDC are priced separately: $0.015 per MAU, with a free tier of 50 MAU, whatever tier the pool is on.
- Machine-to-machine authorization is billed per successful token response, with no free tier.
- Identity pools are free.
- SMS and email go out through Amazon SNS and Amazon SES and are billed by those services, not by Cognito.
The Default Tier Is Not the Cheap One
New user pools are created on Essentials unless you say otherwise. Essentials costs $0.015 per MAU. Lite costs $0.0055, and $0.0046 past 100,000 MAU. The default is almost three times the price of the cheapest tier.
At 100,000 MAU, after the free tier:
| Tier | Calculation | Monthly |
|---|---|---|
| Lite | 90,000 × $0.0055 | $495 |
| Essentials | 90,000 × $0.015 | $1,350 |
| Plus | 100,000 × $0.020 | $2,000 |
Essentials is a reasonable choice when you use what it adds. If your application signs users in with a password or a social provider and you built your own login screens, you are probably paying for passkeys and a managed login editor that no one uses. Check the tier on existing pools before anything else in this post. It is the cheapest fix there is.
What Makes a User Billable
This is the part that produces the bills people don't expect. The pricing page defines an MAU as a user for whom your application generates an identity operation during the calendar month. The list is longer than most people assume:
- sign-up, sign-in, and sign-out
- token refresh
- password change and attribute updates
- administrative creation or update of the user
- an attribute query on the user through
AdminGetUser
Once a user is counted, nothing else about them adds cost that month. Subsequent sessions are free, users with no identity operation are free, and with multi-Region replication the same user is counted once across Regions.
Read the last two items on that list again. They are things your code does, not things your users do. That is where the four patterns below come from.
Batch jobs that read every user
A nightly job enriches a reporting table, or syncs user attributes to a CRM, and it calls AdminGetUser once per user. Every user it reads becomes an MAU for the month, including the ones who haven't opened the app in a year.
Take a pool with 300,000 users, of whom 60,000 actually sign in during a given month:
| Essentials | Lite | |
|---|---|---|
| Only real users counted (60,000) | $750 | $275 |
| Nightly job reads all 300,000 | $4,350 | $1,415 |
The fix is to stop asking Cognito for data you already have. The ID token carries the user's attributes at sign-in. If a job needs attributes for users who are not signed in, keep your own copy in a table and update it from a Lambda trigger or from your application when attributes change.
The pricing page names AdminGetUser explicitly. It says nothing either way about ListUsers. I would not switch a job to ListUsers and assume it's free without watching a full month of billing first.
Token refresh on a timer
Token refresh counts as an identity operation. That's fine when a user is actually using the app, because they are already counted. It becomes a cost when the app refreshes tokens on its own. A mobile app that refreshes on launch or in a background task turns every installed copy into an MAU every month, whether the person did anything or not.
Refresh when an API call needs a fresh token, not on launch and not on a schedule.
Migrations that create everyone at once
Administrative creation counts. If you move 500,000 users into a new pool with AdminCreateUser in one month, that month bills 500,000 MAUs: $7,350 on Essentials, even if most of those users never come back.
The Migrate User Lambda trigger avoids this. Cognito calls it when a user who doesn't exist in the pool tries to sign in or reset their password, and the trigger creates the user from your old system at that moment. You only ever create, and pay for, the users who return.
There is a catch. The trigger needs the user's password, so it only fires for password flows that send it: USER_PASSWORD_AUTH, ADMIN_USER_PASSWORD_AUTH, and USER_AUTH with a password, plus forgot-password. It does not fire for SRP sign-in, which many client libraries use by default, or for passwordless sign-in. If your clients use SRP, they have to switch flows for the migration window, or the trigger never runs.
Load tests against a real pool
Synthetic users are users. A load test that signs in 50,000 test accounts after the free tier is used up costs $750 on Essentials for that month. Which leads to the next problem: the free tier probably isn't where you think it is.
The Free Tier Is Smaller Than It Looks
It is defined per account or per AWS organization. If your accounts sit in one organization, plan on staging, development, and production sharing a single 10,000 MAU allowance rather than getting 10,000 each. A load test in a sandbox account can use up the free tier that production was counting on.
Plus has none. On Plus you pay from the first user.
Federated users get 50. For users who sign in through SAML or OIDC, the free tier is 50 MAU and the rate is $0.015 on every tier, Lite included. A pool of 30,000 SSO users costs $449.25 a month. If someone estimated it with Lite's direct sign-in rate, they budgeted about $110.
Older pools have a better deal, and you can lose it. User pools created on or before November 22, 2024 that stay on Lite keep a free tier of 50,000 MAU. Accounts that had at least one MAU in the twelve months before that date can also create new Lite pools that count against the same 50,000.
That makes "upgrading" an older pool expensive in a way that isn't obvious. A legacy pool with 60,000 MAU on Lite pays for 10,000 users: $55. Move it to Essentials and it pays for 50,000: $750. Same users, about thirteen times the bill. There was a promotion that let existing accounts move to Essentials at the old price, but it ended on November 30, 2025.
Switching Tiers Mid-Month
When a pool changes tier during a month, each MAU is billed at the highest-priced tier that was enabled while that user was active.
That turns "let's try Plus for a couple of days" into an expensive experiment. Take a Lite pool with 400,000 MAU. Someone enables Plus on day 12 to evaluate threat protection and switches back on day 14. 150,000 users happened to sign in during those two days.
| Calculation | Monthly | |
|---|---|---|
| 150,000 users active on days 12 or 13 | Plus, no free tier: 150,000 × $0.020 | $3,000 |
| 250,000 users active only on other days | Lite: 90,000 × $0.0055 + 150,000 × $0.0046 | $1,185 |
| Total | $4,185 | |
| Same month, Lite throughout | $1,875 |
Evaluate tiers in a separate pool with test users. Never in the production pool.
Machine-to-Machine Tokens
For the client credentials flow, Cognito charges per successful token response: $0.00225 in US East (N. Virginia), with no free tier. The pricing page says there's no additional charge for the number of app clients registered. What moves the bill is how often they ask for a token.
The most expensive version of this is a service that requests a new token before every call instead of caching it. At 10 requests per second, that's about 25.9 million token responses in a 30-day month, or $58,320.
Even with caching, token lifetime moves the bill. Take 200 workers that each fetch a token when the previous one expires:
| Access token validity | Token responses per month | Monthly |
|---|---|---|
| 5 minutes | 1,728,000 | $3,888 |
| 15 minutes | 576,000 | $1,296 |
| 60 minutes | 144,000 | $324 |
Access token validity is configured per app client. Pick it on purpose, cache the token until shortly before it expires, and if you run many workers for the same client, share one cached token between them instead of letting each fetch its own.
Costs That Don't Show Up on the Cognito Line
SMS
SMS for MFA, sign-up confirmation, password recovery, and phone verification is sent through Amazon SNS and billed at SNS rates, which vary a lot by destination country. None of it appears in a Cognito estimate.
SMS also attracts abuse. Sign-up and forgot-password flows that send a text message can be driven by bots to generate messages to numbers the attacker profits from. Three things help:
- Set the SNS account spend limit for SMS deliberately. The default is $1 a month, which protects you right up until someone raises it to fix a delivery problem and doesn't think about the new number. SNS can take a few minutes to stop sending once the limit is reached, so expect a little overshoot.
- Associate AWS WAF with the user pool and rate limit the sign-up and recovery endpoints.
- Prefer authenticator apps or email codes over SMS where your users allow it. Email one-time codes for MFA need Essentials or Plus, so weigh that against what you'd save on SMS.
Legacy advanced security features
Pools that enabled advanced security features (ASF) before the tiers existed still pay ASF as an add-on to Lite, with no free tier: $0.05 per MAU for the first 50,000, $0.035 for the next 50,000, and $0.02 after that.
At 100,000 MAU that is $495 for Lite plus $4,250 for ASF, $4,745 in total. Plus includes the same protections for $2,000. AWS's own example at 950,000 MAU puts Lite with ASF at $25,655 and Plus at $19,000. If any pool still has AdvancedSecurityMode set to AUDIT or ENFORCED on Lite, moving it to Plus is a straight saving.
Multi-Region replication
Replication is an add-on billed per MAU, $0.0045 on Essentials, and it is charged whether or not you ever fail over. For a pool with 500,000 MAU, that's $2,250 a month on top of a $7,350 base, about 31% more. It may be worth it. It should be a decision, not something that was switched on during a resilience review and forgotten.
Controls
Set the tier in code
Leaving the tier out means Essentials. Write it down.
resource "aws_cognito_user_pool" "main" {
name = "main-users"
user_pool_tier = "LITE"
}
aws cognito-idp update-user-pool \
--user-pool-id us-east-1_EXAMPLE \
--user-pool-tier LITE
update-user-pool replaces any settings you don't pass with their defaults, so run it with the pool's full current configuration or make the change in your infrastructure code instead. In the AWS CDK, the property is featurePlan: cognito.FeaturePlan.LITE.
Find out what you have
This lists every pool in a Region with its tier and any legacy ASF setting:
for id in $(aws cognito-idp list-user-pools --max-results 60 \
--query 'UserPools[].Id' --output text); do
aws cognito-idp describe-user-pool --user-pool-id "$id" \
--query 'UserPool.[Id,Name,UserPoolTier,UserPoolAddOns.AdvancedSecurityMode]' \
--output text
done
User pools are regional, so repeat it with --region for every Region you use. Anything on Essentials or Plus should have a reason. Anything on Lite with ASF set to AUDIT or ENFORCED should probably be on Plus.
Put a number on it
In the console, go to Billing and Cost Management → Budgets → Create budget, choose a cost budget, and filter by the service Amazon Cognito. Set the alert a little above your normal month. Most of the patterns in this post show up as a step change, so a budget alert catches them in the month they happen rather than when the invoice lands. AWS Cost Anomaly Detection is worth enabling alongside it.
Checklist
- Every user pool has its tier set in code, and the tier matches the features it uses.
- No scheduled job calls
AdminGetUseracross the user base. - Clients refresh tokens when they need them, not on launch or on a timer.
- Bulk migrations use the Migrate User trigger instead of
AdminCreateUser. - Tier evaluations and load tests run against a separate pool, and nobody assumes each account has its own free tier.
- M2M clients cache tokens, and the access token validity was chosen on purpose.
- No pool runs Lite with legacy ASF, SMS has a spending limit, and the pool has WAF in front of sign-up.
Nearly every item on that list is a one-time decision. That's the point of making them early. A Cognito bill rarely grows because more people use the product. It grows because of what the pool was set up to count.




Top comments (0)