TL;DR
- Licence waste and excess permissions have the same cause: access is granted when someone asks and never revisited when the need ends. The audit that finds one finds the other.
- Three layers cost you money and most audits check one: user licences (one per user), permission set licences, and feature licences. A user can hold a cheap user licence and expensive add-ons.
- Start with
UserLicensefor total against used, then last login for idle seats, then the harder question of whether active users need the licence type they hold. - Ninety days without a login is the practical line. A seat unused for a quarter rarely comes back before renewal.
- The biggest single saving is usually licence type mismatch, not idle seats: users on a full CRM licence who only ever touch custom objects.
- For Experience Cloud, the question is member-based against login-based, decided per audience by usage pattern. Getting that wrong costs more than any number of idle internal seats.
- Run it 60 to 90 days before renewal. Later than that and you are negotiating without evidence.
What You'll Learn
- The three licence layers, and which one hides the most waste
- How to get the headline numbers with two SOQL queries
- Why idle seats are the easy half and licence-type mismatch is the expensive half
- How member-based and login-based Experience Cloud licences fail differently
- Why this is the same exercise as a privileged access review
The Problem
Licences get bought in a hurry and reviewed once a year, usually about a fortnight before renewal, by which point the negotiation has already started and nobody has evidence.
Meanwhile the org accumulates. Someone leaves and their user is deactivated but nobody reduces the seat count. A department trials a feature, the permission set licences get assigned, the trial ends and the assignments stay. A team gets full CRM licences during implementation because it was simpler than working out what they needed, and five years later they still only use custom objects.
None of this is anyone's mistake exactly. It is the same drift that produces over-permissioned users, from the same cause: granting access is a request somebody makes, and removing it is a task nobody owns. That is why we treat licence review and access review as one exercise rather than two.
Common questions this article answers:
- How do I find out what we are actually paying for and what is idle?
- Which users could be on a cheaper licence type?
- When should I run this so the answer is useful?
Quick Answer
Salesforce charges you across three layers: user licences, where each user holds exactly one, permission set licences that add capability on top, and feature licences. Most audits look only at the first. Get the headline picture with SELECT Name, TotalLicenses, UsedLicenses FROM UserLicense, then repeat it against PermissionSetLicense, which is where quietly expensive add-ons sit. Find idle seats by listing active users whose LastLoginDate is more than 90 days ago or null, since a seat unused for a quarter rarely returns before renewal. Then do the harder and more valuable pass: check whether active users need the licence type they hold, because the largest saving is usually people on full CRM licences who only ever touch custom objects and would run fine on Platform. For Experience Cloud, decide member-based against login-based per audience based on actual login frequency rather than headcount, since the wrong model costs more than any number of idle internal seats. Run the whole exercise 60 to 90 days before renewal so there is time to act and validate.
The three layers
Worth separating, because they are bought differently and audited differently.
User licences. Every user has exactly one, and it sets the baseline of what they can do. Salesforce, Platform and Identity are the common internal types. This is the layer everyone checks, and it is the one where the counts are easiest to see.
Permission set licences. These sit on top of a user licence and unlock specific capability. They are assigned individually, which means they accumulate individually, and because they do not appear on the user's profile they are easy to miss entirely. This is where I most often find things still assigned years after the project that needed them finished.
Feature licences. Smaller, checkbox-style entitlements on the user record. Individually cheap, collectively untidy.
The important consequence: a user on an inexpensive user licence can still be expensive. If you only audit layer one, you will conclude the org is efficient while paying for add-ons nobody uses.
The two queries that give you the picture
-- Headline: what you own against what is assigned
SELECT Name, TotalLicenses, UsedLicenses
FROM UserLicense
ORDER BY Name
-- The layer most audits miss
SELECT MasterLabel, TotalLicenses, UsedLicenses
FROM PermissionSetLicense
ORDER BY MasterLabel
TotalLicenses minus UsedLicenses is what you are paying for and have not assigned. That number is genuinely useful and also the least interesting finding, because unassigned licences are visible and therefore usually already known.
The interesting number is assigned-but-unused, which needs a different query:
-- Active users who have not logged in for a quarter
SELECT Id, Name, Username, Profile.Name, UserType, LastLoginDate
FROM User
WHERE IsActive = true
AND (LastLoginDate < LAST_N_DAYS:90 OR LastLoginDate = null)
ORDER BY LastLoginDate NULLS FIRST
LastLoginDate = null is the row to look at first. An active user who has never logged in is either a provisioning mistake, an integration user that authenticates another way, or a person who left before they started.
Two cautions before you act on that list. Integration and API users may legitimately show no interactive login, so check UserType and the username pattern before deactivating anything. And seasonal roles exist: a user who logs in at quarter end will look idle for 89 days and then need access urgently.
Idle seats are the easy half
Deactivating idle users is satisfying, visible, and usually the smaller saving.
The larger one is licence type mismatch: users who hold a full CRM licence and only ever use custom objects. Platform licences cost materially less than full CRM ones, and the gap per user per year is large enough that a handful of misassigned users outweighs a dozen idle seats. I am deliberately not quoting figures, because list prices move and what you actually pay is whatever you negotiated, so use your own contract rather than a blog's numbers.
The test is not what a user could do, it is what they demonstrably do. A user who never opens a standard CRM object, never touches Opportunities, Leads, Cases or Forecasts, and lives entirely in custom objects, is a Platform candidate.
Finding them properly needs usage rather than assumption. Options in increasing order of effort:
- Profile and permission set inspection, which tells you what they are entitled to and not what they use
- Login history and setup audit trail, which shows activity but not object-level usage
- Event Monitoring, if you have it, which shows actual object access and answers the question directly. Free-tier EventLogFile covers some of this, and we cover capturing it in free event monitoring with EventLogFile
Be careful with downgrades. Moving a user to Platform removes access to standard CRM objects, and if any of their automation, reports or integrations depend on that access it breaks. Test with one willing user before doing a batch.
Experience Cloud is a different question
External licensing fails differently, and it usually fails larger.
The decision is member-based against login-based, audience by audience:
Member-based licences are provisioned per user and cost scales linearly with how many people you provision. Predictable, and expensive if most of them rarely visit.
Login-based licences are bought in blocks and consumed by monthly logins. They suit audiences who visit occasionally, and they become expensive fast for anyone who logs in daily.
The failure mode is not choosing wrongly at the start. It is that the audience changes and the model does not. A portal launched for a few hundred occasional users on login-based licences becomes a daily tool for a subset, and consumption climbs without anyone deciding anything. Nothing in the system flags that the model no longer fits.
So the review question is per audience: how often does this group actually log in, and does the model still match? Answer it with login history rather than with what you assumed when you launched.
Why this is an access review
The reason to run these together is that they share a root cause and a fix.
An idle licence and a standing privilege are both access that outlived its purpose. Both were granted for a real reason. Neither had an owner responsible for removing it. Both are found by asking the same question: who has this, and do they still need it?
The practical consequence is that one exercise produces two deliverables. The list of users who have not logged in for 90 days is a licence saving and a security finding, because a dormant account with live credentials and standing access is exactly what an attacker prefers. The list of permission set licences still assigned after a project ended is a cost line and an over-permissioning finding.
If you already run privileged access reviews, licence review is a column added to a report you produce anyway, which is considerably cheaper than a separate annual project. The access model side of that is covered in access model sizing for internal and external admin teams and what developers actually need instead of Modify All Data.
Timing
Run it 60 to 90 days before renewal.
Earlier is fine and less useful, because the picture drifts. Later is common and close to worthless: you find savings you no longer have time to realise, since deactivating users and downgrading licence types needs validation, and you end up in a negotiation quoting numbers you have not tested.
The 60 to 90 day window gives you time to identify candidates, act on the confident ones, watch for breakage, and arrive at the conversation with a defended position rather than an assertion.
Frequently Asked Questions
Q: What is the fastest way to see what we own?
A: Setup, then Company Information, shows user licences, permission set licences and feature licences with used and remaining counts. For anything beyond a glance, query UserLicense and PermissionSetLicense so you can diff the result over time instead of reading a screen.
Q: Is 90 days the right threshold for idle?
A: It is a good default rather than a rule. It matches a quarter, which is long enough to survive leave and short enough to act on before renewal. Adjust for your business, and always exclude integration users and genuinely seasonal roles before acting.
Q: Can we just deactivate users to free seats?
A: Deactivating frees the assignment so the seat can be reused, which is what you want mid-term. It does not reduce what you are contracted for; that happens at renewal. This is exactly why the timing matters: deactivating in month eleven saves nothing this cycle.
Q: What is the single biggest saving usually?
A: Licence type mismatch, not idle seats. Users on full CRM licences who only ever use custom objects can often move to Platform, and the per-user gap is large enough that a handful of them outweighs a long list of dormant accounts. It is also more work to prove, which is why it gets skipped.
Q: How do we stop it drifting back?
A: Give removal an owner. The reason both licences and permissions accumulate is that granting is a request someone makes and removing is a task nobody owns. Attaching licence review to an existing access review, with a named owner and a date, is more durable than any one-off cleanup.
Key Takeaways
- Licence waste and over-permissioning are the same failure, so audit them together and get two deliverables from one exercise.
- Three layers cost money. Checking only user licences misses permission set licences, which is where forgotten add-ons live.
- Idle seats are the easy half. Licence type mismatch is usually the bigger number.
- Ninety days without a login is a good idle threshold, after excluding integration and seasonal users.
- For Experience Cloud, the model has to match the audience, and audiences change without anyone revisiting the decision.
- Sixty to ninety days before renewal, or you are negotiating without evidence.
What's Next?
Recommended Reading:
- Access model sizing for internal and external admin teams
- What developers actually need instead of Modify All Data
- Free event monitoring with EventLogFile
- Salesforce cancelled the profile permissions retirement. Migrate anyway.
Action Items:
- Run the
UserLicenseandPermissionSetLicensequeries and record the numbers, so next quarter you have a diff rather than a snapshot. - Pull active users with no login in 90 days, exclude integration and seasonal accounts, and treat the remainder as both a cost list and a security finding.
- Put a date in the calendar 90 days before renewal, with a named owner. Without that this is a one-off cleanup that drifts straight back.
Responses
Checking your session.
Loading responses.