Note! This tutorial is for Google Workspace for organisations and not the free consumer account, unfortunately.
While the Google Apps Script docs provide a great example of how to get a list of users in a Domain on a Google Workspace account, it is not in the scope of the documentation to go into the weeds and explain all the ways we can search for all users.
Weeds sound much more like the purview of a goat. A coding goat, perhaps 🐐. Me. I’m talking about me…yeesh!
In this tutorial, we will cover how to access your Google Workspace organisation’s user data, what data you can retrieve and how it looks, who can retrieve it and a couple of ways to display what you need.
This post is intended as a resource reference that compliments the Google Docs on the Admin SDK. Links to the Google documentation are provided throughout the post. It is worth a bookmark if you intend on using the Admin SDK a lot in Google Apps Script.
Use the contents page to navigate to what you need.
Table of Contents
Setup
To access your organisation’s user data in Google Apps Script, you will need to use the Admin SDK Directory Service. This is an Advance Service and will need to be enabled in your Google Apps Script console and you also need to ensure that it is enabled in your domain.
My recommendation is to first check to see if you can load and run a test script in your Google Apps Script IDE before bothering your organisation’s admin. However, because you will be dealing with user account data that you can view, add or remove, I would strongly recommend getting your admin’s permissions along with presenting them your plans before diving into your project.
Enable in the Editor
To do this click on the Services button in the editor sidebar (1). Then select the Admin SDK (2) and click Add (3).
You will know that the Advanced Services has been added correctly when you see it appear in the sidebar.
Enable on the Domain
If you also are your organisation’s administrator you can follow these steps. Likewise, you could forward this section to your Google Workspace admin team to save them the hassle of searching through the docs on your behalf. They will be eternally grateful for saving them the hassle.
Don’t worry, I will add links to the official docs too.
First, head to the Google Workspace admin console and sign in. On the left-hand side select the Security button (1). This will expand a sub-menu where you can click API controls(2).
Your main window will update, and from here you can select the blue Manager Google Services in the main window.
Scroll down the list of APIs until you find: Google Workspace Admin
Then select the option you are most comfortable with for your project. Most likely it will be unrestricted if you need other users to access a script with the Admin SDK. Or if there has been no radio button selected, it can be left blank and you can hit cancel.
Once you or your Google Workspace admin have completed this step, head back to your project and give it a test run.
Keep in mind that even when you select one of these options there are two different access privileges to view the user’s full admin details and then what they show to the public. You may need your admin to provide you with some limited admin access if you are working in non-public user data.
Note! The information in the Docs under Setup Your API, I believe, is outdated (Well at the time of writing this 01 Aug 2021). You can check the last update of the doc in the bottom left of the page.
On with the show…
Getting the User Data
Get all user details in your org.
If you are trying to build an updated HR list on a Google Sheet or a dropdown list of all users in your domain, you are probably going to use some version of this code.
Check it out.
Getting your list of users
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function getAllUsersInOrgRaw() { const list = AdminDirectory.Users.list({ customer: 'my_customer', }) return list } function runsies(){ console.log(getAllUsersInOrgRaw()) } |
I’ve put this in a function for now because we will build this out in a moment. First, let’s take a quick look at lines 3-4. We start by calling the AdminDirectory service and then the Users resource. Inside this resource, we can use the ‘list’ method.
Now you can’t simply get a general list without adding an argument to the ‘list’ method.
Minimum Code
The very minimum you will need is to provide the customer
property if you want all users across all domains in your Google Workspace’s organisation account. You can use the “my_customer” alias for your own account.
Alternatively, if you only want a particular domain in your account or you only have one domain in your account, you could replace customer
with domain
. Like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function getAllUsersInOrgRaw() { const list = AdminDirectory.Users.list({ domain:'reitsma.com' }) return list } function runsies(){ console.log(getAllUsersInOrgRaw()) } |
The result of either of these approaches will be a JSON object containing the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "kind": string, "etag": string, "users": [ { object (User1) << All the user information }, { object (User2) << All the user information }, { ... }, ... ], "nextPageToken": string } |
The 'kind'
property describes the type of object that we have extracted. For us, this will be "admin#directory#users"
.
'etag'
is the unique id of the resource that you have extracted.
'users'
will be your main array of users in your domain or organisation and their associated data.
'nextPageToken'
will provide a token if you have limited the number of 'users'
you wanted to call from the Admin SDK by n amount. If you are iterating through your full list at say, ten users at a time, each time you will get a new page token ID until there are no more users to review. In which case, you will get undefined
. This is important when you need to draw from a list of users that will be greater than 100.
Just the user data
So the main thing we need to look at is the 'users'
data to extract the information on each user that we need. This is a simple case of adding the 'users'
property extension. Our new code will look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function getAllUsersInOrgUsers() { const users = AdminDirectory.Users.list({ customer: 'my_customer', }).users return users } function runsies(){ console.log(getAllUsersInOrgUsers()) } |
Note that we grab the 'users'
property after the list()
methods on line 4.
This will provide us with our array of user data. By default, it will be in ascending order by the primary email address for each user.
Each user’s details can now be accessed in an array. For example, users[0]
for the first user object in the array will contain an object of key-value data pairs about that user. Alternatively, the 'users'
list can be iterated by using your preferred iteration method.
What data can I see?
As a developer with administrative privileges you will see something like the following results for each user:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
{ nonEditableAliases: [ 'admin@yagisanatode.com.test-google-a.com' ], agreedToTerms: true, orgUnitPath: '/', emails: [ { primary: true, address: 'coder@yagisanatode.com' }, { address: 'admin@yagisanatode.com.test-google-a.com' }, { address: 'goattime@yagisanatode.com' } ], organizations: [ { primary: true, name: 'Yagisanatode Co.', customType: '', location: 'The Farm', title: 'Goat About Town' } ], aliases: [ 'ningirsu@yagisanatode.com' ], isEnrolledIn2Sv: false, suspended: false, primaryEmail: 'coder@yagisanatode.com', id: '1216511551566451165165', phones: [ { type: 'work', value: '07 5555 555' }, { type: 'mobile', value: '0455 555 555' } ], isEnforcedIn2Sv: false, name: { givenName: 'Yagi', fullName: 'Yagi T Goat', familyName: 'Goat' }, creationTime: '2017-02-21T09:36:45.000Z', customerId: 'A029yt344', includeInGlobalAddressList: true, kind: 'admin#directory#user', changePasswordAtNextLogin: false, ipWhitelisted: false, isMailboxSetup: true, isDelegatedAdmin: false, archived: false, etag: '"skjhjsdjhdfsds-654s6546sfdflksfdlkjksdnsdonsdfkdsfdoKNSJFKNkngdsk"', isAdmin: false, lastLoginTime: '2021-07-04T20:26:49.000Z' } |
You can find a great detailed explanation of each of the properties in the docs or another example from Google’s Apps Script guide.
Getting User Data for Non-Admin Accounts
You can also set up your code so that non-users can get a limited list of information about each user in a domain. This approach will show user data that is publically available. This might be useful if you are providing a custom form in a Web App or a dialogue window when your users have a prefilled ‘select’ input.
Using this approach might solve access errors if you don’t have admin privileges when you are writing your code (No 100% sure here so if anyone can confirm, I will update this part).
To get a user…well…usable list, we can need to add the 'viewType'
parameter and set it to 'domain_public'
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function getAllUsersInOrgUsersPublic() { const list = AdminDirectory.Users.list({ customer: 'my_customer', viewType:'domain_public', }).users return list } function runsies(){ console.log(getAllUsersInOrgUsersPublic()) } |
This will result in a much more limited list of information for each user:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ phones: [ { type: 'work', value: '07 5555 555' }, { type: 'mobile', value: '0455 555 555' } ], emails: [ { primary: true, address: 'coder@yagisanatode.com' }, { address: 'goattime@yagisanatode.com' } ], primaryEmail: 'coder@yagisanatode.com', kind: 'admin#directory#user', name: { givenName: 'Yagi', fullName: 'Yagi T Goat', familyName: 'Goat' }, organizations: [ { primary: true, name: 'Yagisanatode Co.', customType: '', location: 'The Farm', title: 'Goat About Town' } ], id: '1216511551566451165165', etag: '"skjhjsdjhdfsds-654s6546sfdflksfdlkjksdnsdonsdfkdsfdoKNSJFKNkngdsk"', } |
Let’s keep this in our code from now on so it is a little easier to read.
Users are Greater Than 100
Out of the box, your list()
method query will only display a max of 100 users that can be modified to 500 users per page.
Set your max user results for each list call.
You can set the number of users to be displayed by using the 'maxResults'
query parameter. Without adding this query to your query object, your max results will automatically be capped at 100 users per page (or request to the Admin SDK). You can change this by adding this query to your query object. You can provide a value anywhere between 1 and 500 users to be displayed each time you call the list method.
If you have a number of users below the 'maxResults'
amount, then it will only display that amount.
Here is what it will look like in our code example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function getAllUsersInOrg() { const list = AdminDirectory.Users.list({ customer: 'my_customer', viewType:'domain_public', maxResults: 50 }).users return list } function runsies(){ console.log(getAllUsersInOrg()) } |
Personally, I think it would be a good approach to stick with 100 or fewer users per page. Any more and your request will take some time and may create a call error. It’s perhaps a flakey and overly cautious opinion, but there you go, I’m a flakey and overly cautious old goat.
Alternatively, if you have 1,000 users in your organisation and decide to set maxResults
to something bonkers like 2 then you may end up with some more quota limitations.
Get the next page of users in your list
If you have more users than your 'maxResults'
, then you will need a way to access these users. You can do this with the 'nextPageToken'
. This is one of the main object properties that is on the same level as the 'users'
and 'kind'
property.
The 'nextPageToken'
will store a token key for you that might look like this:
1 2 3 |
... "nextPageToken":"CloKJvf-2kD9_____56InpOTnpya_pD-__6v3JjKmp3LzZmZyZvN__4QCiHNitAn7xtNdzkAAAAAAr8lAUgBUABaCwmh18AxpMA7ghADYMSUvsQFcgYIhNGiiAY=" ... |
You can then use this token to reference the starting point of another call using the list()
method. This will give you the next set of 100 users in your domain or organisation. If you have over 200 users, then a new 'nextPageToken'
will be available for you to reference.
Let’s give a very basic example to get the hang of using this token. Imagine we have 19 users in our organisation. We want to display these users in two sets so we will create a max result of 10 for each page.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function getAllUsersInOrg() { const pageOne = AdminDirectory.Users.list({ customer: 'my_customer', viewType:'domain_public', maxResults: 10 }) let pageOneUsers = pageOne.users console.log(pageOneUsers) let pageToken = pageOne.nextPageToken //Token form first page. const pageTwo = AdminDirectory.Users.list({ customer: 'my_customer', viewType:'domain_public', maxResults: 10, pageToken:pageToken // << Note the addition of the pageToken query. }) let pageTwoUsers = pageTwo.users console.log(pageTwoUsers) } |
Here on line 6, we have set the max results for our first page to 10. When we make this first call to the list()
method, it will display a page token for the next page that we have stored in the pageToken
variable on line 12.
Now the second time we call the list()
method it will remember that page token and allow us to start our list from the 11th user if we add the pageToken
query to our list of queries on line 16.
Note that if there are no more users in the organisation then the page token will be undefined, or not displayed in the object.
Looping through pages of users
Of course, it is all a bit silly for us to hard-code each time we want to call our next page. We are probably going to want to loop through each page until we run out of users and store our user data somehow.
Here we are going to create a version of Eric Koleda’s excellent example in the Google Apps Script version of the docs (Also a glaring reminder that I should be using do/while for more things 😁).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
function getAllUsersInOrg() { let page = ''; let pageToken = ''; let staffList = []; do{ page = AdminDirectory.Users.list({ customer: 'my_customer', viewType:'domain_public', maxResults: 100, pageToken: pageToken }) staffList = staffList.concat(page.users) pageToken = page.nextPageToken; }while(pageToken) return staffList; } function runsies(){ console.log(getAllUsersInOrg()) } |
We use a while loop here because we can easily state that when there are no more page tokens, we will stop our iterations. Using Javascript’s do
will allow us to carry out our first call to the list()
method to get our first pageToken
before starting our loop.
First off we will set some variables that we will update throughout our loop. Lines 3-5
Next, we will commence our do
statement with our list()
method call, not forgetting to add in the maxResults
and pageToken
queries this time. Line 8-13
Once we grab each page we will concatenate the user information to our staffList
array container. Line 15
Then, we need to update the pageToken
item so it can be read on the first iteration of the while loop. Line 17
Once we loop through all the pages we can return our staffList
to do that voodoo that you do so well on.
Getting selected data from each user
Quite often you will not want to get all the data from each user. You will only want to store some of it. Let’s say we want to get a record of all of our users’ first names, last names and primary emails.
No worries.
All we need to do is loop through each member on our page and store just the data we require in a small object and return that to our staffList
variable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
function getAllUsersInOrg() { let page = ''; let pageToken = ''; let staffList = []; do{ page = AdminDirectory.Users.list({ customer: 'my_customer', viewType:'domain_public', maxResults: 10, pageToken: pageToken, }) // Store only selected info for each user. let staffMember = page.users.map(user => { return { 'firstName': user.name.givenName, 'lastName' : user.name.familyName, 'email' : user.primaryEmail } }) staffList = staffList.concat(staffMember) pageToken = page.nextPageToken; }while(pageToken) return staffList; } function runsies(){ console.log(getAllUsersInOrg()) } |
For convenience and ease of reading, we will use the JavaScript map() method to create a new object for each user and store the values in the staffMemeber
variable. Then we will concatenate this to our staffList
. Lines 16-22
The results of this function will look a little like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[ { firstName: 'Yagi', lastName: 'Goat', email: 'coder@yagisanatode.com' }, { firstName: 'Kid', lastName: 'Pygme', email: 'kid@yagisanatode.com', }, ... ] |
Dealing with optional user properties (schemas) in the Organisations property
Businesses can store a lot of useful information in the 'organizations'
property – like the user’s job title, their department, their physical work location and even custom information fields that your admin has added for the organisation.
Single organisations
If your Google Workspace is set up with a single organisation then there will be only one item in the array of 'organizations'
. However, sometimes not all users have been assigned to this one organisation. This is often the case for external consultants.
If we started searching for an item in the organisation then we will end up with an error.
Likewise, organisation information is not mandatory, so there may be some users who don’t have a property in their organisation and this will end up with an ugly undefined
result.
We can fix all of this up with a bit of conditional handling.
Let’s say in addition to our first name, last name and email, we want to add the user’s job title… if they have one. We can find the job title in the users organisations under 'title'
.
We will use some JavaScript ternary operators to hand any errors or undefined titles inline. Here is our updated code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
function getAllUsersInOrg() { let page = ''; let pageToken = ''; let staffList = []; do{ page = AdminDirectory.Users.list({ customer: 'my_customer', viewType:'domain_public', maxResults: 100, pageToken: pageToken, }) // Store only selected info for each user. let staffMember = page.users.map(user => { return { 'firstName': user.name.givenName, 'lastName' : user.name.familyName, 'email' : user.primaryEmail, 'job' : (user.hasOwnProperty('organizations'))? (user.organizations[0].hasOwnProperty("title"))? user.organizations[0].title : "" : "" } }) staffList = staffList.concat(staffMember) pageToken = page.nextPageToken; }while(pageToken) return staffList; } function runsies(){ console.log(getAllUsersInOrg()) } |
First, on line 22, we check that our user property contains the organizations
child property. If it does, then we check the zeroeth item of the organisations
list to see if it has the 'title'
property (Line 23) if it does, then we want to grab that title. Otherwise, we want to return a blank string (Line 24).
User with multiple organisations
Sometimes large Google Workspace groups have multiple organisations assigned to them or the admin will separate the company into organisations by office location or child company. In some of these cases, a user might have a position under a number of these organisations.
If a user is assigned to multiple organisations they will always retain a primary organisation. This can be found, within the organisation’s property under 'primary'
.
The primary organisation may not always be the first organisations in the users organisations list. In this case, we need too filter through our list of organisations and display only the primary organisation.
We could probably use an inline function here, but I think it will start to look a little messy and confusing. Let’s go ahead and reference a method inside our function instead.
It’s also gives us a good opportunity to create a universal organisation lookup tool.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
function getAllUsersInOrg() { let page = ''; let pageToken = ''; let staffList = []; do{ page = AdminDirectory.Users.list({ customer: 'my_customer', viewType:'domain_public', maxResults: 100, pageToken: pageToken, }) // Store only selected info for each user. let staffMember = page.users.map(user => { return { 'firstName': user.name.givenName, 'lastName' : user.name.familyName, 'email' : user.primaryEmail, 'job' : getUserOrgItem(user, 'title') } }) staffList = staffList.concat(staffMember) pageToken = page.nextPageToken; }while(pageToken) /** * Get users' organisation data if > 1 org in Workspace. * @param {object} user - user object for each user. * @param {string} item - property (Schema) to search. * @returns {string} Selected property's value. */ function getUserOrgItem(user, item){ if(user.hasOwnProperty('organizations')){ const primaryOrg = user.organizations.flatMap( org => (org.primary == true)? org : "")[0] return primaryOrg.hasOwnProperty(item)? primaryOrg.title : ""; }else{ return ""; } } return staffList; } function runsies(){ console.log(getAllUsersInOrg()) } |
Let’s say we want to access the job title for each user in their primary organisation. On line 22, we reference our newly built getUserOrgItem()
method that takes two arguments, the currently iterated user object for each user and the property in the organisation list that we want to select. For us, this is the ‘title’ property.
Line 33-48. We kick off the function by changing out the ternary operator with a simple ‘if’ statement and check first if the user has an object property (Line 42).
If they do, then we can use JavaScript’s fairly recent addition to their awesome list of functional iterators the flatMap(). The flatMap()
method can basically map through a set of data and then flatten it by one level. However, one of the benefits of this is that it can also be used like a map().filter() combo in one hit. Pretty cool, hey?
On line 42, our goal is to get the primary organisation object and store it in our primaryOrg
variable. To do this we access our organisations’ list and apply the flatMap() method. This method takes a callback function that we have created in the form of an arrow function.
Next, we use a ternary operator to check if there primary property in each of the organisation objects in the list contains true. If it is true then we store that object. Finally, we extract it from the array it is in by accessing the zeroeth value.
On line 44, we then check to see if the title exists in the primary organisation object and if it does we return the title otherwise we return an empty string.
Finally, if no organisation property exists in the user object, then we just return an empty string.
Our result will then look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[ { firstName: 'Yagi', lastName: 'Goat', email: 'coder@yagisanatode.com', job: 'Goat About Town' }, { firstName: 'Kid', lastName: 'Pygme', email: 'kid@yagisanatode.com', job: 'Adorable Distraction' }, ... ] |
Accessing Custom Fields (Schema)
Sometimes your administrator will add custom field attributes (schema) into your user’s profiles. These schema parent categories have their own fields. These schemas are not always visible when you call the list()
method.
You can display ALL (full) or partial custom fields by using the projection
query.
Display all (FULL) custom fields
To display a full list of all your custom fields you apply the 'full'
value.
1 2 3 4 5 6 7 8 |
function orderBySortOrder(){ const list = AdminDirectory.Users.list({ customer: 'my_customer', projection:'full' }) console.log(list) } |
Display Partial custom fields
You can also display a partial list of custom fields. You will need to call an additional query, customFieldMask
and set projection
to custom
. In the customFieldMask, you can provide a comma-separated (no spaces) list of all the schemas that you want to be displayed. Let’s say we have two fields that we want to access in the staffTraining
schema hasCompletedInduction
, coursesAttended
. Here is what it would look like:
1 2 3 4 5 6 7 8 9 |
function orderBySortOrder(){ const list = AdminDirectory.Users.list({ customer: 'my_customer', projection:'custom', customFieldMask: 'staffTraining' }) console.log(list) } |
Note that using viewType:'domain_public'
may also prevent you from seeing all the custom fields. So if you aren’t finding what you are looking for, try removing this option.
Accessing the custom field data
Your custom schema and associate field data will be found in your user’s object. You access it by calling the custom schema key name followed by the custom field.
1 2 3 4 5 |
users[0].myCustomSchema.myCustomField e.g. users[0].staffTraining.hasCompletedInduction |
Ordering and Sorting your Users Data
The Google Apps Script Admin Directory API allows you to use the query object to sort and order your data before you receive it.
By default, Google will sort your users alphabetically by primary email address.
You can change this behaviour with the ‘orderBy’ and ‘sortOrder’ queries. Here are your options:
Note that the Admin Directory documentation displays capitalisation but in Google Apps Script, things are a little different.
Here is an example of the inclusion of these two queries:
1 2 3 4 5 6 7 8 9 10 11 |
function orderBySortOrder(){ const list = AdminDirectory.Users.list({ customer: 'my_customer', viewType:'domain_public', maxResults: 10, orderBy:'givenName', sortOrder:'descending' }) console.log(list) } |
Searching for Users using Parameters
The Google Admin Directory API provides an extensive tool for you to search for lists of users based on specific parameters.
You can use the 'query'
parameter of the list query objects. Yeah…confusing. I know. Essentially, the object inside your list() method’s curly braces is a query parameter. Within this, is the 'query'
parameter (It’s turtles all the way down 🤣).
The 'query'
parameter takes one or more sets of searches. The docs on this are really extensive and clear here so I won’t go into too much detail except for providing a few Google Apps Script specific examples so that you can start experimenting on your own.
A successful query will return a list containing only those users that successfully met your search criteria. An unsuccessful query will result in an object with just the kind
and etag
properties.
A search query consists of three elements:
- Field: These are the searchable properties available to you. Note, that they don’t always match the property name in the users’ JSON object. It is best to find the corresponding field name in the docs. For example, if you want to search by the first name then you would use the field
givenName
. Likewise, if you wanted to search by job title you would use the fieldorgTitle
. - Operator: This is how you want to query the field. You can use the following operators:
=
(Equal to) Strings, numbers, dates and booleans.:
(Contains text) Strings.:{PREFIX}*
(Text starts with) String.:[{MIN,MAX}]
(Field within a range) Numbers, dates.>
(Greater than) Number date.>=
(Greater than or equal to) Numbers and dates.<
(Less than) Numbers and dates.<=
(Less than or equal to) Numbers and dates.
- Value: These are the values you will use to search either as a complete value on its own or part of a range or a partial value, depending on the operator that you choose and the value type. Values can be strings, booleans, numbers or dates.
Writing a Query in Google Apps Script
A basic example of applying a query to your Admin Directory List method would be something like this:
1 2 3 4 5 6 7 8 9 |
function orderBySortOrder(){ const list = AdminDirectory.Users.list({ customer: 'my_customer', query:'orgTitle=Goat' }) console.log(list) } |
In this example, we have run a search for anyone in our Google Workspace account with the title ‘Goat’. You can still also apply other queries to the list object like orderBy
or viewType
.
Searching Custom Fields in Custom Schema
To search a custom field in a custom schema you can reference the schema followed by the field ID. For example:
1 2 3 |
... query:'myCustomSchema.myCustomField=queryvalue' ... |
Let’s go into more detail for each of the value types.
Text (String)
Any of our user properties that have a string value like text, email or phone numbers can be searched if they have a corresponding field reference or custom schema found in the search docs.
Search equals
Let’s say we want a list of everyone in the same “Finance” department then we could run the following query:
1 2 3 |
... query:'orgDepartment=finance', ... |
There are a couple of things to note here. First, the entire query is encapsulated in a text string. You can use single quotation marks, double quotation marks or backticks for this.
Spaces count. If you don’t have any leading spaces in our search, do not add them. Ensure there are no spaces between your query field, your operator and your query.
Queries are not case sensitive. You can use upper or lowercase (like in the example) to get the same result.
Spaces and Apostrophies (‘)
Let’s say you have a search query to find all of your organisation’s staff who are in a particular location. Here we will use the addressLocality
field to search for the same town or city. We will call our town, ‘Tragos Landing’.
Spaces in the query announce a new query so we will need to find a way around this.
There are generally three approaches:
- Replace your spaces with a”+” symbol.
1 2 3 |
... query:'addressLocality=tragos+landing', ... |
- Change your string encapsulator from a single quotation (
'
) to double quotations ("
) or backticks (`
).
1 2 3 4 5 6 7 |
... query:"addressLocality='tragos landing'", //Double quotation ... OR ... query:`addressLocality='tragos landing'`, //Backticks ... |
- Escaped single quotation marks. If you are a devout single quotation acolyte and simply must use them (weirdo), you can escape them.
1 2 3 |
... query:'addressLocality=\'tragos landing\'', //Ecaped single quotation ... |
You can also use the escape method to escape apostrophes. Let say our location is now ‘Yagi’s Place’. Our query would look like this.
1 2 3 |
... query:'addressLocality=yagi\'s+landing', //Ecaped apostrophe ... |
Search contains
The Search contains (:
) query allows you to search a selected field in each of your users for a set of text that contains a certain value.
Let’s say we want to query all the users with ‘Manager’ or ‘Managing’ in their job title.
1 2 3 |
... query:'orgTitle:manag', ... |
This might result in a list containing titles like this:
- Managing Director
- Feed Manager
- Manager
- Management Relations
Note that the search text can appear anywhere in the user’s field value.
Starts with
We can modify the text contains operator by appending some text with the wildcard symbol (*
), :{PREFIX}*
. Let’s say we want to search to all the names of our users that start with ‘La’:
1 2 3 |
... query:'givenName:la*', ... |
This may result in a list of users like:
- Lauren
- Lance
- Larry
…but not, Blake, because the ‘la’ contained in the name is not at the start.
Boolean
Boolean values (true or false) can be found in the following fields:
isAdmin
isDelegatedAdmin
isSuspended
isEnrolledIn2Sv
isEnforcedIn2Sv
- or any custom schema that your admin has assigned a boolean value.
We can only apply the equal to (=
) operator to boolean fields. A boolean query would look like this:
1 2 3 |
... query:'isAdmin=true', ... |
Number
There are no number values in the standard user fields. However, your administrator may have created a custom user field with a value.
Equals
You can search for the exact number in a field by user with the equals (=
) operator:
1 2 3 4 5 6 7 8 9 10 11 |
... query:'customSchema.customField=10', ... or ... query:'customSchema.customField=132.52', ... or ... query:'customSchema.customField=10239475', ... |
As you can see, you can add fractions of numbers but must use a decimal (.
) indicator (sorry half of Europe). Also, make sure you leave out the thousand separators. For example, 1,234 is bad, but 1234 is good.
greater and less than (or equal to) number
You can use standard greater than (>) and less than (<) symbols and include optional (=) to search for a range above (and or equal to) or below a value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
... query:'customSchema.customField<100', // Less than 100 ... OR ... query:'customSchema.customField>100', // Greater than 100 ... OR ... query:'customSchema.customField<=100', // Less than or equal to 100 ... OR ... query:'customSchema.customField>=100', // Greater than or equal to 100 ... |
Between two numbers
You can also search for numbers between two values by using the min-max operator, :[{MIN},{MAX}]
.
1 2 3 |
... query:'customSchema.customField:[10,70]', // Between 10 and 70 ... |
Date
Dates use the same greater than, less than, equal to and between operators as numbers.
When running a search by date you should write your dates as follows:
Year-Month-day
YYYY-MM-DD
For example,
2020-07-11
greater and less than (or equal to) Date
You can use standard greater than (>) and less than (<) symbols and include optional (=) to search for a range above (and or equal to) or below a date.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
... query:'customSchema.customFiled=2021-07-11', //Equal to 11 Aug 2021 OR ... query:'customSchema.customField<2021-07-11', // Less than 11 Aug 2021 ... OR ... query:'customSchema.customField>2021-07-11', // Greater than 11 Aug 2021 ... OR ... query:'customSchema.customField<=2021-07-11', // Less than or equal to 11 Aug 2021 ... OR ... query:'customSchema.customField>=2021-07-11', // Greater than or equal to 11 Aug 2021 ... |
Between two Dates
You can also search for numbers between two values by using the min-max operator, :[{MIN},{MAX}]
.
1 2 3 |
... query:'customSchema.customField:[2021-01-01,2021-07-11]', // Between 01 January 2021 and 11 August 2021 ... |
Query Limitations
Query search field items are pretty extensive, but there might be one or two that cannot be searched in this way. For example, you can’t search for the account creation date using this approach. You may have to first grab all the users and run a query with Google Apps Script.
Query also does not support OR conditional queries. So in this instance, you would be better off collecting all the user data and running your own iterative search in Google Apps Script and not from the Admin SDK.
Multiple Conditions (AND)
You can add multiple search fields to a query by separating them with a space.
Let’s say we want to search for all user accounts that are suspended from the location Tragos Landing.
1 2 3 |
... query:'isSuspended=true addressLocality=tragosLanding', ... |
Or, how about we query all users whose surname start with ‘Mu’ who have completed on-site safety training (Custom field:date) after Sep 19 2019.
1 2 3 |
... query:'familyName:mu* training.siteSafety>2019-09-19', ... |
Getting details on one user
While you could definitely use the query approach to get specific information from a single user, the Admin Directory SDK has a specific method for getting data from a single user.
You can use the ‘get’ method on the Users resource to access an individual user by the user’s primary or alias email addresses or their primary user ID. Ironically, you would probably use the ‘get’ or ‘list’ method to get the user’s ID. So we will just stick with emails in this example.
Here is what our Google Apps Script Admin Directory call would look like:
1 2 3 4 5 6 7 8 9 10 11 12 |
function getSpecificUser(user){ const userDetails = AdminDirectory.Users.get( user ) return userDetails } //This is just a sample function call for demo purposes function runsies(){ console.log(getSpecificUser('yagiTgoat@patme.now')) } |
Pretty simple.
The ‘get’ method also takes an optional second query object that we can add some familiar parameters to:
- customFieldMask
- projection
- viewType
Let’s get a little wild here and use all three in one go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function getSpecificUser(user){ const userDetails = AdminDirectory.Users.get( user, { viewType:'domain_public', projection:'custom', customFieldMask:'releaseparty,dancers' } ) return userDetails } //Just a demo access function function runsies(){ console.log(getSpecificUser('sdonald@reitsma.com')) } |
Here we are getting the user’s details that are available to the public. If the project’s custom schema is public then we should be able to see the data for the ‘releaseparty’ and ‘dancers’ custom schemas.
Who says we never have any fun.
Conclusion
The goal of this article was to provide a reference tool for Google Apps Script users who are accessing users with the Admin Directory SDK.
To be honest, this post was going to be a short one, but then I realised the benefit of having a full breakdown of how to use the list() method. While the Google Docs on the Admin SDK are detailed, they appear to be more focused on other runtimes and external access within Python, Java or node.js.
Hopefully, this tutorial will clear up some of the confusion for the Google Apps Script user and can be used as a reference tool in conjunction with the approved Google Docs.
Let me know if I missed anything out that is important or if something has changed in a recent update and I will try and keep this page as relevant as possible.
Need help with Google Workspace development?
Go something to solve bigger than Chat GPT?
I can help you with all of your Google Workspace development needs, from custom app development to integrations and security. I have a proven track record of success in helping businesses of all sizes get the most out of Google Workspace.
Schedule a free consultation today to discuss your needs and get started or learn more about our services here.
Hi! What an amazing writeup, it helped me so much! I’m stuck with one thing: I’m trying to filter out users with a specific custom attribute, that I created from the Google Admin interface. I get the response: “GoogleJsonResponseException: API call to directory.users.list failed with error: Invalid Input: query”, which means something is wrong in how i express my query.
This is what I wrote:
const list = AdminDirectory.Users.list({
query:’utility.isNonHuman=false’,
domain:’mydomain.org’,
maxResults: 500,
orderBy:’givenName’,
sortOrder:’descending’,
}).users;
When I created it, I entered this:
– category: “utility”
– attribute: “isNonHuman”
– type: Yes or No
– Visibility: to user and admin
– Single value.
So I assume it is a boolean value, hence the “false”. I also tried “no”, “No”, “False”, “0”… Perhaps it leaves in the system in a different name than utility.isNonHuman but I don’t see anywhere how to find out.
Could you perhaps tip me in the right direction?
Thank you again !
Be well,
Alexandre.
Hi Alexandre,
I think maybe the “visibility” might be the issue here. You may also need to set ‘projection:full’, though I think it should run without it.
Also you can run a check of how the “custom field” will be displayed by using the DISPLAY ALL (FULL) CUSTOM FIELDS.
Let me know how you go with this. If you are still stuck we can look at some more options.
~Yagi
Hey,
your effort has not been in vain. 🙂
I was looking for the information on default value for the list call.
Specifically, for the limit on the max possible users per page.
Opted for 150 at the end instead of 500. I’d be looking
for a long time for it if you didn’t lay it all out so pedantically.
Although complete I find Google documentation poorly organized.
Thank you!
Hi Dariodaic,
That is really encouraging to hear. Thanks!
Yes, I am sure that Google devs need to meet certain documentation guidelines, which can tie their hands sometimes.
Cheers,
Yagi
Hi,
Well, this article is very informative.
I am getting error when trying to fetch the users list “Admin SDK API has not been used in project 34********7 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/admin.googleapis.com/overview?project=34********7 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry”
Please share your valuable feedback.
Hi classicm1238,
No 100% sure what is going on here. The only thing I can think of without looking at your code is that to check if your Google Workspace super-admin has enabled permission to use the AdminSDK and that your account is an admin account.
~Yagi
Hi,
I am trying to get my own profile contact information which is in the contacts or the information that my Google admin added on my account information then it will write to the sheet. Problem is it does not show the phone number
I used this script:
function onOpen() {
var optionalArgs = {
customer: ‘my_customer’,
maxResults: 500,
orderBy: ’email’
};
var response = AdminDirectory.Users.list(optionalArgs);
var output = [];
var users = response.users;
var activeEmail = Session.getActiveUser().getEmail();
var home = “”;
if (users && users.length > 0) {
Logger.log(‘Users:’);
for (i = 0; i < users.length; i++) {
var user = users[i];
if(user.primaryEmail == activeEmail){
//Logger.log(users[i]);
Logger.log(‘%s (%s %s %s)’, user.primaryEmail, user.name.fullName, user.recoveryPhone, user.addresses);
//home = String.toString(user.addresses);
// home = home.substring(home.indexOf(“formatted=”),home.indexOf(“type”));
var activeSheet = SpreadsheetApp.getActiveSheet();
activeSheet.getRange(1,2).setValue(user.name.fullName);
activeSheet.getRange(2,2).setValue(user.name.givenName);
activeSheet.getRange(3,2).setValue(user.name.familyName);
activeSheet.getRange(4,2).setValue(user.primaryEmail);
activeSheet.getRange(5,2).setValue(user.recoveryPhone);
activeSheet.getRange(6,2).setValue(user.addresses);
} else {
Logger.log(‘No users found.’);
}
}
Hi Jeffy,
It’s probably better to search for your personal details by using the ‘get’ method:
AdminDirectory.Users.get("jeffy@onlylegends.com")
.Phone numbers can be found in the ‘phone’ property:
AdminDirectory.Users.get("jeffy@onlylegends.com").phones
.Which would result in a list of phone numbers:
If you are having trouble displaying your recovery phone number check if you have a recover number by reviewing your security setting to double-check you actually have a number assigned. Your recoveryPhone will not appear otherwise in the get request and will trigger an error here
activeSheet.getRange(5,2).setValue(user.recoveryPhone);
You could change this to something like:
activeSheet.getRange(5,2).setValue((user.recoveryPhone)? user.recoveryPhone: “”);
Hope this helps.
~Yagi
can you help me how to do this and i required to read all user information within organization and store it into big querry
This is great thank you! I had a quick question I was wondering you knew the answer to:
Any idea how I can write this data to a connected Sheet? Ideally I’d like to have firstname in comlumn 1, lastname in column 2, etc.
Appreciate any help with this, and thanks again for the tutorial.
Hi Luke,
Yes you can create a Google Sheet bound script and draw the user data using the steps above.
You will need to generate an array for your names. Something like this quasi code:
To add data to the current sheet you can use:
References:
https://developers.google.com/apps-script/reference/spreadsheet/sheet#getrangerow,-column,-numrows,-numcolumns
https://developers.google.com/apps-script/reference/spreadsheet/range#setvaluesvalues
Google Apps Script: Get the last row of a data range when other columns have content like hidden formulas and check boxes [updated Mar 2022]
Google Apps Script Course – Intro: 2D Array Data Transformation for Google Sheets