Sitemap

Power Pages WebAPI with $pages Client API: Modern Dataverse CRUD Implementation Guide

4 min readFeb 15, 2026

--

If you have worked with Power Pages, you probably got stuck at some point using webapi.safeAjax for Dataverse operations. Microsoft introduced the $pages Client API to simplify how developers interact with forms, authentication, Dataverse data, and multilingual configuration directly from custom HTML and JavaScript pages.

Initialize Client API

The $pages Client API is not automatically available on page load. You must ensure it is initialized before calling its methods.

Microsoft.Dynamic365.Portal.onPagesClientApiReady(($pages) => { const forms = $pages.currentPage.forms.getAll(); console.log(`Found ${forms.length} forms on the page.`); });

Promise or async/await-based readiness

let $pages = await Microsoft.Dynamic365.Portal.onPagesClientApiReady(); const forms = $pages.currentPage.forms.getAll();

$pages Client API Objects

The Client API exposes the below objects

  • currentPage
  • user
  • webAPI
  • languages

currentPage Object

Provides access to forms and components available on the current page.

The below piece of code will get all the forms inside the current page

let forms = $pages.currentPage.forms.getAll();

The below piece of code will get all the list inside the current page

let lists = $pages.currentPage.lists.getAll();

user Object

Allows programmatic sign-in and sign-out operations.

The below line of code will sign the user into the site.

$pages.user.signIn

The below line of code will sign the user into the site.

$pages.user.signOut

web API Object

Supports Create, Retrieve, and Retrieve Multiple operations in Dataverse.

Create

$pages.webAPI.createRecord('contacts', { firstName: 'John', lastName: 'Doe' });

Retrieve

let record = await $pages.webAPI.retrieveRecord('accounts', 'aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb', '$select=name');

Retrieve Multiple

let records = await $pages.webAPI.retrieveMultipleRecords('accounts','$select=name&$top=3');

languages Object

Retrieves the list of configured languages for the website.

let allLanguages = $pages.languages.getAll();

Demonstration: Custom Appointment Form

A Bootstrap-based custom HTML page submits form data into the Appointment table in Dataverse. Choice values are fetched from the StringMaps table using $pages.webAPI.retrieveMultiple and records are created using $pages.webAPI.createRecord

Press enter or click to view image in full size

Below is the HTML code of the page

<div class="container mt-2">
<h2 class="text-center">Book Appointment</h2>
<hr />
<div class="row mt-2">
<div class="col-lg-4 col-md-6">
<div class="input-group mb-3">
<span class="input-group-text rounded-0"><i class="bi bi-person"></i></span>
<input type="text" class="form-control rounded-0" id="mw_firstname" placeholder="Firstname" />
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="input-group mb-3">
<span class="input-group-text rounded-0"><i class="bi bi-person"></i></span>
<input type="text" class="form-control rounded-0" id="mw_lastname" placeholder="Lastname" />
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="input-group mb-3">
<span class="input-group-text rounded-0"><i class="bi bi-telephone"></i></span>
<input type="text" class="form-control rounded-0" id="mw_mobilenumber" placeholder="Mobile Number" />
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="input-group mb-3">
<span class="input-group-text rounded-0"><i class="bi bi-envelope"></i></span>
<input type="text" class="form-control rounded-0" id="mw_emailaddress" placeholder="Email Address" />
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="input-group mb-3">
<span class="input-group-text rounded-0"><i class="bi bi-pencil"></i></span>
<textarea class="form-control rounded-0" id="mw_subject" placeholder="Subject" rows="1"></textarea>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="input-group mb-3">
<span class="input-group-text rounded-0"><i class="bi bi-calendar-event"></i></span>
<input type="date" class="form-control rounded-0" id="mw_appointment_date" placeholder="Appointment Date" />
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="input-group mb-3">
<span class="input-group-text rounded-0"><i class="bi bi-clock"></i></span>
<select name="appointmentSlot" id="mw_appointment_slot" class="form-control rounded-0">
<option value="null" disabled selected>Select your slot</option>
</select>
</div>
</div>
<div class="col-lg-4 col-md-6">
<button class="btn btn-outline-info rounded-0 w-100" id="bookAppointment">Book Appointment</button>
</div>
</div>
</div>

Below is the JS Code

Microsoft.Dynamic365.Portal.onPagesClientApiReady(async ($pages) => {
const firstNameField = document.getElementById("mw_firstname");
const lastNameField = document.getElementById("mw_lastname");
const emailAddressField = document.getElementById("mw_emailaddress");
const mobileNumberField = document.getElementById("mw_mobilenumber");
const subjectField = document.getElementById("mw_subject");
const aptDateField = document.getElementById("mw_appointment_date");
const slotField = document.getElementById("mw_appointment_slot");
const bookAptBtn = document.getElementById("bookAppointment");

// retrieve multiple stringmaps records for fetching options of slot choice
let slotOptions = await $pages.webAPI.retrieveMultipleRecords('stringmaps', "$select=attributename,attributevalue,displayorder,objecttypecode,value&$filter=attributename eq 'mw_appointment_slot' and objecttypecode eq 'mw_appointment'&$orderby=displayorder asc");
slotOptions.value.forEach((opt) => {
let newSlotOption = document.createElement("option");
newSlotOption.text = opt.value;
newSlotOption.value = opt.attributevalue;
slotField.appendChild(newSlotOption);
});

bookAptBtn.addEventListener("click", function () {
try {
// create an appointment record
$pages.webAPI.createRecord('mw_appointments', {
"mw_firstname": firstNameField.value,
"mw_lastname": lastNameField.value,
"mw_mobilenumber": mobileNumberField.value,
"mw_emailaddress": emailAddressField.value,
"mw_appointment_date": aptDateField.value,
"mw_appointment_slot": slotField.value,
"mw_subject": subjectField.value,
"mw_appointment_status": 2000,
}).then(
function success(){
// show alert after appointment booked
alert(`Appointment Booked on ${aptDateField.value} and the your slot is ${slotField.options[slotField.selectedIndex].text}`);
// refresh the current page
history.go(0);
}
);
} catch (err) {
console.error(err.message);
}
});
});

Below is the image after the appointment is booked

Press enter or click to view image in full size

References

Have a great day!

Originally published at https://www.tamilarasu.blog on February 15, 2026.

--

--

Tamilarasu Arunachalam
Tamilarasu Arunachalam

Written by Tamilarasu Arunachalam

A Software Engineer with experience in developing applications out of Power Platform, majorly on Power Apps and Power Automate. I am a seasonal blogger too.