Mathias Osterkamp

Specialist – focus development Microsoft technology stack

Site termgroup add contributor or manager

Adds a manager or contributor to a SharePoint site collection termgroup

Add site collection term group Managers and Contributors using JSOM

On client side for CSOM we have already a solution for adding managers or contributors for term groups. You can have a look here for the original article.

ClientContext clientContext = GetClientContext();

var taxonomySession = TaxonomySession.GetTaxonomySession(clientContext);

var termStore = taxonomySession.GetDefaultSiteCollectionTermStore();

var myTermGroup = termStore.Groups.GetByName("My Custom Terms Group");

//Add Group Managers
myTermGroup.AddGroupManager("i:0#.f|membership|...@tenant.onmicrosoft.com");

//Add Group Contributors
myTermGroup.AddContributor("i:0#.f|membership|...@tenant.onmicrosoft.com");
myTermGroup.AddContributor("i:0#.f|membership|...@tenant.onmicrosoft.com");

clientContext.Load(myTermGroup, group => group.GroupManagerPrincipalNames, group => group.ContributorPrincipalNames);
clientContext.ExecuteQuery();

Console.WriteLine("Group Managers: ");
foreach (var manager in myTermGroup.GroupManagerPrincipalNames)
{
    Console.WriteLine(manager);
}

Console.WriteLine("Group Contributors: ");
foreach (var contributors in myTermGroup.ContributorPrincipalNames)
{
    Console.WriteLine(contributors);
}

I extracted the methods for the special case of site collection term group managers and contributors, you can find here a working version with pure typescript. Your class should also have a property for spHttpClient and absoluteWebUrl from pnp js.

/**
   * Add manager (creates site term group if not exists, allowed to run multiple times for same user)
   * @param principalName name of user contoso\login or Everyone
   */
  public async addSiteGroupManager(principalName: string): Promise<void> {
    const requestBody = `<?xml version="1.0" encoding="UTF-8"?>
       <Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="Javascript Library">
          <Actions>
             <ObjectPath Id="1" ObjectPathId="0" />
             <ObjectIdentityQuery Id="2" ObjectPathId="0" />
             <ObjectPath Id="4" ObjectPathId="3" />
             <ObjectIdentityQuery Id="5" ObjectPathId="3" />
             <ObjectPath Id="7" ObjectPathId="6" />
             <ObjectPath Id="9" ObjectPathId="8" />
             <ObjectPath Id="11" ObjectPathId="10" />
             <ObjectIdentityQuery Id="12" ObjectPathId="10" />
             <Method Name="AddGroupManager" Id="13" ObjectPathId="10">
                <Parameters>
                   <Parameter Type="String">${principalName}</Parameter>
                </Parameters>
             </Method>
          </Actions>
          <ObjectPaths>
             <StaticMethod Id="0" Name="GetTaxonomySession" TypeId="{981cbc68-9edc-4f8d-872f-71146fcbb84f}" />
             <Method Id="3" ParentId="0" Name="GetDefaultSiteCollectionTermStore" />
             <StaticProperty Id="6" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
             <Property Id="8" ParentId="6" Name="Site" />
             <Method Id="10" ParentId="3" Name="GetSiteCollectionGroup">
                <Parameters>
                   <Parameter ObjectPathId="8" />
                   <Parameter Type="Boolean">true</Parameter>
                </Parameters>
             </Method>
          </ObjectPaths>
       </Request>`;

    return await this.postcsom(requestBody).then((result) => {
      if (result[0].ErrorInfo !== null) {
        throw new Error(JSON.stringify(result[0].ErrorInfo));
      }
      return;
    });
  }

  /**
   * Add contributor (creates site term group if not exists, allowed to run multiple times for same user)
   * @param principalName name of user contoso\login or Everyone
   */
  public async addSiteGroupContributor(principalName: string): Promise<void> {
    const requestBody = `<?xml version="1.0" encoding="UTF-8"?>
      <Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="Javascript Library">
         <Actions>
            <ObjectPath Id="1" ObjectPathId="0" />
            <ObjectIdentityQuery Id="2" ObjectPathId="0" />
            <ObjectPath Id="4" ObjectPathId="3" />
            <ObjectIdentityQuery Id="5" ObjectPathId="3" />
            <ObjectPath Id="7" ObjectPathId="6" />
            <ObjectPath Id="9" ObjectPathId="8" />
            <ObjectPath Id="11" ObjectPathId="10" />
            <ObjectIdentityQuery Id="12" ObjectPathId="10" />
            <Method Name="AddContributor" Id="13" ObjectPathId="10">
               <Parameters>
                  <Parameter Type="String">${principalName}</Parameter>
               </Parameters>
            </Method>
         </Actions>
         <ObjectPaths>
            <StaticMethod Id="0" Name="GetTaxonomySession" TypeId="{981cbc68-9edc-4f8d-872f-71146fcbb84f}" />
            <Method Id="3" ParentId="0" Name="GetDefaultSiteCollectionTermStore" />
            <StaticProperty Id="6" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
            <Property Id="8" ParentId="6" Name="Site" />
            <Method Id="10" ParentId="3" Name="GetSiteCollectionGroup">
               <Parameters>
                  <Parameter ObjectPathId="8" />
                  <Parameter Type="Boolean">true</Parameter>
               </Parameters>
            </Method>
         </ObjectPaths>
      </Request>`;
    return await this.postcsom(requestBody).then((result) => {
      if (result[0].ErrorInfo !== null) {
        throw new Error(JSON.stringify(result[0].ErrorInfo));
      }
      return;
    });
  }


