Live training: next batches coming soon. Join the waitlist:Waitlists open:Developer Training →Developer →Copilot Studio Training →Copilot Studio →

JSOM in SharePoint: JavaScript Object Model Examples That Still Work

If you need to read or update SharePoint list data with plain JavaScript on a classic page, the JavaScript Object Model (JSOM) does it in three moves: get an SP.ClientContext, queue the work with load(), and send it with executeQueryAsync().

Where people get stuck is the setup. Most JSOM samples online are a decade old, they paste script into a Script Editor web part, and that web part isn’t even available on most SharePoint Online sites today.

In this tutorial, we’ll run real JSOM SharePoint examples on my SPGuides site: site properties, creating a list, adding, reading, updating and deleting items, and the current user’s groups. I’ll also show where JSOM still runs in 2026 and what to use instead on modern pages.

What is JSOM in SharePoint?

JSOM stands for JavaScript Object Model. It’s the browser version of the SharePoint client object model, shipped as SP.Runtime.js and SP.js under /_layouts/15/. It’s also called the ECMAScript object model.

It works like the C# client object model (CSOM): you build a batch of requests and nothing reaches the server until you execute it:

  • SP.ClientContext is the entry point; get_current() returns the current site’s context.
  • load() marks the objects and properties you want back, without calling the server.
  • executeQueryAsync(success, failure) sends the whole batch in one request to /_vti_bin/client.svc/ProcessQuery and runs one of your two callbacks.

That batching is why you can create a list, add two columns and read the result in one round trip.

Is JSOM still supported in SharePoint Online?

Yes, but only on classic pages. Microsoft’s JSOM reference says the classic JavaScript client object model isn’t supported with modern experiences or with the SharePoint Framework. In my tenant, modern list pages didn’t even load the SP object, while the classic Site contents page did.

Three more changes limit where it runs in 2026:

  • Custom script is blocked by default on modern team and communication sites, which removes the Script Editor and Content Editor web parts. An admin can allow it per site, but SharePoint turns it back off within 24 hours.
  • Content Security Policy has been enforced since March 1, 2026 (tenants could delay it to June 1, 2026). It blocks inline script on modern pages; Microsoft says it isn’t enforced on classic pages.
  • SharePoint Add-ins, where most JSOM code used to live, stopped working on April 2, 2026.

So keep JSOM for maintaining classic pages, SharePoint Server farms and quick console scripts. For new work, use the SharePoint REST API or PnPjs inside SharePoint Framework solutions.

The sample lists we’ll use

Every read example uses my SPGDemo Orders list: 210 sales orders with Title (order number), Customer Name, Quantity, Order Total, Order Date and Order Status. A few rows have a blank total, which matters for the CAML query later.

SPGDemo Orders sample SharePoint list used for the JSOM read examples

For the write examples, Method 2 creates a second list, SPGDemo JSOM Tasks, with a TaskStatus choice column and a DueDate column. If you prefer building lists by hand, see creating a SharePoint list or library in SPFx for the modern way.

How to run JSOM code on a SharePoint page

I ran every example here from the browser console on the classic Site contents page (/_layouts/15/viewlsts.aspx), where SP.js is already loaded. No site settings change needed.

On a classic page you own, don’t call JSOM before SP.js is ready. SP.SOD.executeFunc loads it on demand and then runs your function:

SP.SOD.executeFunc("sp.js", "SP.ClientContext", function () {
  var ctx = SP.ClientContext.get_current();
  var web = ctx.get_web();
  ctx.load(web, "Title");
  ctx.executeQueryAsync(function () {
    console.log("Connected to " + web.get_title());
  }, function (sender, args) {
    console.log("Request failed: " + args.get_message());
  });
});

Save that code as a .js file in Site Assets and reference the file, because inline script is what CSP and the custom script setting block:

<div id="jsomResult"></div>
<script src="/sites/SPGuides/SiteAssets/jsom-demo.js"></script>

To see the results on the page, I use a small helper that writes into a panel at the top of the classic page. Every example below calls show() and onFail(). The HTML/JS idea is the same one a modern Script Editor web part built with SPFx uses:

// Writes the result into a panel at the top of the classic page's main area
function show(html) {
  var panel = document.getElementById("jsomResult");
  if (!panel) {
    panel = document.createElement("div");
    panel.id = "jsomResult";
    panel.style.cssText = "display:inline-block;margin:0 0 16px;padding:12px 16px;border:2px solid #0078d4;" +
      "background:#f3f9fd;font:15px 'Segoe UI';line-height:1.6";
    var main = document.getElementById("DeltaPlaceHolderMain") || document.body;
    main.prepend(panel);
  }
  panel.innerHTML = html;
}

