Create a Subsite in SharePoint 2013 Using REST Calls

Today I was working on what seemed like a simple little task. I wanted to create a subsite using a REST call in SharePoint 2013. I’m not working in an “app” per se – this is for SharePoint 2013 on premises, and I want to do the subsite creation directly from a call in a plain old aspx page.

Unfortunately, there isn’t a clear example out there, at least not that I could find. It seems as though everyone has simply copied the example from this page on MSDN and quotes it verbatim. (Look for the “Creating a site with REST” example.) Unfortunately, I couldn’t get that example to work for me. Most of the examples are also focused on creating subsites in a workflow rather than directly.

I knew I’d need to have a fresh copy of the Form Digest for the request to work. There’s a great article from Wictor Wilén (@wictor) called SharePoint 2013: How to refresh the Request Digest value in JavaScript that explains several different ways to get this token. You must have the token in order for SharePoint to be willing to accept your requests to do a write to anything; it’s a part of the security features. In the page where I am working, we’re building a full application on top of SharePoint, but without using the out of the box SharePoint UI. Because of this, things like the hidden variable which usually holds the Form Digest token aren’t available in our pages. By making a quick call to the ContextInfo REST API, I can grab the Form Digest token pretty painlessly using the second method from Wictor’s post.

The breakthrough came due to some great suggestions from Matt Gibson (@Gibz) and Rob Windsor (@robwindsor) on Twitter. (You’ve got to love the #SPHelp hashtag!) Both Matt and Rob were sure this should work and gave me tips. The winning idea came from Rob. There’s a comment at the bottom of the MSDN page referenced above that shows slightly different JSON data in the REST call payload, and that did it. Bottom line, the documentation doesn’t seem to be adequate or correct in this case. At the very least, I couldn’t find it if it’s out there.

Here’s the code I ended up with. I’ve built it as a jQuery function for reusability.

// Create a new subsite
$.fn.Site.create = function(options) {

  var opt = $.extend({}, {
    siteUrl: null,
    siteName: null,
    siteDescription: "",
    siteTemplate: "sts",
    uniquePermissions: false
  }, $.fn.ProjectName.defaults, options);

  // Because we don't have the hidden __REQUESTDIGEST variable, we need to ask the server for the FormDigestValue
  var __REQUESTDIGEST;
  var rootUrl = location.protocol + "//" + location.host;

  var contextInfoPromise = $.ajax({
    url: rootUrl + "/_api/contextinfo",
    method: "POST",
    headers: {
      "Accept": "application/json; odata=verbose"
    },
    success: function(data) {
      __REQUESTDIGEST = data.d.GetContextWebInformation.FormDigestValue;
    },
    error: function(data, errorCode, errorMessage) {
      alert(errorMessage);
    }
  });

  // Once we have the form digest value, we can create the subsite
  $.when(contextInfoPromise).done(function() {
    $.ajax({
      url: rootUrl + "/_api/web/webinfos/add",
      type: "POST",
      headers: {
        "accept": "application/json;odata=verbose",
        "content-type": "application/json;odata=verbose",
        "X-RequestDigest": __REQUESTDIGEST
      },
      data: JSON.stringify({
        'parameters': {
          '__metadata': {
            'type': 'SP.WebInfoCreationInformation'
          },
          'Url': opt.siteUrl,
          'Title': opt.siteName,
          'Description': opt.siteDescription,
          'Language': 1033,
          'WebTemplate': opt.siteTemplate,
          'UseUniquePermissions': opt.uniquePermissions
        }
      })
    });
  });

}; // End $.fn.Site.create

With the function set up this way, I can call it something like this:

$().ProjectName.Site.create({
  siteUrl: "mysite",
  siteName: "My Site's Name",
  siteDescription: "This is the description that explains how to use this site.",
  siteTemplate: "sts",
  uniquePermissions: false
});

I like to publish stuff like this when I figure it out. We’re all in this together and we shouldn’t need to figure things out more than once.

Thanks Matt and Rob!

22 Comments

  1. The lack of developer documentation for many areas in 2013 is staggering. So many of the code samples are poorly documented at best (and incorrectly documented at worst), and countless others haven’t been touched since July 2012. I wonder if they would consider crowdsourcing MSDN documentation?

    Reply
    • Danny:

      I totally agree. Documentation should be a help, not a hindrance. I’m reaching out to the Product Group folks with this example to see how we can help.

      As @johnnliu just said in a tweet:

      @sympmarc MSDN errors is basically MS making developers lose productivity. We need something like spdevwiki. Hope @jthake can do something.

      M.

      Reply
    • German:

      It’s hard to say, but the error is telling you that at least one of the variables is undefined or null. You’ll probably need to do some debugging to figure out what’s happening.

      M.

      Reply
  2. Hi Marc. Because your post I finally could understand how work with sharepoint rest services. I have a question, I need to create a new site but with a custom template, not the “sts”. How can I do that?
    Thanks.

    Reply
      • Thanks Marc for the reply. I see the post but not show how work with Sharepoint Online and not show how can I see the right name for my custom tamplate… Do you have any other idea?
        Thanks a lot.

        Reply
        • After look a lot I finally find how create but in this process I don’t see how we can use the same parent navigation bar. I try to find some parameter but so far I found nothing, you know what would be? Thanks a lot.

          Reply
          • Douglas:

            I’m not exactly sure what the answer is, but if you append “/_api/web/” to the URL of the site, you can see the current settings. One of them may be the one you are looking for.

            M.

            Reply
            • I’m already done this and I found a UseShared parameter that I need change to subsite have same navegation bar but I stuck in the update code to change, can you see what is wrong?

              $.ajax({
              url: siteUrl + “/_api/Web/Navigation”,
              type: “POST”,
              data: JSON.stringify({ ‘UseShared’: true }),
              headers: {
              “X-HTTP-Method”: “MERGE”,
              “accept”: “application/json;odata=verbose”,
              “content-type”: “application/json;odata=verbose”,
              “X-RequestDigest”: $(“#__REQUESTDIGEST”).val(),
              “IF-MATCH”: “*”
              },
              success: onSuccess,
              error: onError
              });
              function onSuccess(data) {
              alert(“Ok”);
              }
              function onError(errorMessage) {
              alert(“Erro = ” + errorMessage);
              }

              Reply
  3. Hi Marc, GREAT POST! Thanks!
    But I’m facing a language problem… Hope you can help me.
    This is the situation: My SharePoint enviroment was installed in English, but then added the Spanish language pack and the sites are created in Spanish.
    Well, I have replicated this behaviour for one of my Spanish sites via workflow, granting full trust to the Workflow App, and works pretty good for language 1033 and template STS. But what I really need is to use my own template, so I’ve modified the parameters to language 1034 (Spanish) and my GUID template. The template was created from another of my Spanish sites. When I try to create a site, the response content is that lang es-ES is not admited in the server… And even trying to use my template with language 1033 gets another error: File or arguments are not valid for this site template.
    Any ideas?

    Thank you very much.

    Reply
      • I have tried to create a new Team site using the Office 365 API and REST. But, I am unable to create a new site….

        private static async Task GetSharePointAccessToken()
        {
        return (await _discoveryContext.AuthenticationContext.AcquireTokenByRefreshTokenAsync(new SessionCache().Read(“RefreshToken”), new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(_discoveryContext.AppIdentity.ClientId, _discoveryContext.AppIdentity.ClientSecret), SharePointServiceResourceId)).AccessToken;
        }

        private static async Task GetFormDigestToken()
        {
        string responseString = “”;
        string requestUrl = SharePointServiceResourceId + “/_api/contextinfo”;
        string accessToken = await GetSharePointAccessToken();

        HttpClient client = new HttpClient();
        HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(“application/json”));
        request.Headers.Authorization = new AuthenticationHeaderValue(“Bearer”, accessToken);
        HttpResponseMessage response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
        responseString = await response.Content.ReadAsStringAsync();
        int StartPos = responseString.IndexOf(“FormDigestValue”) + 18;
        int length = responseString.IndexOf(@”””,”, StartPos) – StartPos;
        string FormDigestValue = responseString.Substring(StartPos, length);
        return FormDigestValue;
        }
        return responseString;
        }

        public static async Task CreateSite()
        {
        string siteURL = SharePointServiceResourceId;
        string accessToken = await GetSharePointAccessToken();
        string digestToken = await GetFormDigestToken();

        var Data = string.Concat(
        “{‘parameters’: { ‘__metadata’: { ‘type’: ‘SP.WebInfoCreationInformation’ },”,
        “‘Title’: ‘Math Class’, ‘Url’: ‘MathClass’, ‘Language’: 1033, ‘WebTemplate’: ‘sts’,”,
        “‘UseUniquePermissions’: false }}”);

        HttpClient client1 = new HttpClient();
        HttpRequestMessage request1 = new HttpRequestMessage(HttpMethod.Post, SharePointServiceResourceId + “/_api/web/webinfos/add”);
        request1.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(“application/json”));
        request1.Headers.Add(“X-RequestDigest”, digestToken);
        request1.Headers.Authorization = new AuthenticationHeaderValue(“Bearer”, accessToken);
        request1.Content = new StringContent(Data, System.Text.Encoding.UTF8, “application/json”);

        HttpResponseMessage response1 = await client1.SendAsync(request1);

        if (response1.IsSuccessStatusCode)
        {
        string responseString1 = await response1.Content.ReadAsStringAsync();
        return responseString1;
        }
        }

        Reply
  4. Hey thanks heaps for that Marc… saved me a lot of mucking around. Also even though it was asked over a year ago I thought I would just mention how to use a custom template for anyone else that might get stuck. You can’t seem to just use the title or name with a Custom Template it must be the full SharePoint ID eg. siteTemplate: “{1e3e2a28-4721-426d-b300-1a048e084722}”.
    Although I haven’t tried i’m sure you would be able to get the ID through the API however for those who want to jump right in like me and test it first you can get the ID by downloading the solution (.wsp file) of your template and renaming extension to .cab, extract using something like 7zip and template ID will be in Feature.xml under the template folder. Hope that helps someone…
    Thanks again Marc for doing all the hard yards!

    Reply
    • Danny, thank you so much for posting this! I was desparately searching for this info, but all I was able to find were references to standard templates. Worked like a charm after following your adivce!

      Reply

Have a thought or opinion?