/**
   * sends a request to client svc
   * @param data request data
   */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  private async postcsom(data: string): Promise<any> {
    const clientServiceUrl = this.absoluteWebUrl + '/_vti_bin/client.svc/ProcessQuery';

    const formDigest = await this.getFormDigest();

    const requestHeaders: Headers = new Headers();
    requestHeaders.append('Accept', 'application/json');
    requestHeaders.append('Content-Type', 'text/xml');
    requestHeaders.append('X-RequestDigest', formDigest);

    const httpPostOptions: IHttpClientOptions = {
      headers: requestHeaders,
      body: data
    };

    const serviceResponse: IResponse = await this.spHttpClient.post(clientServiceUrl,SPHttpClient.configurations.v1, httpPostOptions);
    const serviceJSONResponse = await serviceResponse.json();
    if (serviceResponse.ok) {
      return serviceJSONResponse;
    }
    return null;
  }

  /**
   * digest is needed for post requests
   */
  private async getFormDigest(): Promise<string> {
    if (this.cachedDigest && !this.isDigestDateInvalid()) return this.cachedDigest;
    this.cachedDigestDate = new Date();
    const contextInfoUrl = this.absoluteWebUrl + '/_api/contextinfo';

    const requestHeaders: Headers = new Headers();
    requestHeaders.append('Accept', 'application/json');
    requestHeaders.append('Content-Type', 'text/xml');

    const httpPostOptions: IHttpClientOptions = {
      headers: requestHeaders
    };
    const contextInfoResponse: IResponse = await this.spHttpClient.post(contextInfoUrl, httpPostOptions);
    const contextInfoJsonResponse = await contextInfoResponse.json();
    const formDigest: string = contextInfoJsonResponse.FormDigestValue;
    this.cachedDigest = formDigest;
    return formDigest;
  }
  
  private isDigestDateInvalid(): boolean {
    const today = new Date().getTime();
    const cachedtime = this.cachedDigestDate.getTime();
    const difference = Math.abs(cachedtime - today);
    const seconds = (difference / 1000) % 60;
    return seconds > 60;
  }

The easy usage is:

  addSiteGroupContributor('Everyone')