function onFail(sender, args) {
  show("Request failed: " + args.get_message());
}

Method 1: Get SharePoint site properties with JSOM

Start here to check which site you’re on. Naming the properties in load() keeps the response small:

var ctx = SP.ClientContext.get_current();
var web = ctx.get_web();
ctx.load(web, "Title", "ServerRelativeUrl", "Language", "WebTemplate");

ctx.executeQueryAsync(function () {
  show("<b>Title:</b> " + web.get_title() +
    "<br><b>Server-relative URL:</b> " + web.get_serverRelativeUrl() +
    "<br><b>Language (LCID):</b> " + web.get_language() +
    "<br><b>Template:</b> " + web.get_webTemplate());
}, onFail);

On my site it returned the title SPGuides, the server-relative URL /sites/SPGuides, language 1033 (English, United States) and the GROUP template of a group-connected team site.

JSOM get_web result showing the SPGuides site title and server-relative URL

How does this work?

get_web() only gives you an empty proxy object. The load(web, "Title", ...) call marks four properties to fetch, and executeQueryAsync sends the request.

In the success callback web.get_title() works. Read a property you didn’t load, like get_description(), and JSOM throws a “has not been initialized” error.

Method 2: Create a SharePoint list and add columns with JSOM

SP.ListCreationInformation describes the new list and addFieldAsXml adds its columns in the same batch:

var ctx = SP.ClientContext.get_current();
var info = new SP.ListCreationInformation();
info.set_title("SPGDemo JSOM Tasks");
info.set_templateType(SP.ListTemplateType.genericList);
info.set_description("Tasks created with the SharePoint JavaScript Object Model");

var list = ctx.get_web().get_lists().add(info);

// Two extra columns, added to the default view
var fields = list.get_fields();
fields.addFieldAsXml(
  "<Field Type='Choice' DisplayName='TaskStatus' Name='TaskStatus'>" +
  "<Default>Not Started</Default><CHOICES><CHOICE>Not Started</CHOICE>" +
  "<CHOICE>In Progress</CHOICE><CHOICE>Completed</CHOICE></CHOICES></Field>",
  true, SP.AddFieldOptions.addFieldInternalNameHint);
fields.addFieldAsXml(
  "<Field Type='DateTime' DisplayName='DueDate' Name='DueDate' Format='DateOnly' />",
  true, SP.AddFieldOptions.addFieldInternalNameHint);

ctx.load(list, "Title", "Id");
ctx.executeQueryAsync(function () {
  show("Created list <b>" + list.get_title() + "</b> (ID " + list.get_id() + ")");
}, onFail);

The list appeared in Site contents with both columns in the default view.

How does this work?

SP.ListTemplateType.genericList is a plain custom list (template 100). The second argument of addFieldAsXml, true, adds the column to the default view.

addFieldInternalNameHint makes SharePoint use the Name from the XML as the internal name. That’s why I keep column names without spaces: TaskStatus stays TaskStatus instead of Task_x0020_Status.

Method 3: Add an item to a SharePoint list using JSOM

Adding an item is addItem, then set_item for each column, then update(). I’d already added four tasks the same way, so this one became item 5:

var ctx = SP.ClientContext.get_current();
var list = ctx.get_web().get_lists().getByTitle("SPGDemo JSOM Tasks");

var item = list.addItem(new SP.ListItemCreationInformation());
item.set_item("Title", "Order holiday gift cards");
item.set_item("TaskStatus", "Not Started");
// Noon UTC on 12/11/2026 (months are 0-based), so no time zone shifts the date
item.set_item("DueDate", new Date(Date.UTC(2026, 11, 11, 12)));
item.update();

ctx.load(item, "Id");
ctx.executeQueryAsync(function () {
  show("Item created with ID <b>" + item.get_id() + "</b>");
}, onFail);

The panel showed “Item created with ID 5”, and the list view shows the new task with its 12/11/2026 due date.

SPGDemo JSOM Tasks list showing the item added with JSOM addItem

How does this work?

An empty SP.ListItemCreationInformation creates a normal item in the root folder. Nothing is saved until executeQueryAsync runs, so get_id() only works in the success callback.

