Comprehensive JavaScript framework for Microsoft Dynamics CRM/365 WebAPI integration with promise-based CRUD operations, batch processing, and 200+ pre-implemented actions
Transaction-based batch operations allowing multiple requests to be sent together with rollback capabilities. Batches can contain GET requests directly and change sets for transactional operations, providing efficient bulk processing with automatic error handling.
Container for multiple requests that can be processed as a single operation.
/**
* Batch request container for sending multiple operations together
*/
class Batch implements BatchParameters {
/** Batch identifier name */
name?: string;
/** Change sets containing transactional operations */
changeSets?: Array<ChangeSet>;
/** GET requests (cannot be in change sets) */
requests?: Array<BatchRequest>;
/** Headers to apply to the entire batch */
headers?: Array<Header>;
/** Override async setting for batch */
async?: boolean;
/** Internal flag for URL length handling */
isOverLengthGet?: boolean;
constructor(parameters: BatchParameters);
}
interface BatchParameters extends BaseParameters {
name?: string;
changeSets?: Array<ChangeSet>;
requests?: Array<BatchRequest>;
isOverLengthGet?: boolean;
}Transactional container where all operations succeed or fail together.
/**
* Transactional container for batch operations - all requests succeed or fail together
*/
class ChangeSet implements ChangeSetParameters {
/** Change set identifier name */
name?: string;
/** Array of batch requests in this change set */
requests?: Array<BatchRequest>;
constructor(parameters: ChangeSetParameters);
}
interface ChangeSetParameters {
name?: string;
requests?: Array<BatchRequest>;
}Individual request within a batch or change set.
/**
* Individual request that can be included in batches or change sets
*/
class BatchRequest implements BatchRequestParameters {
/** HTTP method (GET, POST, PATCH, DELETE) */
method: string;
/** Request URL (optional, can be auto-generated) */
url?: string;
/** Request payload as JSON string */
payload?: string;
/** Headers specific to this request */
headers?: Array<Header>;
/** Content ID for referencing in other requests */
contentId?: string;
constructor(params: BatchRequestParameters);
}
interface BatchRequestParameters {
method: string;
url?: string;
payload?: string;
headers?: Array<Header>;
contentId?: string;
}Executes a batch request and returns comprehensive response information.
/**
* Send a batch request to CRM
* @param batch - Batch object containing requests and change sets
* @returns Promise resolving to batch response with results and error information
*/
function SendBatch(batch: Batch): Promise<BatchResponse> | BatchResponse;/**
* Response from a batch operation containing all individual responses
*/
class BatchResponse implements BatchResponseParameters {
/** Batch identifier name */
name?: string;
/** Responses from change sets */
changeSetResponses?: Array<ChangeSetResponse>;
/** Responses from direct batch requests (GET operations) */
batchResponses?: Array<Response>;
/** Whether any requests in the batch failed */
isFaulted?: boolean;
/** Collection of all error responses */
errors?: Array<string>;
/** Original XMLHttpRequest object */
xhr?: XMLHttpRequest;
constructor(parameters: BatchResponseParameters);
}
interface BatchResponseParameters {
name?: string;
changeSetResponses?: Array<ChangeSetResponse>;
batchResponses?: Array<Response>;
isFaulted?: boolean;
errors?: Array<string>;
xhr?: XMLHttpRequest;
}
/**
* Response from a change set containing individual request responses
*/
class ChangeSetResponse {
/** Change set identifier name */
name?: string;
/** Individual responses for each request in the change set */
responses?: Array<Response>;
}
/**
* Individual response from a batch request
*/
class Response implements ResponseParameters {
/** Raw response data */
rawData?: string;
/** Content ID if specified in request */
contentId?: string;
/** Parsed response payload */
payload?: object;
/** HTTP status code */
status?: string;
/** Response headers as key-value object */
headers?: any;
constructor(parameters: ResponseParameters);
}
interface ResponseParameters {
rawData?: string;
contentId?: string;
payload?: object;
status?: string;
headers?: any;
}// Create multiple related records in a transaction
const batch = new WebApiClient.Batch({
changeSets: [
new WebApiClient.ChangeSet({
requests: [
WebApiClient.Create({
entityName: "account",
entity: { name: "Parent Account" },
headers: [{ key: "Prefer", value: "return=representation" }],
asBatch: true
}),
WebApiClient.Create({
entityName: "contact",
entity: {
firstname: "John",
lastname: "Doe",
"parentcustomerid_account@odata.bind": "/accounts($1)" // Reference to first request
},
asBatch: true
})
]
})
]
});
const batchResponse = await WebApiClient.SendBatch(batch);
if (batchResponse.isFaulted) {
console.log("Batch failed:", batchResponse.errors);
} else {
console.log("All operations succeeded");
console.log(batchResponse.changeSetResponses[0].responses);
}// Combine read operations with transactional writes
const batch = new WebApiClient.Batch({
// GET requests go directly in batch (not in change sets)
requests: [
WebApiClient.Retrieve({
entityName: "systemuser",
queryParams: "?$select=fullname,systemuserid&$filter=isdisabled eq false",
asBatch: true
})
],
// Change operations go in change sets for transactions
changeSets: [
new WebApiClient.ChangeSet({
requests: [
WebApiClient.Update({
entityName: "account",
entityId: "12345678-1234-1234-1234-123456789abc",
entity: { name: "Updated Account" },
asBatch: true
}),
WebApiClient.Delete({
entityName: "contact",
entityId: "87654321-4321-4321-4321-cba987654321",
asBatch: true
})
]
})
]
});
const result = await WebApiClient.SendBatch(batch);
// Access GET request results
const userResults = result.batchResponses[0].payload;
// Access change set results
const changeResults = result.changeSetResponses[0].responses;// Execute multiple CRM actions in a transaction
const batch = new WebApiClient.Batch({
changeSets: [
new WebApiClient.ChangeSet({
requests: [
WebApiClient.Execute(
WebApiClient.Requests.QualifyLeadRequest.with({
entityId: "lead-guid-here",
payload: {
CreateAccount: true,
CreateContact: true,
CreateOpportunity: true,
Status: 3
}
}),
{ asBatch: true }
),
WebApiClient.Execute(
WebApiClient.Requests.SendEmailRequest.with({
payload: {
TemplateId: "template-guid-here",
RegardingId: "opportunity-guid-here",
RegardingType: "opportunity"
}
}),
{ asBatch: true }
)
]
})
]
});
const actionResults = await WebApiClient.SendBatch(batch);// Long FetchXML queries are automatically converted to batch requests
const longFetchXml = `
<fetch mapping='logical'>
<entity name='account'>
<attribute name='name'/>
<attribute name='accountid'/>
<!-- Very long fetch query that exceeds URL limits -->
<filter type='and'>
<condition attribute='name' operator='like' value='%very long search term%'/>
<!-- Many more conditions... -->
</filter>
<link-entity name='contact' from='parentcustomerid' to='accountid'>
<!-- Many attributes and conditions -->
</link-entity>
</entity>
</fetch>`;
// This will automatically be sent as a batch if URL length exceeds limits
const results = await WebApiClient.Retrieve({
entityName: "account",
fetchXml: longFetchXml
});const batch = new WebApiClient.Batch({
changeSets: [
new WebApiClient.ChangeSet({
name: "AccountOperations",
requests: [
WebApiClient.Create({
entityName: "account",
entity: { name: "Test Account 1" },
asBatch: true
}),
WebApiClient.Create({
entityName: "account",
entity: { name: "Test Account 2" },
asBatch: true
}),
WebApiClient.Update({
entityName: "account",
entityId: "existing-account-guid",
entity: { name: "Updated Name" },
asBatch: true
})
]
})
]
});
try {
const response = await WebApiClient.SendBatch(batch);
if (response.isFaulted) {
console.log("Batch contained errors:");
response.errors.forEach((error, index) => {
console.log(`Error ${index + 1}:`, error);
});
// Check individual change set responses
response.changeSetResponses.forEach((changeSetResp, csIndex) => {
console.log(`Change set ${csIndex} (${changeSetResp.name}):`);
changeSetResp.responses.forEach((resp, reqIndex) => {
if (resp.status !== "200" && resp.status !== "201") {
console.log(` Request ${reqIndex} failed:`, resp.status, resp.payload);
} else {
console.log(` Request ${reqIndex} succeeded:`, resp.payload);
}
});
});
} else {
console.log("All batch operations succeeded");
// Process successful results
const changeSetResults = response.changeSetResponses[0].responses;
changeSetResults.forEach((result, index) => {
console.log(`Operation ${index} result:`, {
status: result.status,
entityId: result.headers["OData-EntityId"],
data: result.payload
});
});
}
} catch (networkError) {
console.error("Network or request error:", networkError);
}// Use content IDs to reference results from previous requests in the same batch
const batch = new WebApiClient.Batch({
changeSets: [
new WebApiClient.ChangeSet({
requests: [
// Create account with content ID
new WebApiClient.BatchRequest({
method: "POST",
url: WebApiClient.GetApiUrl() + WebApiClient.GetSetName("account"),
payload: JSON.stringify({ name: "Parent Account" }),
headers: [
{ key: "Content-Type", value: "application/json" },
{ key: "Prefer", value: "return=representation" }
],
contentId: "1"
}),
// Create contact referencing the account
new WebApiClient.BatchRequest({
method: "POST",
url: WebApiClient.GetApiUrl() + WebApiClient.GetSetName("contact"),
payload: JSON.stringify({
firstname: "John",
lastname: "Doe",
"parentcustomerid_account@odata.bind": "$1" // References content ID 1
}),
headers: [
{ key: "Content-Type", value: "application/json" }
],
contentId: "2"
})
]
})
]
});
const result = await WebApiClient.SendBatch(batch);Use Change Sets for Transactional Operations: Put related create, update, delete operations in change sets to ensure data consistency.
Keep GET Requests Outside Change Sets: Retrieve operations must be placed directly in the batch requests array.
Limit Batch Size: Keep batches under 100 requests for optimal performance.
Handle Errors Properly: Always check the isFaulted property and process errors appropriately.
Use Content IDs for Dependencies: When requests depend on results from previous requests in the same batch, use content IDs for references.
Set Appropriate Headers: Include necessary headers like Prefer: return=representation when you need response data.
Monitor Performance: Large batches may take longer to process and could timeout in some scenarios.
Install with Tessl CLI
npx tessl i tessl/npm-xrm-webapi-client