Get details about team members in your Google Workspace organization

  • This Google Workspace add-on displays contact information for collaborators within Gmail, Drive, and Calendar using the Admin SDK Directory API.

  • The add-on automatically identifies collaborators from emails, files, and events, presenting their details in an easy-to-use card format.

  • Users can search for colleagues by name or email and retrieve details such as phone numbers, departments, and email addresses.

  • It leverages Apps Script services like Card service for UI, Cache service for optimized data retrieval, and the Admin SDK Directory service for user data.

  • Setting up this add-on requires enabling the Admin SDK API, configuring OAuth consent, and deploying the Apps Script project.

Coding level: Intermediate
Duration: 45 minutes
Project type: Google Workspace add-on

Objectives

  • Understand what the solution does.
  • Understand what the Apps Script services do within the solution.
  • Set up the environment.
  • Set up the script.
  • Run the script.

About this solution

Show information, such as email, phone number, and department, about people you collaborate with in your organization while you're working in Google Workspace. You can view this information when responding to Gmail messages, editing a Google Drive file, or viewing Google Calendar events.

Screenshot of the Teams List Google Workspace add-on

How it works

The script gets email addresses from the active message, file, or event. Depending on the context, this can include Gmail message recipients, Drive file editors, and Calendar event attendees. The script only shows information for email addresses in your organization.

Apps Script services

This solution uses the following services:

  • Admin SDK Directory advanced service–Searches for people using the Directory API.
  • Base service–Uses the Session class to help filter email addresses and not show the current user in search results.
  • Cache service–Searches the cache first when looking up a single person from the Directory API.
  • Calendar service–If the context is a Calendar event, gets email addresses from the active event.
  • Card service–Creates the user interface of the add-on.
  • Drive service–If the context is a Drive file, gets email addresses of the collaborators if the user has permission to view them in the active file.
  • Gmail service–If the context is a Gmail message, gets email addresses from the To, Cc, and From fields in the active Gmail message.

Prerequisites

Set up your environment

Open your Cloud project in the Google Cloud console

If it's not open already, open the Cloud project that you intend to use for this sample:

  1. In the Google Cloud console, go to the Select a project page.

    Select a Cloud project

  2. Select the Google Cloud project you want to use. Or, click Create project and follow the on-screen instructions. If you create a Google Cloud project, you might need to turn on billing for the project.

Turn on the Admin SDK API

This quickstart uses the Admin SDK API Directory advanced service, which accesses the Admin SDK API.

Before using Google APIs, you need to turn them on in a Google Cloud project. You can turn on one or more APIs in a single Google Cloud project.

Google Workspace add-ons require a consent screen configuration. Configuring your add-on's OAuth consent screen defines what Google displays to users.

  1. In the Google Cloud console, go to Menu > Google Auth platform > Branding.

    Go to Branding

  2. If you have already configured the Google Auth platform, you can configure the following OAuth Consent Screen settings in Branding, Audience, and Data Access. If you see a message that says Google Auth platform not configured yet, click Get Started:
    1. Under App Information, in App name, enter a name for the app.
    2. In User support email, choose a support email address where users can contact you if they have questions about their consent.
    3. Click Next.
    4. Under Audience, select Internal.
    5. Click Next.
    6. Under Contact Information, enter an Email address where you can be notified about any changes to your project.
    7. Click Next.
    8. Under Finish, review the Google API Services User Data Policy and if you agree, select I agree to the Google API Services: User Data Policy.
    9. Click Continue.
    10. Click Create.
  3. For now, you can skip adding scopes. In the future, when you create an app for use outside of your Google Workspace organization, you must change the User type to External. Then add the authorization scopes that your app requires. To learn more, see the full Configure OAuth consent guide.

Set up the script

Create the Apps Script project

  1. Click the following button to open the Teams list Apps Script project.
    Open the project

  2. Click Overview .

  3. On the overview page, click Make a copy The icon for making a copy.

Copy the Cloud project number

  1. In the Google Cloud console, go to Menu > IAM & Admin > Settings.

    Go to IAM & Admin Settings

  2. In the Project number field, copy the value.

Set the Apps Script project's Cloud project

  1. In your copied Apps Script project, click Project Settings The icon for project settings.
  2. Under Google Cloud Platform (GCP) Project, click Change project.
  3. In GCP project number, paste the Google Cloud project number.
  4. Click Set project.

Install a test deployment

  1. In your copied Apps Script project, click Editor .
  2. Open the Code.gs file and click Run. When prompted, authorize the script.
  3. Click Deploy > Test deployments.
  4. Click Install > Done.