So here’s the catch with dates. My first run used new Date(2026, 10, 20) and SharePoint showed 11/19/2026: browser-time midnight is the evening before in my site’s Pacific time zone. Noon UTC avoids the shift.

Want the same thing without JSOM? Compare it with CRUD operations in an SPFx web part.

Method 4: Get SharePoint list items with a CAML query

Use getItems with an SP.CamlQuery whenever you need a filter, a sort or a row limit. This one returns the five largest orders that have a total:

var ctx = SP.ClientContext.get_current();
var list = ctx.get_web().get_lists().getByTitle("SPGDemo Orders");

var query = new SP.CamlQuery();
query.set_viewXml(
  "<View><Query><Where>" +
  "<IsNotNull><FieldRef Name='OrderTotal'/></IsNotNull>" +
  "</Where><OrderBy><FieldRef Name='OrderTotal' Ascending='FALSE'/></OrderBy></Query>" +
  "<ViewFields><FieldRef Name='Title'/><FieldRef Name='CustomerName'/>" +
  "<FieldRef Name='OrderTotal'/><FieldRef Name='OrderStatus'/></ViewFields><RowLimit>5</RowLimit></View>");

var items = list.getItems(query);
ctx.load(items);
ctx.executeQueryAsync(function () {
  var rows = "";
  var e = items.getEnumerator();
  while (e.moveNext()) {
    var it = e.get_current();
    rows += "<tr><td>" + it.get_item("Title") + "</td><td>" + it.get_item("CustomerName") +
      "</td><td>" + it.get_item("OrderStatus") +
      "</td><td>$" + it.get_item("OrderTotal").toFixed(2) + "</td></tr>";
  }
  show("<b>The 5 largest orders</b><table cellpadding='4'>" +
    "<tr><th>Order</th><th>Customer</th><th>Status</th><th>Total</th></tr>" + rows + "</table>");
}, onFail);

SO-1102 for Adventure Works ($1,890.00) came back first, then Fabrikam ($1,499.88).

JSOM CAML query result listing the five largest orders from SPGDemo Orders

How does this work?

IsNotNull skips orders with a blank total. Without it, my first test crashed on toFixed() because a null total came back in the top five.

OrderBy sorts on the server, ViewFields limits the columns and RowLimit caps the batch. If writing CAML by hand feels error-prone, a CAML query builder can generate the XML for you.

Pro Tip: Always set a RowLimit. Lists with more than 5,000 items hit the list view threshold when your filter column isn’t indexed, and JSOM reports it as a failed query.

Method 5: Update a SharePoint list item using JSOM

To change an existing item, get it with getItemById, set the new values and call update(). Here’s item 1 moving to In Progress:

var ctx = SP.ClientContext.get_current();
var list = ctx.get_web().get_lists().getByTitle("SPGDemo JSOM Tasks");

var item = list.getItemById(1);
item.set_item("TaskStatus", "In Progress");
item.set_item("Title", "Prepare Q4 budget review (draft sent)");
item.update();

ctx.load(item, "Title", "TaskStatus");
ctx.executeQueryAsync(function () {
  show("Item 1 is now <b>" + item.get_item("TaskStatus") + "</b>: " + item.get_item("Title"));
}, onFail);
JSOM update result showing item 1 changed to In Progress

How does this work?

getItemById(1) doesn’t fetch anything first. JSOM sends the update and the follow-up load in one request, so the panel shows the saved values: In Progress and the new title.

Method 6: Delete a SharePoint list item using JavaScript

JSOM gives you two ways to remove an item. I prefer recycle(), because it moves the item to the site recycle bin instead of deleting it for good:

var ctx = SP.ClientContext.get_current();
var list = ctx.get_web().get_lists().getByTitle("SPGDemo JSOM Tasks");

var item = list.getItemById(3);
item.recycle();          // deleteObject() would skip the recycle bin

ctx.executeQueryAsync(function () {
  show("Item 3 moved to the site recycle bin");
}, onFail);

Item 3, Renew Adobe Acrobat licenses, landed in the recycle bin with its original location.

Site recycle bin showing the list item removed with the JSOM recycle method

How does this work?

recycle() works like Delete in the browser, so users can restore the item for 93 days from the SharePoint recycle bin. deleteObject(), the method in most older samples, deletes the item permanently.

Method 7: Get the current user and their SharePoint groups

This is the classic way to show or hide page parts by group membership. get_currentUser() returns the signed-in user and get_groups() their SharePoint groups:

