In short
With eight queries, in half an hour you'll know how many users actually log in, how many licenses you have assigned and free, which AppExchange packages you pay for, and how many custom objects, Flows, Apex classes and triggers are in your org. It's the foundation for negotiating a renewal or sizing a migration.
Before you start
You need a user who can view the setup. Run the queries from the Developer Console, in the Query Editor, or from Workbench. The ones marked Tooling API require turning that option on. None of them change anything: they're all read-only.
1. Active users who never log in
SOQL
SELECT Name, Profile.Name, LastLoginDate
FROM User
WHERE IsActive = true
AND (LastLoginDate < LAST_N_DAYS:90 OR LastLoginDate = null)Each row is a license you pay for that nobody has used in three months.
2. Licenses purchased and used
SOQL
SELECT Name, TotalLicenses, UsedLicenses
FROM UserLicense
WHERE TotalLicenses > 0Compare the Salesforce license with Salesforce Platform. Many people who only look things up could get by with a cheaper license.
3. Permission set licenses
SOQL
SELECT MasterLabel, TotalLicenses, UsedLicenses
FROM PermissionSetLicense
WHERE TotalLicenses > 0This is where add-ons assigned per user show up, such as CPQ or AI features.
4. AppExchange packages
SOQL
SELECT NamespacePrefix, Status, AllowedLicenses, UsedLicenses, ExpirationDate
FROM PackageLicenseEach package is another invoice, another vendor and another dependency in a migration.
5. Custom objects
Tooling API
SELECT DeveloperName, NamespacePrefix, CreatedDate
FROM CustomObject
WHERE NamespacePrefix = nullThen count the records in the ones that matter with SELECT COUNT() FROM Objeto__c. An object with no new records in a year is a candidate to be left out.
6. Active Flows and processes
SOQL
SELECT ApiName, ProcessType, LastModifiedDate
FROM FlowDefinitionView
WHERE IsActive = trueThe ones with ProcessType = 'Workflow' are Process Builder processes, out of support since the end of 2025.
7. Your own Apex code
Tooling API
SELECT Name, LengthWithoutComments, LastModifiedDate
FROM ApexClass
WHERE NamespacePrefix = nullLong classes nobody has touched in years are debt. The ones that carry your business logic are the first thing to understand, whether you stay or go.
8. Triggers
Tooling API
SELECT Name, TableEnumOrId, Status
FROM ApexTrigger
WHERE NamespacePrefix = nullSeveral triggers on the same object, plus Flows and rules, is the recipe for those errors nobody can explain.
What to do with the results
- Right-size your licenses before renewal.
- List what you pay for and don't use: packages, add-ons and permission set licenses.
- Flag which objects, Flows and code are still alive. That's the real scope of any change.
If you'd rather not do it yourself, in the first week of a project we run this same audit with a read-only user. We don't change anything in your org.
FAQ
Do I need to be an admin?
You need the View Setup and Configuration permission and, for the Tooling API, API access. Usually that means an admin.
Do these queries change anything in my org?
No. They're read-only queries.
Do they work on Pro Suite?
It depends on your edition's API access. On Enterprise, Unlimited, Core or Advanced, yes.
Sources: SOQL reference (Salesforce Developers), Tooling API guide (Salesforce Developers).