Run the script

  1. Open a Gmail message, Calendar event, or Drive file.
  2. On the right sidebar, open the Team List add-on .
  3. If prompted, authorize the add-on.
  4. The add-on shows information about team members or indicates that the message, event, or file has no team members.
  5. To find team members, click Search for people and enter a name or email address. Click Search.

Review the code

To review the Apps Script code for this solution, click View source code below:

View source code

Code.gs

//Copyright2022GoogleInc.AllRightsReserved.
//
//LicensedundertheApacheLicense,Version2.0(the"License");
//youmaynotusethisfileexceptincompliancewiththeLicense.
//YoumayobtainacopyoftheLicenseat
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unlessrequiredbyapplicablelaworagreedtoinwriting,software
//distributedundertheLicenseisdistributedonan"AS IS"BASIS,
//WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
//SeetheLicenseforthespecificlanguagegoverningpermissionsand
//limitationsundertheLicense.
//SampleGoogleWorkspaceadd-onthatdisplaysprofileinformationaboutpeople
//theuseriscollaboratingwith.Collaboratorsarebasedonthecontext--
//recipientsofagmailmessage,DrivefileACLs,oreventattendees.
//
//ProfileinformationisfromtheDirectoryAPIintheAdminSDK.Asaresult,
//theadd-ononlyshowsinformationforemailaddressesinthesamedomain
//asasthecurrentuser.Differentstrategiescanbeusedforotherusecases,
//suchasintegrationwithaCRMwherethefocusmaybeonexternalemail
//addresses/customers.
//Seehttps://github.com/contributorpw/lodashgs
var_=LodashGS.load();
/**
*Rendersthehomepagefortheadd-on.Usedinallhostappswhen
*nocontextselected.
*
*@param{Object}event-currentadd-onevent
*@return{Card[]}Card(s)todisplay
*/
functiononHomePage(event){
varcard=buildSearchCard_();
return[card];
}
/**
*RendersthecontextualinterfaceforaGmailmessage.
*
*@param{Object}event-currentadd-onevent
*@return{Card[]}Card(s)todisplay
*/
functiononGmailMessageSelected(event){
varemails=extractEmailsFromMessage_(event);
varpeople=fetchPeople_(emails);
if(people.length==0){
varcard=buildSearchCard_("No team members found for current message.");
return[card];
}
varcard=buildTeamListCard_(people)
return[card];
}
/**
*Rendersthecontextualinterfaceforacalendarevent.
*
*@param{Object}event-currentadd-onevent
*@return{Card[]}Card(s)todisplay
*/
functiononCalendarEventOpen(event){
varemails=extractEmailsFromCalendarEvent_(event);
varpeople=fetchPeople_(emails);
if(people.length==0){
varcard=buildSearchCard_("No team members found for current event.");
return[card];
}
varcard=buildTeamListCard_(people)
return[card];
}
/**
*RendersthecontextualinterfaceforaselectedDrivefile.
*
*@param{Object}event-currentadd-onevent
*@return{Card[]}Card(s)todisplay
*/
functiononDriveItemsSelected(event){
//Fordemo,onlyallowsingleselectonfiles.
if(event.drive.selectedItems.length!=1){
varmessage="To view team members collaborating on a file, select one file only.";
varcard=buildSearchCard_(message);
return[card];
}
varselectedItem=event.drive.selectedItems[0];
if(!selectedItem.addonHasFileScopePermission){
//NeedfileaccesstoreadACL,askusertoauthorize.
varauthorizeFilesAction=CardService.newAction()
.setFunctionName("onAuthorizeDriveFiles")
.setLoadIndicator(CardService.LoadIndicator.SPINNER)
.setParameters({id:selectedItem.id});
varauthorizationMessage=CardService.newTextParagraph()
.setText("To view the people on your team the file is shared with, click *Authorize* to grant access.");
varauthorizeButton=CardService.newTextButton()
.setText("Authorize")
.setOnClickAction(authorizeFilesAction);
varcard=CardService.newCardBuilder()
.addSection(CardService.newCardSection()
.addWidget(authorizationMessage)
.addWidget(authorizeButton))
.build();
return[card];
}
//Haveaccess,extractACLstofindco-workers
varemails=extractEmailsFromDrivePermissions_(event);
varpeople=fetchPeople_(emails);
if(people.length==0){
varcard=buildSearchCard_("No team members found for current file.");
return[card];
}
varcard=buildTeamListCard_(people)
return[card];
}
/**
*Handlestheclickforrequestingdrivefileaccess.
*
*@param{Object}event-currentadd-onevent
*@return{ActionResponse}Requesttoauthorizeaccesstoadriveitem
*/
functiononAuthorizeDriveFiles(event){
varid=event.parameters.id;
returnCardService.newDriveItemsSelectedActionResponseBuilder()
.requestFileScope(id)
.build();
}
/**
*Handlestheusersearchrequest.
*
*@param{Object}event-currentadd-onevent
*@return{Card[]}Card(s)todisplay
*/
functiononSearch(event){
if(!event.formInputs||!event.formInputs.query){
varnotification=CardService.newNotification()
.setText("Enter a query before searching.");
returnCardService.newActionResponseBuilder()
.setNotification(notification)
.build();
}
varquery=event.formInputs.query[0];
varpeople=queryPeople_(query);
if(!people||people.length==0){
varnotification=CardService.newNotification().setText("No people found.");
returnCardService.newActionResponseBuilder()
.setNotification(notification)
.build();
}
varcard=buildTeamListCard_(people);
varnavigation=CardService.newNavigation().pushCard(card);
returnCardService.newActionResponseBuilder()
.setNavigation(navigation)
.build();
}
/**
*Handlesthedrilldowntoviewdetailedinformationaboutaperson.
*
*@param{Object}event-currentadd-onevent
*@return{Card[]}Card(s)todisplay
*/
functiononShowPersonDetails(event){
varperson=fetchPerson_(event.parameters.email);
varcard=buildPersonDetailsCard_(person);
return[card]
}
/**
*Buildsacardfordisplayingdetailedinformationaboutateammember.Currentlyonlyshows
*asmallsubsetofavailableinformationfordemopurposes.
*
*@param{Object}person-UserobjectfromtheDirectoryAPI
*@return{Card}Cardtodisplay
*/
functionbuildPersonDetailsCard_(person){
varphotoUrl=person.thumbnailPhotoUrl?
person.thumbnailPhotoUrl:"https://ssl.gstatic.com/s2/profiles/images/silhouette200.png";
varcardHeader=CardService.newCardHeader()
.setImageUrl(photoUrl)
.setImageStyle(CardService.ImageStyle.CIRCLE)
.setTitle(person.name.fullName)
if(person.organizations && person.organizations.length){
cardHeader.setSubtitle(person.organizations[0].title);
}
varsection=CardService.newCardSection();
if(person.emails){
person.emails.forEach(function(email){
section.addWidget(CardService.newKeyValue()
.setIcon(CardService.Icon.EMAIL)
.setContent(email.address));
});
}
if(person.phones){
person.phones.forEach(function(phone){
section.addWidget(CardService.newKeyValue()
.setIcon(CardService.Icon.PHONE)
.setContent(phone.value));
});
}
if(person.organizations){
person.organizations.forEach(function(org){
section.addWidget(CardService.newKeyValue()
.setIcon(CardService.Icon.MEMBERSHIP)
.setContent(org.department));
});
}
if(person.locations){
person.locations.forEach(function(location){
varformattedLocation=
Utilities.formatString("%s<br>%s",location.area,location.buildingId);
section.addWidget(CardService.newKeyValue()
.setIcon(CardService.Icon.MAP_PIN)
.setContent(formattedLocation));
});
}
returnCardService.newCardBuilder()
.setHeader(cardHeader)
.addSection(section)
.build();
}
/**
*Buildsacardfordisplayingalistofpeople
*
*@param{Object[]}people-ArrayofusersfromtheDirectoryAPI
*@return{Card}Cardtodisplay
*/
functionbuildTeamListCard_(people){
varresultsSection=CardService.newCardSection();
people.forEach(function(person){
varphotoUrl=person.thumbnailPhotoUrl?
person.thumbnailPhotoUrl:"https://ssl.gstatic.com/s2/profiles/images/silhouette200.png";
vartitle=person.organizations?person.organizations[0].title:null;
varclickAction=CardService.newAction()
.setFunctionName("onShowPersonDetails")
.setLoadIndicator(CardService.LoadIndicator.SPINNER)
.setParameters({email:person.primaryEmail});
varpersonSummaryWidget=CardService.newKeyValue()
.setContent(person.name.fullName)
.setIconUrl(photoUrl)
.setOnClickAction(clickAction);
if(person.organizations && person.organizations.length){
personSummaryWidget.setBottomLabel(person.organizations[0].title);
}
resultsSection.addWidget(personSummaryWidget);
});
returnCardService.newCardBuilder()
.addSection(resultsSection)
.build();
}
/**
*Buildsthesearchinterfaceforlookinguppeople.
*
*@param{string}opt_error-Optionalmessagetoinclude(typicallywhen
*contextualsearchfailed.)
*@return{Card}Cardtodisplay
*/
functionbuildSearchCard_(opt_error){
varbanner=CardService.newImage()
.setImageUrl('https://storage.googleapis.com/gweb-cloudblog-publish/original_images/Workforce_segmentation_1.png');
varsearchField=CardService.newTextInput()
.setFieldName("query")
.setHint("Name or email address")
.setTitle("Search for people");
varonSubmitAction=CardService.newAction()
.setFunctionName("onSearch")
.setLoadIndicator(CardService.LoadIndicator.SPINNER);
varsubmitButton=CardService.newTextButton()
.setText("Search")
.setOnClickAction(onSubmitAction);
varsection=CardService.newCardSection()
.addWidget(banner)
.addWidget(searchField)
.addWidget(submitButton);
if(opt_error){
varmessage=CardService.newTextParagraph()
.setText("Note: "+opt_error);
section.addWidget(message);
}
returnCardService.newCardBuilder()
.addSection(section)
.build();
}
/**
*ExtractsemailaddressesfromtheselectedGmailmessage.Grabsallemails
*fromtheto/cc/fromheaders.
*
*@param{Object}event-currentadd-onevent
*@return{string[]}Arrayofemailaddresses.
*/
functionextractEmailsFromMessage_(event){
//Fetchcurrentlyselectedmessage
varaccessToken=event.messageMetadata.accessToken;
varmessageId=event.messageMetadata.messageId;
GmailApp.setCurrentMessageAccessToken(accessToken);
varmessage=GmailApp.getMessageById(messageId);
if(!message){
return[];
}
//Parse/emitanyemailaddressesintheto/cc/fromheaders
varsplitEmailsRegexp=/\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}\b/gi;
varemails=_.union(
message.getTo().match(splitEmailsRegexp),
message.getCc().match(splitEmailsRegexp),
message.getFrom().match(splitEmailsRegexp)
);
//Removeany+suffixesintheusernameportiontogetthecanonicalemail
varnormalizeRegexp=/(.*)\+.*@(.*)/;
emails=emails.map(function(email){
returnemail.replace(normalizeRegexp,"1ドル@2ドル");
});
returnfilterAndSortEmails_(emails);
}
/**
*ExtractsemailaddressesfromtheselectedDriveitem.Grabsallemails
*fromthefileACLs(ifuserhaspermissiontoviewthem.)
*
*@param{Object}event-currentadd-onevent
*@return{string[]}Arrayofemailaddresses.
*/
functionextractEmailsFromDrivePermissions_(event){
//Makesurejust1fileselected.
if(event.drive.selectedItems.length!=1){
return[];
}
varitemId=event.drive.selectedItems[0].id;
varemails=[];
varitem=Drive.Files.get(itemId,{fields:"owners, sharingUser"});
if(item.sharingUser){
emails.push(item.sharingUser.emailAddress);
}
if(item.owners){
item.owners.forEach(function(owner){
emails.push(owner.emailAddress);
});
}
try{
varpermissions=Drive.Permissions.list(itemId,{fields:'*'});
if(permissions){
permissions.permissions.forEach(function(permission){
if(permission.type!='domain'){
emails.push(permission.emailAddress);
}
});
}
}catch(e){
//Ignoreinabilitytofetchpermissions,maynothaveaccess
console.warn(e);
}
returnfilterAndSortEmails_(emails)
}
/**
*Extractsemailaddressesfromtheselectedcalendarevent(attendees.)
*
*@param{Object}event-currentadd-onevent
*@return{string[]}Arrayofemailaddresses.
*/
functionextractEmailsFromCalendarEvent_(event){
if(!event.calendar||!event.calendar.attendees){
return[];
}
varemails=event.calendar.attendees.map(function(attendee){
returnattendee.email;
});
returnfilterAndSortEmails_(emails);
}
/**
*Filteremailaddressestoincludeonlythoseinthesame
*domainandexcludingthecurrentuser.
*
*@param{string[]}emails-Arrayofemailaddresses
*@return{string[]}
*/
functionfilterAndSortEmails_(emails){
if(!emails){
return[];
}
varuserEmail=Session.getActiveUser().getEmail();
vardomain=userEmail.slice(userEmail.indexOf('@')+1);
emails=emails.filter(function(email){
return_.endsWith(email,domain) && email!=userEmail;
});
emails=_.uniq(emails);
returnemails.sort();
}
/**
*LookuponeormorepeoplefromtheDirectoryAPI.Mayomititems
*ifemailaddressesaren't valid domain users.
*
*@param{string[]}emails-Arrayofemailaddressestofetch
*@return{Object[]}Arrayofuserobjects.
*/
functionfetchPeople_(emails){
if(!emails||emails.length==0){
return[];
}
returnemails.map(fetchPerson_).filter(function(item){
returnitem!=null && item.primaryEmail;
});
}
/**
*LookupasinglepersonfromtheDirectoryAPI.
*
*@param{string}email-Emailaddressestofetch
*@return{Object}Userobjectornullifnotavaliduser
*/
functionfetchPerson_(email){
if(!email){
returnnull;
}
//Checkcachefirst
varperson=CacheService.getUserCache().get(email);
if(person && person.primaryEmail){
returnJSON.parse(person);
}
try{
person=AdminDirectory.Users.get(
email,{projection:'full',viewType:'domain_public'});
CacheService.getUserCache().put(email,JSON.stringify(person));
returnperson;
}catch(e){
//Ignoreerror,maynotbevaliddomainuseranymore.
console.warn(e);
}
returnnull;
}
/**
*SearchforpeoplefromtheDirectoryAPIbynameoremailaddress.
*
*@param{string}query-Nameoremailaddresstosearchfor.
*@return{Object[]}Arrayofuserobjects.
*/
functionqueryPeople_(query){
try{
varoptions={
query:query,
maxResults:10,
customer:'my_customer',
projection:'full',
viewType:'domain_public'
};
varresults=AdminDirectory.Users.list(options);
varcacheValues=results.users.reduce(function(map,person){
map[person.primaryEmail]=JSON.stringify(person);
returnmap;
},{});
CacheService.getUserCache().putAll(cacheValues);
returnresults.users;
}catch(e){
//Ignoreerror
console.warn(e);
}
return[];
}