var ctx = SP.ClientContext.get_current();
var user = ctx.get_web().get_currentUser();
var groups = user.get_groups();
ctx.load(user, "Title", "IsSiteAdmin");
ctx.load(groups, "Include(Title)");

ctx.executeQueryAsync(function () {
  var names = [];
  var e = groups.getEnumerator();
  while (e.moveNext()) { names.push(e.get_current().get_title()); }
  show("<b>Signed in as:</b> " + user.get_title() +
    "<br><b>Site collection admin:</b> " + user.get_isSiteAdmin() +
    "<br><b>SharePoint groups:</b> " + (names.join(", ") || "none"));
}, onFail);
JSOM current user result showing site admin status and SharePoint group names

How does this work?

Include(Title) loads just the group names. My account showed as a site collection admin in three groups.

Keep in mind it lists SharePoint groups only. Access that comes from a Microsoft 365 group or admin rights won’t show up here. In SPFx you’d read the user from the page context instead, as in displaying the current user name in an SPFx web part.

Method 8: Use executeQueryAsync with async/await

Nested callbacks get messy fast. Wrap executeQueryAsync in a Promise and write JSOM top to bottom:

// Wrap executeQueryAsync in a Promise so you can use async/await
function executeQuery(ctx) {
  return new Promise(function (resolve, reject) {
    ctx.executeQueryAsync(resolve, function (sender, args) {
      reject(new Error(args.get_message()));
    });
  });
}

async function countOrders() {
  var ctx = SP.ClientContext.get_current();
  var list = ctx.get_web().get_lists().getByTitle("SPGDemo Orders");
  ctx.load(list, "Title", "ItemCount");
  await executeQuery(ctx);
  show(list.get_title() + " has <b>" + list.get_itemCount() + "</b> items");
}

countOrders().catch(function (err) { show("Request failed: " + err.message); });

It printed “SPGDemo Orders has 210 items”.

How does this work?

The wrapper resolves on success and rejects with the JSOM error message, so one catch handles every failure. Loading ItemCount is also the cheapest way to count list items, because it doesn’t read a single row.

JSOM vs REST API vs PnPjs: which should you use?

OptionWhere it runsUse it when
JSOMClassic pages, SharePoint Server, browser consoleYou maintain existing classic pages or need a quick console script
SharePoint REST APIAnywhere that can send HTTP requestsYou need plain fetch calls, Power Automate or code outside SharePoint
PnPjs in SPFxModern pages, Teams, list extensionsYou build anything new for SharePoint Online

If you’re new to SPFx, building your first SharePoint client-side web part walks through the setup, and displaying list items in an SPFx web part is the modern version of Method 4.

Things to Keep in Mind

These are the errors and limits I hit most often with JSOM code, the first two in my own test runs. More SharePoint fixes live in our SharePoint tutorials hub.

  • “List … does not exist”: getByTitle needs the exact display name, so check spelling and spaces.
  • “The property or field … has not been initialized”: add the property to load() before you read it.
  • Large files: JSOM handles files up to 1.5 MB. Use the REST API for bigger uploads.
  • Dates shift by a day: send dates as noon UTC, or convert to the site’s time zone first.
  • Nothing happens on a modern page: there’s no SP.js there. Move the code to an SPFx web part.

Moving JSOM code to modern SharePoint? These guides cover the next steps:

Frequently Asked Questions

What is the full form of JSOM in SharePoint?

JSOM stands for JavaScript Object Model, the browser version of the SharePoint client object model loaded from SP.Runtime.js and SP.js.

Is JSOM deprecated in SharePoint Online?

JSOM still works on classic pages, but Microsoft doesn’t support it on modern pages or in SPFx. The JavaScript library basic operations article carries that warning, and new projects should use REST or PnPjs in SPFx.

What does executeQueryAsync do in JSOM?

It sends every queued operation to SharePoint in one request, then calls your success or failure function. Loaded values exist only from the success callback on.

Can I use JSOM in a Script Editor web part?

Only on classic pages of sites that allow custom script, and SharePoint resets that setting within 24 hours. Inline script on modern pages is blocked by Content Security Policy in SharePoint Online.

What is the difference between JSOM and the REST API?

JSOM batches calls through client.svc and needs SP.js on the page. The REST API uses plain HTTP calls to /_api and works from any page, app or flow.

3 thoughts on “JSOM in SharePoint: JavaScript Object Model Examples That Still Work”

Leave a Comment