A modern, enterprise-grade TypeScript SDK for Microsoft Dataverse (Online), designed for both Node.js 18+ and modern browsers. Built with ESM-only architecture, native fetch, and comprehensive TypeScript typing.
Provide a production-ready, type-safe, and developer-friendly SDK for Microsoft Dataverse that:
- โ Works everywhere: Node.js 18+ and modern browsers (ESM-only)
- โ Two developer experiences: HTTP-first fluent API (Microsoft Graph-like) + C#-familiar OrganizationService facade
- โ Enterprise resilience: Built-in retry, adaptive concurrency, throttling awareness
- โ Type-safe operations: Strongly typed Web API actions/functions, messages, and metadata operations
- โ
Modern DX: Native
fetch, promise-based, async iterators, middleware pipeline - โ Production telemetry: Application Insights and OpenTelemetry support
The SDK is organized as a monorepo with focused, composable packages:
| Package | Description | Status |
|---|---|---|
| @mohsinonxrm/dataverse-sdk-core | HTTP pipeline, middleware, fluent request builder, OData query support | โ Complete |
| @mohsinonxrm/dataverse-sdk-batch | $batch operations with change sets, Content-ID references, auto-split | โ Complete |
| @mohsinonxrm/dataverse-sdk-xrm | OrganizationService facade (create/retrieve/update/delete/associate/execute) | โ Complete |
| Package | Description | Status |
|---|---|---|
| @mohsinonxrm/dataverse-sdk-auth-msal-browser | MSAL browser token provider (SPA authentication) | โ Complete |
| @mohsinonxrm/dataverse-sdk-auth-msal-node | MSAL node token provider (device code, client credentials) | โ Complete |
| @mohsinonxrm/dataverse-sdk-auth-azure-identity | Azure Identity token provider (DefaultAzureCredential) | โ Complete |
| Package | Description | Status |
|---|---|---|
| @mohsinonxrm/dataverse-sdk-actions | Typed Web API actions (POST operations with side effects) | โ Complete |
| @mohsinonxrm/dataverse-sdk-functions | Typed Web API functions (GET operations without side effects) | โ Complete |
| @mohsinonxrm/dataverse-sdk-messages | 1,356+ typed Organization Service messages (direct exports) | โ Complete |
| @mohsinonxrm/dataverse-sdk-metadata | Strict metadata typing (EntityMetadata, AttributeMetadata, etc.) | โ Complete |
| @mohsinonxrm/dataverse-sdk-files | File/image column operations with chunked upload/download | โ Complete |
| Package | Description | Status |
|---|---|---|
| @mohsinonxrm/dataverse-sdk-generator | Early-bound entity type generator from $metadata | โ Complete |
| @mohsinonxrm/dataverse-sdk-entities-runtime | Runtime base classes for generated entities | โ Complete |
| @mohsinonxrm/dataverse-sdk-discovery | Global Discovery Service (Commercial cloud) | โ Complete |
| Package | Description | Status |
|---|---|---|
| @mohsinonxrm/dataverse-sdk-appinsights | Application Insights telemetry adapter | โ Complete |
| @mohsinonxrm/dataverse-sdk-otel | OpenTelemetry telemetry adapter | โ Complete |
# Core SDK + authentication
npm install @mohsinonxrm/dataverse-sdk-core @mohsinonxrm/dataverse-sdk-auth-msal-node
# Or with pnpm
pnpm add @mohsinonxrm/dataverse-sdk-core @mohsinonxrm/dataverse-sdk-auth-msal-nodeimport { DataverseClient } from "@mohsinonxrm/dataverse-sdk-core";
import { MsalNodeTokenProvider } from "@mohsinonxrm/dataverse-sdk-auth-msal-node";
import { ConfidentialClientApplication } from "@azure/msal-node";
// Configure MSAL
const cca = new ConfidentialClientApplication({
auth: {
clientId: process.env.AZURE_CLIENT_ID!,
authority: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}`,
clientSecret: process.env.AZURE_CLIENT_SECRET!,
},
});
// Create token provider
const tokenProvider = new MsalNodeTokenProvider(cca, {
flow: "clientCredentials",
scopes: ["https://yourorg.crm.dynamics.com/.default"],
});
// Create Dataverse client
const client = new DataverseClient({
baseUrl: "https://yourorg.crm.dynamics.com",
tokenProvider,
});
// Query data
const accounts = await client
.api("/accounts")
.select("name", "revenue")
.filter("revenue gt 1000000")
.orderBy("revenue desc")
.top(10)
.get();
console.log(accounts);import { DataverseClient } from '@mohsinonxrm/dataverse-sdk-core';
import { MsalBrowserTokenProvider } from '@mohsinonxrm/dataverse-sdk-auth-msal-browser';
import { PublicClientApplication } from '@azure/msal-browser';
// Configure MSAL
const msalInstance = new PublicClientApplication({
auth: {
clientId: 'your-app-registration-id',
authority: 'https://login.microsoftonline.com/your-tenant-id',
redirectUri: window.location.origin,
},
});
// Create token provider
const tokenProvider = new MsalBrowserTokenProvider(msalInstance, {
scopes: ['https://yourorg.crm.dynamics.com/user_impersonation'],
});
// Create client
const client = new DataverseClient({
baseUrl: 'https://yourorg.crm.dynamics.com',
tokenProvider,
});
// Use in React component
function AccountList() {
const [accounts, setAccounts] = useState([]);
useEffect(() => {
client.api('/accounts')
.select('name', 'revenue')
.top(50)
.get()
.then(setAccounts);
}, []);
return <div>{/* Render accounts */}</div>;
}Modern, chainable API for web/Node.js developers:
// OData query building
const highValueAccounts = await client
.api("/accounts")
.select("name", "revenue", "industrycode")
.filter("revenue gt 5000000 and industrycode eq 6")
.expand("primarycontactid($select=fullname,emailaddress1)")
.orderBy("revenue desc")
.top(25)
.get<Account[]>();
// Create with return
const newAccount = await client
.api("/accounts")
.header("Prefer", "return=representation")
.post<Account>({
name: "Contoso Ltd",
revenue: 10000000,
industrycode: 6,
});
// Update with optimistic concurrency
await client.api(`/accounts(${accountId})`).ifMatch(etag).patch({
revenue: 12000000,
});
// Paging support
for await (const account of client.api("/accounts").select("name").iterate()) {
console.log(account.name);
}Familiar API for Dataverse/C# developers:
import { OrganizationService } from "@mohsinonxrm/dataverse-sdk-xrm";
const org = new OrganizationService(client);
// Create
const accountId = await org.create("accounts", {
name: "Fabrikam Inc",
revenue: 5000000,
});
// Retrieve
const account = await org.retrieve("accounts", accountId, {
select: ["name", "revenue"],
expand: {
primarycontactid: { select: ["fullname"] },
},
});
// Update
await org.update(
"accounts",
accountId,
{
websiteurl: "https://fabrikam.com",
},
{ etag: account["@odata.etag"] }
);
// Delete
await org.delete("accounts", accountId);
// Associate
await org.associate("accounts", accountId, "contact_customer_accounts", [contactId1, contactId2]);Actions (POST with side effects):
import { CreateMultipleAction, WinOpportunityAction } from "@mohsinonxrm/dataverse-sdk-actions";
// Bulk create
const createMultiple = new CreateMultipleAction("accounts", [
{ "@odata.type": "Microsoft.Dynamics.CRM.account", name: "Account 1" },
{ "@odata.type": "Microsoft.Dynamics.CRM.account", name: "Account 2" },
]);
const result = await client.execute(createMultiple);
console.log(result.Ids); // ['guid1', 'guid2']
// Win opportunity
const winAction = new WinOpportunityAction(
{
opportunityid: opportunityId,
"opportunityid@odata.bind": `/opportunities(${opportunityId})`,
actualrevenue: 100000,
actualend: new Date(),
},
3
); // Status: Won
await client.execute(winAction);Functions (GET without side effects):
import { WhoAmIFunction } from "@mohsinonxrm/dataverse-sdk-functions";
const whoAmI = await client.execute(new WhoAmIFunction());
console.log(whoAmI.UserId);import { BatchRequestBuilder } from "@mohsinonxrm/dataverse-sdk-batch";
const batch = new BatchRequestBuilder(client);
// Non-transactional requests
batch.addRequest("get-accounts", "GET", "/accounts?$top=5");
// Transactional change set
batch.beginChangeset();
// Create account with Content-ID $1
batch.addRequest(
"create-account",
"POST",
"/accounts",
{ "Content-Type": "application/json", Prefer: "return=representation" },
{ name: "Parent Account" }
);
// Create contact referencing $1
batch.addRequest(
"create-contact",
"POST",
"/contacts",
{ "Content-Type": "application/json" },
{
firstname: "John",
lastname: "Doe",
"parentcustomerid_account@odata.bind": "$1", // References Content-ID
}
);
batch.endChangeset();
// Execute
const result = await batch.execute();
if (result.success) {
console.log("All operations succeeded");
} else {
for (const [id, error] of result.errors) {
console.error(`Request ${id} failed:`, error);
}
}import {
WhoAmIRequest,
ExecuteWorkflowRequest,
MergeRequest,
WinOpportunityRequest,
} from "@mohsinonxrm/dataverse-sdk-messages";
import { OrganizationService } from "@mohsinonxrm/dataverse-sdk-xrm";
const org = new OrganizationService(client);
// Platform messages (1,356+ available)
const whoAmI = await org.execute(new WhoAmIRequest());
console.log("User ID:", whoAmI.result.UserId);
// Execute workflow
const workflowRequest = new ExecuteWorkflowRequest(
workflowId,
recordId,
{} // Input arguments
);
const workflowResponse = await org.execute(workflowRequest);
// Merge records
const mergeRequest = new MergeRequest(
{ logicalName: "account", id: targetAccountId },
{ logicalName: "account", id: subordinateAccountId },
{ name: "Merged Account Name" },
false // PerformParentingChecks
);
await org.execute(mergeRequest);import { MetadataClient, StringAttributeMetadataCreate } from "@mohsinonxrm/dataverse-sdk-metadata";
const metadata = new MetadataClient(client);
// Create custom entity
await metadata.createEntity({
"@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata",
SchemaName: "new_CustomEntity",
DisplayName: {
LocalizedLabels: [{ Label: "Custom Entity", LanguageCode: 1033 }],
},
PrimaryNameAttribute: "new_name",
Attributes: [
/* ... */
],
});
// Create string attribute
const attribute: StringAttributeMetadataCreate = {
"@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata",
SchemaName: "new_Description",
MaxLength: 500,
DisplayName: {
LocalizedLabels: [{ Label: "Description", LanguageCode: 1033 }],
},
};
await metadata.createAttribute("new_custentity", attribute);import { FileColumnClient } from "@mohsinonxrm/dataverse-sdk-files";
const fileClient = new FileColumnClient(client);
// Upload with progress
await fileClient.upload(
"accounts",
accountId,
"new_logo",
logoFile, // Blob or Buffer
{
chunkSize: 4 * 1024 * 1024, // 4MB chunks
onProgress: (uploaded, total) => {
console.log(`Upload: ${Math.round((uploaded / total) * 100)}%`);
},
}
);
// Download
const file = await fileClient.download("accounts", accountId, "new_logo");The SDK includes production-grade middleware:
- AuthMiddleware: Automatic token acquisition and refresh
- ODataHeadersMiddleware: Standard OData headers (Accept, OData-Version, OData-MaxVersion)
- RetryMiddleware: Retries 429 (throttling) and โฅ502 (transient failures) with exponential backoff + jitter
-
ConcurrencyMiddleware: Adaptive concurrency using
x-ms-dop-hintfrom Dataverse - TelemetryMiddleware: Request/response tracking
- LoggingMiddleware: Structured logging
import { Middleware } from "@mohsinonxrm/dataverse-sdk-core";
const customMiddleware: Middleware = {
name: "CustomMiddleware",
async execute(request, next) {
console.log(`Request: ${request.method} ${request.url}`);
const response = await next(request);
console.log(`Response: ${response.status}`);
return response;
},
};
const client = new DataverseClient({
baseUrl: "https://yourorg.crm.dynamics.com",
tokenProvider,
middleware: [customMiddleware],
});import { ApplicationInsightsTelemetryClient } from "@mohsinonxrm/dataverse-sdk-appinsights";
import { ApplicationInsights } from "@azure/monitor-opentelemetry";
const appInsights = new ApplicationInsights({
connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING,
});
const client = new DataverseClient({
baseUrl: "https://yourorg.crm.dynamics.com",
tokenProvider,
telemetryClient: new ApplicationInsightsTelemetryClient(appInsights),
});import { OpenTelemetryClient } from "@mohsinonxrm/dataverse-sdk-otel";
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("dataverse-sdk");
const client = new DataverseClient({
baseUrl: "https://yourorg.crm.dynamics.com",
tokenProvider,
telemetryClient: new OpenTelemetryClient(tracer),
});Generate strongly-typed entity classes from your Dataverse metadata:
# Install generator
npm install -D @mohsinonxrm/dataverse-sdk-generator
# Initialize configuration
npx dataverse-gen init
# Edit .dataverse-gen.json with your settings
# Generate entities
npx dataverse-gen generateConfiguration example (.dataverse-gen.json):
{
"serverUrl": "https://yourorg.crm.dynamics.com",
"output": "./src/generated",
"entities": ["account", "contact", "opportunity"],
"actions": true,
"functions": true,
"generateEnums": true
}Use generated entities:
import { Account } from "./generated/entities/Account";
import { Contact } from "./generated/entities/Contact";
// Type-safe entity creation
const account = new Account();
account.name = "Contoso Ltd";
account.revenue = 5000000;
const id = await account.save(client);Comprehensive sample projects demonstrating SDK capabilities:
| Sample | Description | Features |
|---|---|---|
| node-cli-devicecode | Interactive CLI with device code flow | MSAL device code, WhoAmI, basic queries |
| daemon-clientcredentials | Daemon application (no user) | Client credentials, bulk operations |
| actions-functions | Typed actions & functions | CreateMultiple, ExecuteWorkflow, WinOpportunity, file upload |
| batch-ops | $batch operations | Change sets, Content-ID refs, error handling |
| messages-sales-cs | Organization Service messages | WinOpportunity, Merge, ExecuteWorkflow |
| metadata-ops | Metadata operations | Entity/attribute creation, RetrieveMetadataChanges |
| Sample | Description | Features |
|---|---|---|
| spa-fluentui-v9 | React 18 SPA with Fluent UI v9 | MSAL React, account management, responsive UI |
- ESM-Only: All packages use ES Modules (no CommonJS)
-
Zero Legacy Dependencies: Native
fetch, no axios/request - Composable Packages: Small, focused packages that can be used independently
-
Type-Safe: Strict TypeScript with no
anyin public APIs - Middleware Pipeline: Microsoft Graph SDK-inspired extensible middleware
- Two Front Doors: HTTP-first + C#-familiar facades
dataverse-sdk-core (foundation)
โโ> auth-msal-browser
โโ> auth-msal-node
โโ> auth-azure-identity
โโ> batch
โโ> xrm
โ โโ> messages
โโ> actions
โโ> functions
โโ> metadata
โโ> files
โ โโ> actions
โโ> discovery
โโ> appinsights (optional)
โโ> otel (optional)
generator (standalone tool)
โโ> entities-runtime
- ADR-0001: Monorepo Architecture & Core SDK Design
- ADR-0002: Strict Typed Metadata
- ADR-0003: Typed Actions & Functions
- ADR-0004: Messages (Direct Exports)
- ADR-0005: File & Image Column Operations
- ADR-0006: Bulk Operations Strategy
- ADR-0007: Versioning & Release Strategy
- ADR-0008: Telemetry & Observability Strategy
The SDK includes comprehensive test coverage with 435+ tests across all 16 packages (100% passing):
- Unit Tests: Vitest-based tests for all packages with mocked HTTP responses
- Contract Tests: MSW (Mock Service Worker) for HTTP behavior validation
- Type Tests: tsd for TypeScript type correctness (metadata package)
- Integration Tests: Gated tests against real Dataverse environments (post-v1.0)
Run tests:
# All tests
pnpm test
# Specific package
pnpm --filter @mohsinonxrm/dataverse-sdk-core test
# Watch mode
pnpm --filter @mohsinonxrm/dataverse-sdk-core test:watch- โ Core HTTP pipeline with middleware
- โ MSAL authentication (browser + node)
- โ $batch operations with change sets
- โ OrganizationService facade
- โ Typed actions/functions (minimal set)
- โ Messages (1,356+ typed Organization Service messages)
- โ Metadata operations with strict typing
- โ File/image column operations (chunked)
- โ Bulk operations (CreateMultiple, UpdateMultiple, etc.)
- โ Global Discovery Service
- โ Entity generator
- โ Telemetry adapters (App Insights + OpenTelemetry)
- Expanded actions/functions coverage
- Expanded messages coverage
- Duplicate detection package
- Stream-based file operations
- Enhanced error handling and diagnostics
- Audit operations
- Async operation polling
- Circuit breaker & bulkhead patterns
- FetchXML query builder
- Relationship operations advanced patterns
We welcome contributions! See CONTRIBUTING.md for guidelines.
# Clone repository
git clone https://github.com/mohsinonxrm/dataverse-sdk-typescript.git
cd dataverse-sdk-typescript
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run tests
pnpm test
# Lint
pnpm lintdataverse-sdk-typescript/
โโโ packages/ # SDK packages
โ โโโ dataverse-sdk-core/
โ โโโ dataverse-sdk-auth-msal-node/
โ โโโ ... (16 packages total)
โโโ samples/ # Sample applications
โ โโโ node-cli-devicecode/
โ โโโ spa-fluentui-v9/
โ โโโ ... (7 samples total)
โโโ docs/ # Documentation
โ โโโ adr/ # Architecture Decision Records
โ โโโ mapping/ # API mapping docs
โ โโโ architecture/ # Technical docs
โโโ .changeset/ # Changesets for versioning
โโโ turbo.json # Turborepo configuration
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
See LICENSE for full text.
The AGPL-3.0 license ensures that:
- The SDK remains open source
- Any modifications must be shared back to the community
- Cloud services using this SDK must provide source code to users
For commercial licensing options, please contact the maintainers.
- Microsoft Graph SDK: Inspiration for middleware pipeline architecture
- scottdurow/dataverse-gen: Reference implementation for entity generation patterns
- Microsoft Dataverse Team: Comprehensive Web API documentation
- Documentation: Full API Docs
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Built with โค๏ธ for the Dataverse community