appsscript.json

{
"timeZone":"America/Denver",
"dependencies":{
"enabledAdvancedServices":[{
"userSymbol":"Drive",
"serviceId":"drive",
"version":"v3"
},{
"userSymbol":"AdminDirectory",
"serviceId":"admin",
"version":"directory_v1"
}],
"libraries":[{
"userSymbol":"LodashGS",
"libraryId":"1SQ0PlSMwndIuOAgtVJdjxsuXueECtY9OGejVDS37ckSVbMll73EXf2PW",
"version":"5"
}]
},
"exceptionLogging":"STACKDRIVER",
"oauthScopes":[
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/admin.directory.user.readonly",
"https://www.googleapis.com/auth/gmail.addons.execute",
"https://www.googleapis.com/auth/gmail.addons.current.message.metadata",
"https://www.googleapis.com/auth/calendar.addons.execute",
"https://www.googleapis.com/auth/calendar.addons.current.event.read",
"https://www.googleapis.com/auth/drive.addons.metadata.readonly",
"https://www.googleapis.com/auth/drive.file"
],
"urlFetchWhitelist":[],
"runtimeVersion":"V8",
"addOns":{
"common":{
"name":"Team List",
"logoUrl":"https://www.gstatic.com/images/icons/material/system/1x/people_black_24dp.png",
"layoutProperties":{
"primaryColor":"#4285f4",
"secondaryColor":"#ea4335"
},
"homepageTrigger":{
"runFunction":"onHomePage",
"enabled":true
},
"universalActions":[{
"label":"Feedback",
"openLink":"https://github.com/googleworkspace/add-ons-samples/issues"
}],
"openLinkUrlPrefixes":[
"https://github.com/googleworkspace/add-ons-samples/"
]
},
"gmail":{
"contextualTriggers":[{
"unconditional":{
},
"onTriggerFunction":"onGmailMessageSelected"
}]
},
"drive":{
"homepageTrigger":{
"runFunction":"onHomePage",
"enabled":true
},
"onItemsSelectedTrigger":{
"runFunction":"onDriveItemsSelected"
}
},
"calendar":{
"homepageTrigger":{
"runFunction":"onHomePage",
"enabled":true
},
"eventOpenTrigger":{
"runFunction":"onCalendarEventOpen"
},
"currentEventAccess":"READ"
}
}
}

Contributors

This sample is maintained by Google with the help of Google Developer Experts.

Next steps

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2025年10月13日 UTC.