Dataverse API
Complete HTTP client for interacting with Microsoft Dataverse.
For complete TypeScript definitions, install the @pptb/types package:
bash npm install --save-dev @pptb/types
CRUD Operations
Each method accepts an optional connectionTarget parameter to specify which connection to use ('primary' | 'secondary'). Defaults to 'primary'.
dataverseAPI.create(entityLogicalName, record, connectionTarget?)
Creates a new record in the primary or secondary dataverse.
Parameters:
entityLogicalName: string Logical name of the entity
record: Record<string, unknown> Object containing the record data to create
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<CreateResult> Object containing the created record ID (id) and any returned fields
// Create account using primary connection
const accountResult = await dataverseAPI.create('account', {
name: 'Contoso Ltd',
telephone1: '555-1234',
websiteurl: 'https://contoso.com',
})
console.log('Created account:', accountResult.id)
// Create contact using secondary connection
const contactResult = await dataverseAPI.create(
'contact',
{
firstname: 'Dave',
},
'secondary',
)
console.log('Created contact in secondary connection:', contactResult.id)
dataverseAPI.retrieve(entityLogicalName, id, columns?, connectionTarget?)
Retrieve a single record.
Parameters:
entityLogicalName: string Logical name of the entity
id: string GUID of the record to retrieve
columns?: string[] Optional array of column names to retrieve (retrieves all if not specified)
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<Record<string, any>> Object representing the retrieved record
// Retrieve an account record using the primary connection
const account = await dataverseAPI.retrieve('account', accountResult.id, [
'name',
'telephone1',
'emailaddress1',
])
console.log('Account name:', account.name)
// Retrieve all fields for a contact record using the secondary connection
// Best practice to only retrieve needed columns to optimize performance
const contact = await dataverseAPI.retrieve(
'contact',
contactResult.id,
undefined,
'secondary',
)
console.log('Contact name:', contact.fullname)
dataverseAPI.update(entityLogicalName, id, record, connectionTarget?)
Update an existing record.
Parameters:
entityLogicalName: string Logical name of the entity
id: string GUID of the record to update
record: Record<string, unknown> Object containing the record data to update
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<void> Successful completion
/// Updating an account record using the primary connection
await dataverseAPI.update('account', accountResult.id, {
telephone1: '555-5678',
websiteurl: 'https://www.contoso.com',
})
/// Updating a contact record using the secondary connection
await dataverseAPI.update(
'contact',
contactResult.id,
{ firstname: 'David', lastname: 'Smith' },
'secondary',
)
dataverseAPI.delete(entityLogicalName, id, connectionTarget?)
Deletes a record.
Parameters:
entityLogicalName: string Logical name of the entity
id: string GUID of the record to delete
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<void> Successful completion
/// Deleting an account record using the primary connection
await dataverseAPI.delete('account', 'e15a8347-f958-4c20-b964-a8d7105f645f')
/// Deleting a contact record using the secondary connection
await dataverseAPI.delete(
'contact',
'e15a8347-f958-4f20-b964-a8d7105f645f',
'secondary',
)
dataverseAPI.createMultiple(entityLogicalName, records, connectionTarget?)
Creates multiple records in Dataverse
Parameters:
entityLogicalName Logical name of the entity
records Array of record data to create, each including the "@odata.type" property
connectionTarget Optional connection target for multi-connection tools ('primary' or 'secondary').
Defaults to 'primary'.
Returns: Promise<string[]> Array of strings representing the created record IDs
const results = await dataverseAPI.createMultiple('account', [
{ name: 'Contoso Ltd', '@odata.type': 'Microsoft.Dynamics.CRM.account' },
{ name: 'Fabrikam Inc', '@odata.type': 'Microsoft.Dynamics.CRM.account' },
])
dataverseAPI.updateMultiple(entityLogicalName, records, connectionTarget?)
Updates multiple records in Dataverse
Parameters:
entityLogicalName Logical name of the entity
records Array of record data to update, each including the "id" property and the "@odata.type" property
connectionTarget Optional connection target for multi-connection tools ('primary' or 'secondary').
Defaults to 'primary'.
Returns: Promise<void> Successful completion
await dataverseAPI.updateMultiple('account', [
{
accountid: 'guid-1',
name: 'Updated Name 1',
'@odata.type': 'Microsoft.Dynamics.CRM.account',
},
{
accountid: 'guid-2',
name: 'Updated Name 2',
'@odata.type': 'Microsoft.Dynamics.CRM.account',
},
])
Relationship Associations
dataverseAPI.associate(primaryEntityName, primaryEntityId, relationshipName, relatedEntityName, relatedEntityId, connectionTarget?)
Associate two records in a many-to-many relationship.
Parameters:
primaryEntityName: string Logical name of the primary entity (e.g., 'systemuser', 'team')
primaryEntityId: string GUID of the primary record
relationshipName: string Logical name of the N-to-N relationship (e.g., 'systemuserroles_association', 'teammembership_association')
relatedEntityName: string Logical name of the related entity (e.g., 'role', 'systemuser')
relatedEntityId: string GUID of the related record
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<void> Successful completion
// Assign a security role to a user
await dataverseAPI.associate(
'systemuser',
'user-guid-here',
'systemuserroles_association',
'role',
'role-guid-here',
)
// Add a user to a team
await dataverseAPI.associate(
'team',
'team-guid-here',
'teammembership_association',
'systemuser',
'user-guid-here',
)
// Multi-connection tool using secondary connection
await dataverseAPI.associate(
'systemuser',
'user-guid',
'systemuserroles_association',
'role',
'role-guid',
'secondary',
)
dataverseAPI.disassociate(primaryEntityName, primaryEntityId, relationshipName, relatedEntityId, connectionTarget?)
Disassociate two records in a many-to-many relationship.
Parameters:
primaryEntityName: string Logical name of the primary entity (e.g., 'systemuser', 'team')
primaryEntityId: string GUID of the primary record
relationshipName: string Logical name of the N-to-N relationship (e.g., 'systemuserroles_association', 'teammembership_association')
relatedEntityId: string GUID of the related record to disassociate
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<void> Successful completion
// Remove a security role from a user
await dataverseAPI.disassociate(
'systemuser',
'user-guid-here',
'systemuserroles_association',
'role-guid-here',
)
// Remove a user from a team
await dataverseAPI.disassociate(
'team',
'team-guid-here',
'teammembership_association',
'user-guid-here',
)
// Multi-connection tool using secondary connection
await dataverseAPI.disassociate(
'systemuser',
'user-guid',
'systemuserroles_association',
'role-guid',
'secondary',
)
Queries
dataverseAPI.fetchXmlQuery(fetchXml, connectionTarget?)
Execute a FetchXML query.
Parameters:
fetchXml: string FetchXML query string
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection
Returns: Promise<FetchXmlResult> Object with value array containing query results, odata context and paging cookie
FetchXmlResult Type:
value: Record<string, unknown>[] Array of records returned by the query
@odata.context: string OData context URL
@Microsoft.Dynamics.CRM.fetchxmlpagingcookie?: string Paging cookie for retrieving additional pages
const fetchXml = `
<fetch top="10">
<entity name="account">
<attribute name="name" />
<attribute name="accountid" />
<filter>
<condition attribute="statecode" operator="eq" value="0" />
</filter>
<order attribute="name" />
</entity>
</fetch>
`
const result = await dataverseAPI.fetchXmlQuery(fetchXml)
result.value.forEach((account) => {
console.log('Account:', account.name)
})
dataverseAPI.retrieveMultiple(fetchXml, connectionTarget?)
Alias of fetchXmlQuery() for backward compatibility.
Parameters:
fetchXml: string FetchXML query string
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<FetchXmlResult> Object with value array containing query results
const result = await dataverseAPI.retrieveMultiple(fetchXml)
console.log(`Found ${result.value.length} records`)
dataverseAPI.queryData(odataQuery, connectionTarget?)
Retrieve multiple records with OData query options.
Parameters:
odataQuery: string OData query string with parameters like $select, $filter, $orderby, $top, $skip, $expand
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<{ value: Record<string, unknown>[] }> Object with value array containing query results
// Get top 10 active accounts with specific fields
const result = await dataverseAPI.queryData(
'accounts?$select=name,emailaddress1,telephone1&$filter=statecode eq 0&$orderby=name&$top=10',
)
console.log(`Found ${result.value.length} records`)
result.value.forEach((record) => {
console.log(`${record.name} - ${record.emailaddress1}`)
})
// Query with expand to include related records
const result = await dataverseAPI.queryData(
'accounts?$select=name,accountid&$expand=contact_customer_accounts($select=fullname,emailaddress1)&$top=5',
)
// Simple query with just a filter
const result = await dataverseAPI.queryData(
`contacts?$filter=contains(fullname, 'Smith')&$top=20`,
)
// Multi-connection tool using secondary connection
const result = await dataverseAPI.queryData(
'contacts?$filter=statecode eq 0',
'secondary',
)
Metadata
dataverseAPI.getEntityMetadata(entityLogicalName, searchByLogicalName, selectColumns?, connectionTarget?)
Get entity metadata.
Parameters:
entityLogicalName: string Logical name or entity id of the entity
searchByLogicalName: boolean Boolean indicating whether to search by logical name (true) or metadata ID (false)
selectColumns?: string[] Optional array of column names to retrieve (retrieves all if not specified)
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<EntityMetadata> Object containing entity metadata
const metadata = await dataverseAPI.getEntityMetadata('account', true, [
'LogicalName',
'DisplayName',
'EntitySetName',
])
console.log('Logical Name:', metadata.LogicalName)
console.log('Display Name:', metadata.DisplayName?.LocalizedLabels[0]?.Label)
// Get entity metadata by metadata ID
const metadata = await dataverseAPI.getEntityMetadata(
'00000000-0000-0000-0000-000000000001',
false,
['LogicalName', 'DisplayName'],
)
console.log('Entity Metadata ID:', metadata.MetadataId)
console.log('Logical Name:', metadata.LogicalName)
console.log('Display Name:', metadata.DisplayName?.LocalizedLabels[0]?.Label)
// Multi-connection tool using secondary connection
const metadata = await dataverseAPI.getEntityMetadata(
'account',
true,
['LogicalName'],
'secondary',
)
console.log('Logical Name from secondary connection:', metadata.LogicalName)
dataverseAPI.getEntityRelatedMetadata(entityLogicalName, relatedPath, selectColumns?, connectionTarget?)
Get related metadata for a specific entity (attributes, relationships, etc.)
Parameters:
entityLogicalName: string Logical name of the entity
relatedPath: EntityRelatedMetadataPath Path after EntityDefinitions(LogicalName='name'). Supports collections and specific records, e.g. Attributes, Keys, ManyToOneRelationships, Attributes(LogicalName='name'), Attributes(LogicalName='industrycode')/OptionSet
selectColumns?: string[] Optional array of column names to retrieve (retrieves all if not specified)
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<EntityRelatedMetadataResponse<P>> Returns either a collection ({ value: [...] }) or a single metadata object depending on relatedPath
// Get all attributes for an entity
const attributes = await dataverseAPI.getEntityRelatedMetadata(
'account',
'Attributes',
)
console.log('Attributes:', attributes.value)
// Get specific attributes with select
const attributes = await dataverseAPI.getEntityRelatedMetadata(
'account',
'Attributes',
['LogicalName', 'DisplayName', 'AttributeType'],
)
console.log('Filtered attributes:', attributes.value)
// Get one-to-many relationships
const relationships = await dataverseAPI.getEntityRelatedMetadata(
'account',
'OneToManyRelationships',
)
console.log('One-to-many relationships:', relationships.value)
// Get a single attribute definition (returns an object)
const nameAttribute = await dataverseAPI.getEntityRelatedMetadata(
'account',
"Attributes(LogicalName='name')",
)
console.log('Attribute type:', nameAttribute.AttributeType)
// Multi-connection tool using secondary connection
const attributes = await dataverseAPI.getEntityRelatedMetadata(
'account',
'Attributes',
['LogicalName'],
'secondary',
)
console.log('Attributes from secondary connection:', attributes.value)
dataverseAPI.getAllEntitiesMetadata(selectColumns?, connectionTarget?)
Get metadata for all entities
Parameters:
selectColumns?: string[] Optional array of column names to retrieve (retrieves LogicalName, DisplayName, MetadataId by default)
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<{ value: any[] }> Object with value array containing all entity metadata
const allEntities = await dataverseAPI.getAllEntitiesMetadata([
'LogicalName',
'DisplayName',
'EntitySetName',
])
console.log(`Total entities: ${allEntities.value.length}`)
allEntities.value.forEach((entity) => {
console.log(
`${entity.LogicalName} - ${entity.DisplayName?.LocalizedLabels[0]?.Label}`,
)
})
// Multi-connection tool using secondary connection
const allEntities = await dataverseAPI.getAllEntitiesMetadata(
['LogicalName'],
'secondary',
)
dataverseAPI.getEntitySetName(logicalName)
Get the entity set name for a given logical name. No connectionTarget needed. This will work in most scenarios but if you have custom entities with non-standard pluralization you may need to retrieve the metadata instead.
Parameters:
logicalName: string Logical name of the entity
Returns: Promise<string> Entity set name as a string
const tableSetName = await dataverseAPI.getEntitySetName('contact')
console.log('Entity Set Name for contact:', tableSetName) // Outputs: contacts
CSDL Metadata Document
dataverseAPI.getCSDLDocument(connectionTarget?)
Retrieve the complete CSDL/EDMX metadata document for the Dataverse environment.
Returns the full OData service document as raw XML containing comprehensive metadata for all entities, actions, functions, and complex types in the environment. The response is automatically compressed with gzip during transfer and decompressed transparently.
Parameters:
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<string> Raw CSDL/EDMX XML document (typically 1-5MB)
What's included in the CSDL document:
- EntityType definitions (tables/entities)
- Property elements (attributes/columns)
- NavigationProperty elements (relationships)
- ComplexType definitions (return types for actions/functions)
- EnumType definitions (picklist/choice enumerations)
- Action definitions (OData Actions - POST operations)
- Function definitions (OData Functions - GET operations)
- EntityContainer metadata
// Get the CSDL metadata document
const csdlXml = await dataverseAPI.getCSDLDocument()
// Parse it using DOMParser
const parser = new DOMParser()
const xmlDoc = parser.parseFromString(csdlXml, 'text/xml')
// Example 1: Extract all custom actions
// Actions are defined with <Action> elements in the schema
const actions = xmlDoc.querySelectorAll('Action')
const customActions = []
actions.forEach((action) => {
const actionName = action.getAttribute('Name')
const isBound = action.getAttribute('IsBound') === 'true'
// Get parameters
const parameters = []
action.querySelectorAll('Parameter').forEach((param) => {
parameters.push({
name: param.getAttribute('Name'),
type: param.getAttribute('Type'),
nullable: param.getAttribute('Nullable') !== 'false',
})
})
// Get return type
const returnType = action.querySelector('ReturnType')
customActions.push({
name: actionName,
isBound,
parameters,
returnType: returnType ? returnType.getAttribute('Type') : null,
})
})
console.log('Found', customActions.length, 'actions')
customActions.forEach((action) => {
console.log(`- ${action.name} (${action.isBound ? 'Bound' : 'Unbound'})`)
action.parameters.forEach((param) => {
console.log(` Parameter: ${param.name} (${param.type})`)
})
if (action.returnType) {
console.log(` Returns: ${action.returnType}`)
}
})
// Example 2: Extract all functions
// Functions are defined with <Function> elements in the schema
const functions = xmlDoc.querySelectorAll('Function')
const customFunctions = []
functions.forEach((func) => {
const funcName = func.getAttribute('Name')
const isBound = func.getAttribute('IsBound') === 'true'
const isComposable = func.getAttribute('IsComposable') === 'true'
// Get parameters
const parameters = []
func.querySelectorAll('Parameter').forEach((param) => {
parameters.push({
name: param.getAttribute('Name'),
type: param.getAttribute('Type'),
nullable: param.getAttribute('Nullable') !== 'false',
})
})
// Get return type
const returnType = func.querySelector('ReturnType')
customFunctions.push({
name: funcName,
isBound,
isComposable,
parameters,
returnType: returnType ? returnType.getAttribute('Type') : null,
})
})
console.log('Found', customFunctions.length, 'functions')
customFunctions.forEach((func) => {
console.log(`- ${func.name} (${func.isBound ? 'Bound' : 'Unbound'})`)
func.parameters.forEach((param) => {
console.log(` Parameter: ${param.name} (${param.type})`)
})
if (func.returnType) {
console.log(` Returns: ${func.returnType}`)
}
})
// Example 3: Find a specific action by name
function findAction(xmlDoc, actionName) {
const actions = xmlDoc.querySelectorAll('Action')
for (const action of actions) {
if (action.getAttribute('Name') === actionName) {
return {
name: actionName,
isBound: action.getAttribute('IsBound') === 'true',
parameters: Array.from(action.querySelectorAll('Parameter')).map(
(p) => ({
name: p.getAttribute('Name'),
type: p.getAttribute('Type'),
nullable: p.getAttribute('Nullable') !== 'false',
}),
),
returnType: action.querySelector('ReturnType')?.getAttribute('Type'),
}
}
}
return null
}
// Search for a common Dataverse action like GrantAccess
const grantAccessAction = findAction(xmlDoc, 'GrantAccess')
if (grantAccessAction) {
console.log('GrantAccess action details:', grantAccessAction)
console.log('Parameters:', grantAccessAction.parameters)
}
// You can also search for functions using a similar pattern
function findFunction(xmlDoc, functionName) {
const functions = xmlDoc.querySelectorAll('Function')
for (const func of functions) {
if (func.getAttribute('Name') === functionName) {
return {
name: functionName,
isBound: func.getAttribute('IsBound') === 'true',
isComposable: func.getAttribute('IsComposable') === 'true',
parameters: Array.from(func.querySelectorAll('Parameter')).map((p) => ({
name: p.getAttribute('Name'),
type: p.getAttribute('Type'),
nullable: p.getAttribute('Nullable') !== 'false',
})),
returnType: func.querySelector('ReturnType')?.getAttribute('Type'),
}
}
}
return null
}
// WhoAmI is a function, not an action
const whoAmIFunction = findFunction(xmlDoc, 'WhoAmI')
if (whoAmIFunction) {
console.log('WhoAmI function details:', whoAmIFunction)
}
// Example 4: Extract entity type definitions
const entityTypes = xmlDoc.querySelectorAll('EntityType')
console.log('Found', entityTypes.length, 'entity types')
entityTypes.forEach((entityType) => {
const name = entityType.getAttribute('Name')
// Get properties (columns)
const properties = []
entityType.querySelectorAll('Property').forEach((prop) => {
properties.push({
name: prop.getAttribute('Name'),
type: prop.getAttribute('Type'),
nullable: prop.getAttribute('Nullable') !== 'false',
})
})
// Get navigation properties (relationships)
const navProps = []
entityType.querySelectorAll('NavigationProperty').forEach((nav) => {
navProps.push({
name: nav.getAttribute('Name'),
type: nav.getAttribute('Type'),
partner: nav.getAttribute('Partner'),
})
})
console.log(`Entity: ${name}`)
console.log(` Properties: ${properties.length}`)
console.log(` Navigation Properties: ${navProps.length}`)
})
// Example 5: Extract complex types (return types for actions/functions)
const complexTypes = xmlDoc.querySelectorAll('ComplexType')
console.log('Found', complexTypes.length, 'complex types')
complexTypes.forEach((complexType) => {
const name = complexType.getAttribute('Name')
const properties = []
complexType.querySelectorAll('Property').forEach((prop) => {
properties.push({
name: prop.getAttribute('Name'),
type: prop.getAttribute('Type'),
})
})
console.log(`ComplexType: ${name}`)
properties.forEach((prop) => {
console.log(` ${prop.name}: ${prop.type}`)
})
})
// Example 6: Find all actions that return a specific type
function findActionsByReturnType(xmlDoc, returnType) {
const actions = xmlDoc.querySelectorAll('Action')
const matchingActions = []
actions.forEach((action) => {
const actionReturnType = action.querySelector('ReturnType')
if (
actionReturnType &&
actionReturnType.getAttribute('Type').includes(returnType)
) {
matchingActions.push(action.getAttribute('Name'))
}
})
return matchingActions
}
const actionsReturningGuid = findActionsByReturnType(xmlDoc, 'Edm.Guid')
console.log('Actions returning Guid:', actionsReturningGuid)
Pro Tip: The CSDL document is large (1-5MB). Consider caching it if you
need to reference it multiple times, or use specific metadata endpoints like
getEntityMetadata() or getEntityRelatedMetadata() for targeted queries.
XML Namespaces: The CSDL document uses XML namespaces. When querying, you
may need to handle namespaces appropriately. The examples above use simple
querySelectorAll() which works for element names, but for more complex
queries, consider using namespace-aware XML parsing.
dataverseAPI.getSolutions(selectColumns, connectionTarget?)
Get solutions from the environment
Parameters:
selectColumns: string[] Required array of column names to retrieve (must contain at least one column)
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<{ value: any[] }> Object with value array containing solutions
const solutions = await dataverseAPI.getSolutions([
'solutionid',
'uniquename',
'friendlyname',
'version',
'ismanaged',
])
console.log(`Total solutions: ${solutions.value.length}`)
solutions.value.forEach((solution) => {
console.log(
`${solution.friendlyname} (${solution.uniquename}) - v${solution.version}`,
)
})
// Multi-connection tool using secondary connection
const solutions = await dataverseAPI.getSolutions(['uniquename'], 'secondary')
Helper Methods
Power Platform ToolBox provides comprehensive metadata operations to programmatically create, read, update, and delete Dataverse schema elements including entities, attributes, relationships, and option sets.
Important: Always call dataverseAPI.publishCustomizations() after making
metadata changes to apply them to your environment.
dataverseAPI.buildLabel(text, languageCode?)
Build a properly formatted Label object for use in metadata operations.
Parameters:
text: string The label text
languageCode?: number Optional language code (defaults to 1033 for English)
Returns: Label Properly formatted label object
// Create a simple label (English by default)
const label = dataverseAPI.buildLabel('Customer Name')
// Create label with specific language code
const labelFrench = dataverseAPI.buildLabel('Nom du client', 1036)
// Label structure returned:
// {
// LocalizedLabels: [{ Label: "Customer Name", LanguageCode: 1033, IsManaged: false }],
// UserLocalizedLabel: { Label: "Customer Name", LanguageCode: 1033, IsManaged: false }
// }
dataverseAPI.getAttributeODataType(attributeType)
Get the OData type name for a given attribute type. Useful when creating attribute definitions.
Parameters:
attributeType: AttributeMetadataType Attribute type enum value
Returns: string OData type name (e.g., 'Microsoft.Dynamics.CRM.StringAttributeMetadata')
const odataType = dataverseAPI.getAttributeODataType(
DataverseAPI.AttributeMetadataType.String,
)
console.log(odataType) // Output: "Microsoft.Dynamics.CRM.StringAttributeMetadata"
const lookupType = dataverseAPI.getAttributeODataType(
DataverseAPI.AttributeMetadataType.Lookup,
)
console.log(lookupType) // Output: "Microsoft.Dynamics.CRM.LookupAttributeMetadata"
Entity Operations
dataverseAPI.createEntityDefinition(entityDefinition, options?, connectionTarget?)
Create a new entity (table) in Dataverse.
Parameters:
entityDefinition: object Entity metadata definition
options?: object Optional settings (e.g., { solutionUniqueName: "MySolution" })
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<{ id: string }> Object with the new entity's MetadataId
// Create a custom entity
const newEntity = await dataverseAPI.createEntityDefinition(
{
'@odata.type': 'Microsoft.Dynamics.CRM.EntityMetadata',
LogicalName: 'new_project',
DisplayName: dataverseAPI.buildLabel('Project'),
DisplayCollectionName: dataverseAPI.buildLabel('Projects'),
Description: dataverseAPI.buildLabel('Custom project tracking entity'),
OwnershipType: 'UserOwned', // UserOwned, TeamOwned, OrganizationOwned, None
IsActivity: false,
HasActivities: true,
HasNotes: true,
Attributes: [
{
'@odata.type': 'Microsoft.Dynamics.CRM.StringAttributeMetadata',
SchemaName: 'new_Name',
DisplayName: dataverseAPI.buildLabel('Project Name'),
RequiredLevel: { Value: 'ApplicationRequired' },
MaxLength: 100,
FormatName: { Value: 'Text' },
},
],
},
{ solutionUniqueName: 'MyCustomSolution' },
)
console.log('Created entity with ID:', newEntity.id)
// Publish to make it available
await dataverseAPI.publishCustomizations('new_project')
dataverseAPI.updateEntityDefinition(entityIdentifier, entityDefinition, options?, connectionTarget?)
Update an existing entity's metadata.
Parameters:
entityIdentifier: string Entity MetadataId or LogicalName
entityDefinition: object Updated entity metadata
options?: object Optional settings (e.g., { mergeLabels: true })
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<void>
// Update entity display name and description
await dataverseAPI.updateEntityDefinition(
'new_project',
{
DisplayName: dataverseAPI.buildLabel('Project Management'),
Description: dataverseAPI.buildLabel(
'Enhanced project tracking and management',
),
HasNotes: false, // Disable notes
},
{ mergeLabels: true },
)
await dataverseAPI.publishCustomizations('new_project')
dataverseAPI.deleteEntityDefinition(entityIdentifier, connectionTarget?)
Delete an entity from Dataverse.
Parameters:
entityIdentifier: string Entity MetadataId or LogicalName
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<void>
// Delete an entity - WARNING: This is permanent!
await dataverseAPI.deleteEntityDefinition('new_project')
// No need to publish after deletion - takes effect immediately
Attribute Operations
dataverseAPI.createAttribute(entityLogicalName, attributeDefinition, options?, connectionTarget?)
Create a new attribute (column) on an entity.
Parameters:
entityLogicalName: string Logical name of the entity
attributeDefinition: object Attribute metadata definition
options?: object Optional settings (e.g., { solutionUniqueName: "MySolution" })
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<{ id: string }> Object with the new attribute's MetadataId
// Create a text field
const textAttribute = await dataverseAPI.createAttribute('new_project', {
'@odata.type': dataverseAPI.getAttributeODataType(
DataverseAPI.AttributeMetadataType.String,
),
SchemaName: 'new_Description',
DisplayName: dataverseAPI.buildLabel('Description'),
Description: dataverseAPI.buildLabel('Project description'),
RequiredLevel: { Value: 'None' }, // None, ApplicationRequired, SystemRequired
MaxLength: 2000,
FormatName: { Value: 'TextArea' }, // Text, TextArea, Email, Url, etc.
})
// Create a whole number field
const numberAttribute = await dataverseAPI.createAttribute('new_project', {
'@odata.type': dataverseAPI.getAttributeODataType(
DataverseAPI.AttributeMetadataType.Integer,
),
SchemaName: 'new_EstimatedHours',
DisplayName: dataverseAPI.buildLabel('Estimated Hours'),
RequiredLevel: { Value: 'None' },
MinValue: 0,
MaxValue: 10000,
Format: 'None', // None, Duration, Locale, TimeZone, Language
})
// Create a decimal field
const decimalAttribute = await dataverseAPI.createAttribute('new_project', {
'@odata.type': dataverseAPI.getAttributeODataType(
DataverseAPI.AttributeMetadataType.Decimal,
),
SchemaName: 'new_Budget',
DisplayName: dataverseAPI.buildLabel('Budget'),
RequiredLevel: { Value: 'None' },
MinValue: 0,
MaxValue: 1000000000,
Precision: 2,
})
// Create a date field
const dateAttribute = await dataverseAPI.createAttribute('new_project', {
'@odata.type': dataverseAPI.getAttributeODataType(
DataverseAPI.AttributeMetadataType.DateTime,
),
SchemaName: 'new_StartDate',
DisplayName: dataverseAPI.buildLabel('Start Date'),
RequiredLevel: { Value: 'None' },
Format: 'DateOnly', // DateOnly, DateAndTime
})
// Create a choice (option set) field - local
const choiceAttribute = await dataverseAPI.createAttribute('new_project', {
'@odata.type': dataverseAPI.getAttributeODataType(
DataverseAPI.AttributeMetadataType.Picklist,
),
SchemaName: 'new_Priority',
DisplayName: dataverseAPI.buildLabel('Priority'),
RequiredLevel: { Value: 'ApplicationRequired' },
OptionSet: {
'@odata.type': 'Microsoft.Dynamics.CRM.OptionSetMetadata',
IsGlobal: false,
OptionSetType: 'Picklist',
Options: [
{
Value: 1,
Label: dataverseAPI.buildLabel('Low'),
},
{
Value: 2,
Label: dataverseAPI.buildLabel('Medium'),
},
{
Value: 3,
Label: dataverseAPI.buildLabel('High'),
},
],
},
})
// Create a lookup field (single entity)
const lookupAttribute = await dataverseAPI.createAttribute('new_project', {
'@odata.type': dataverseAPI.getAttributeODataType(
DataverseAPI.AttributeMetadataType.Lookup,
),
SchemaName: 'new_AccountId',
DisplayName: dataverseAPI.buildLabel('Account'),
RequiredLevel: { Value: 'None' },
Targets: ['account'], // Entity types this lookup can reference
})
await dataverseAPI.publishCustomizations('new_project')
dataverseAPI.updateAttribute(entityLogicalName, attributeIdentifier, attributeDefinition, options?, connectionTarget?)
Update an existing attribute's metadata.
Parameters:
entityLogicalName: string Logical name of the entity
attributeIdentifier: string Attribute MetadataId or LogicalName
attributeDefinition: object Updated attribute metadata
options?: object Optional settings (e.g., { mergeLabels: true })
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<void>
// Update attribute display name and requirement level
await dataverseAPI.updateAttribute(
'new_project',
'new_description',
{
DisplayName: dataverseAPI.buildLabel('Project Details'),
RequiredLevel: { Value: 'ApplicationRequired' },
MaxLength: 4000, // Increase max length
},
{ mergeLabels: true },
)
await dataverseAPI.publishCustomizations('new_project')
dataverseAPI.deleteAttribute(entityLogicalName, attributeIdentifier, connectionTarget?)
Delete an attribute from an entity.
Parameters:
entityLogicalName: string Logical name of the entity
attributeIdentifier: string Attribute MetadataId or LogicalName
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<void>
// Delete an attribute - WARNING: This is permanent!
await dataverseAPI.deleteAttribute('new_project', 'new_description')
// Publish to complete the deletion
await dataverseAPI.publishCustomizations('new_project')
Polymorphic Lookup Attributes
dataverseAPI.createPolymorphicLookupAttribute(entityLogicalName, attributeDefinition, options?, connectionTarget?)
Create a polymorphic lookup attribute that can reference multiple entity types (e.g., Customer field that can reference both Account and Contact).
Parameters:
entityLogicalName: string Logical name of the entity
attributeDefinition: object Lookup attribute metadata with Targets array
options?: object Optional settings
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<{ AttributeId: string }> Object with the new attribute's ID
// Create a Customer lookup (Account or Contact)
const customerLookup = await dataverseAPI.createPolymorphicLookupAttribute(
'new_order',
{
SchemaName: 'new_CustomerId',
DisplayName: dataverseAPI.buildLabel('Customer'),
Description: dataverseAPI.buildLabel('The customer for this order'),
RequiredLevel: { Value: 'ApplicationRequired' },
Targets: ['account', 'contact'], // Can reference both entities
},
)
// Create a Regarding lookup for notes (multiple custom entities)
const regardingLookup = await dataverseAPI.createPolymorphicLookupAttribute(
'new_note',
{
SchemaName: 'new_RegardingId',
DisplayName: dataverseAPI.buildLabel('Regarding'),
RequiredLevel: { Value: 'None' },
Targets: ['new_project', 'new_task', 'new_milestone'],
},
)
await dataverseAPI.publishCustomizations()
Alternative: For customer lookups specifically, you can use the
CreateCustomerRelationships action via dataverseAPI.execute() which
creates both the lookup and relationships in a single operation.
Relationship Operations
dataverseAPI.createRelationship(relationshipDefinition, options?, connectionTarget?)
Create a new entity relationship (1:N or N:N).
Parameters:
relationshipDefinition: object Relationship metadata definition
options?: object Optional settings
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<{ id: string }> Object with the new relationship's MetadataId
// Create a One-to-Many (1:N) relationship
// Account (1) -> Projects (N)
const oneToManyRelationship = await dataverseAPI.createRelationship({
'@odata.type': 'Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata',
SchemaName: 'new_account_project',
ReferencedEntity: 'account', // The "One" side
ReferencedAttribute: 'accountid',
ReferencingEntity: 'new_project', // The "Many" side
Lookup: {
'@odata.type': dataverseAPI.getAttributeODataType(
DataverseAPI.AttributeMetadataType.Lookup,
),
SchemaName: 'new_AccountId',
DisplayName: dataverseAPI.buildLabel('Account'),
RequiredLevel: { Value: 'None' },
},
CascadeConfiguration: {
Assign: 'NoCascade', // NoCascade, Cascade, Active, UserOwned
Delete: 'RemoveLink', // Cascade, RemoveLink, Restrict
Merge: 'NoCascade',
Reparent: 'NoCascade',
Share: 'NoCascade',
Unshare: 'NoCascade',
},
})
// Create a Many-to-Many (N:N) relationship
// Projects (N) <-> Users (N) for team members
const manyToManyRelationship = await dataverseAPI.createRelationship({
'@odata.type': 'Microsoft.Dynamics.CRM.ManyToManyRelationshipMetadata',
SchemaName: 'new_project_systemuser',
Entity1LogicalName: 'new_project',
Entity1IntersectAttribute: 'new_projectid',
Entity2LogicalName: 'systemuser',
Entity2IntersectAttribute: 'systemuserid',
IntersectEntityName: 'new_project_systemuser',
})
await dataverseAPI.publishCustomizations()
dataverseAPI.updateRelationship(relationshipIdentifier, relationshipDefinition, options?, connectionTarget?)
Update an existing relationship's metadata.
Parameters:
relationshipIdentifier: string Relationship MetadataId or SchemaName
relationshipDefinition: object Updated relationship metadata
options?: object Optional settings
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<void>
// Update cascade configuration for a relationship
// First retrieve the current relationship
const relationship = await dataverseAPI.queryData(
`RelationshipDefinitions(SchemaName='new_account_project')`,
)
// Update cascade delete behavior
relationship.CascadeConfiguration.Delete = 'Cascade' // Change from RemoveLink to Cascade
await dataverseAPI.updateRelationship('new_account_project', relationship, {
mergeLabels: true,
})
await dataverseAPI.publishCustomizations()
dataverseAPI.deleteRelationship(relationshipIdentifier, connectionTarget?)
Delete a relationship.
Parameters:
relationshipIdentifier: string Relationship MetadataId or SchemaName
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<void>
// Delete a relationship - WARNING: This is permanent!
await dataverseAPI.deleteRelationship('new_account_project')
await dataverseAPI.publishCustomizations()
Global Option Sets
dataverseAPI.createGlobalOptionSet(optionSetDefinition, options?, connectionTarget?)
Create a new global option set (choice) that can be shared across multiple entities.
Parameters:
optionSetDefinition: object Option set metadata definition
options?: object Optional settings
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<{ id: string }> Object with the new option set's MetadataId
// Create a global option set for project status
const globalOptionSet = await dataverseAPI.createGlobalOptionSet(
{
'@odata.type': 'Microsoft.Dynamics.CRM.OptionSetMetadata',
Name: 'new_projectstatus',
DisplayName: dataverseAPI.buildLabel('Project Status'),
Description: dataverseAPI.buildLabel('Status values for projects'),
OptionSetType: 'Picklist',
IsGlobal: true,
Options: [
{
Value: 1,
Label: dataverseAPI.buildLabel('Planning'),
Description: dataverseAPI.buildLabel('Project is in planning phase'),
},
{
Value: 2,
Label: dataverseAPI.buildLabel('In Progress'),
},
{
Value: 3,
Label: dataverseAPI.buildLabel('On Hold'),
},
{
Value: 4,
Label: dataverseAPI.buildLabel('Completed'),
},
{
Value: 5,
Label: dataverseAPI.buildLabel('Cancelled'),
},
],
},
{ solutionUniqueName: 'MyCustomSolution' },
)
console.log('Created global option set:', globalOptionSet.id)
await dataverseAPI.publishCustomizations()
// Retrieve the created option set
const optionSet = await dataverseAPI.queryData(
"GlobalOptionSetDefinitions(Name='new_projectstatus')",
)
console.log('Option set details:', optionSet)
dataverseAPI.updateGlobalOptionSet(optionSetIdentifier, optionSetDefinition, options?, connectionTarget?)
Update an existing global option set.
Parameters:
optionSetIdentifier: string Option set Name or MetadataId
optionSetDefinition: object Updated option set metadata
options?: object Optional settings
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<void>
// Update global option set display name
await dataverseAPI.updateGlobalOptionSet(
'new_projectstatus',
{
DisplayName: dataverseAPI.buildLabel('Project Lifecycle Status'),
Description: dataverseAPI.buildLabel(
'Tracks the full lifecycle of a project',
),
},
{ mergeLabels: true },
)
await dataverseAPI.publishCustomizations()
dataverseAPI.deleteGlobalOptionSet(optionSetIdentifier, connectionTarget?)
Delete a global option set.
Parameters:
optionSetIdentifier: string Option set Name or MetadataId
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<void>
// Delete a global option set - WARNING: This is permanent!
await dataverseAPI.deleteGlobalOptionSet('new_projectstatus')
await dataverseAPI.publishCustomizations()
Option Value Operations
dataverseAPI.insertOptionValue(params, connectionTarget?)
Insert a new option value into a local or global option set.
Parameters:
params: object Parameters for the option value
params.EntityLogicalName?: string Entity name (for local option sets)
params.AttributeLogicalName?: string Attribute name (for local option sets)
params.OptionSetName?: string Option set name (for global option sets)
params.Value: number Integer value for the new option
params.Label: Label Label object for the option
params.Description?: Label Optional description
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<void>
// Add option to a local option set
await dataverseAPI.insertOptionValue({
EntityLogicalName: 'new_project',
AttributeLogicalName: 'new_priority',
Value: 4,
Label: dataverseAPI.buildLabel('Critical'),
Description: dataverseAPI.buildLabel('Requires immediate attention'),
})
// Add option to a global option set
await dataverseAPI.insertOptionValue({
OptionSetName: 'new_projectstatus',
Value: 6,
Label: dataverseAPI.buildLabel('Archived'),
})
await dataverseAPI.publishCustomizations()
Status Columns: For status choice columns (statuscode), use the
InsertStatusValue action via dataverseAPI.execute() instead, which
requires a StateCode parameter.
dataverseAPI.updateOptionValue(params, connectionTarget?)
Update an existing option value.
Parameters:
params: object Parameters for the update
params.EntityLogicalName?: string Entity name (for local option sets)
params.AttributeLogicalName?: string Attribute name (for local option sets)
params.OptionSetName?: string Option set name (for global option sets)
params.Value: number Integer value to update
params.Label?: Label New label
params.Description?: Label New description
params.MergeLabels?: boolean Whether to merge or replace labels
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<Record<string, unknown>>
// Update local option set value
await dataverseAPI.updateOptionValue({
EntityLogicalName: 'new_project',
AttributeLogicalName: 'new_priority',
Value: 3,
Label: dataverseAPI.buildLabel('High Priority'),
MergeLabels: true,
})
await dataverseAPI.publishCustomizations()
dataverseAPI.deleteOptionValue(params, connectionTarget?)
Delete an option value from an option set.
Parameters:
params: object Parameters for deletion
params.EntityLogicalName?: string Entity name (for local option sets)
params.AttributeLogicalName?: string Attribute name (for local option sets)
params.OptionSetName?: string Option set name (for global option sets)
params.Value: number Integer value to delete
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<Record<string, unknown>>
// Delete option from local option set
await dataverseAPI.deleteOptionValue({
EntityLogicalName: 'new_project',
AttributeLogicalName: 'new_priority',
Value: 4,
})
await dataverseAPI.publishCustomizations()
dataverseAPI.orderOption(params, connectionTarget?)
Reorder option values in an option set.
Parameters:
params: object Parameters for reordering
params.EntityLogicalName?: string Entity name (for local option sets)
params.AttributeLogicalName?: string Attribute name (for local option sets)
params.OptionSetName?: string Option set name (for global option sets)
params.Values: number[] Array of values in the desired order
connectionTarget?: 'primary' | 'secondary' Connection target
Returns: Promise<Record<string, unknown>>
// Reorder local option set values
await dataverseAPI.orderOption({
EntityLogicalName: 'new_project',
AttributeLogicalName: 'new_priority',
Values: [3, 2, 1], // High, Medium, Low
})
await dataverseAPI.publishCustomizations()
Actions & Functions
dataverseAPI.execute(request, connectionTarget?)
Execute a custom action or function using a unified interface. Supports both bound (entity-specific) and unbound (global) operations.
Parameters:
request.entityName: string Logical name of the entity (required for bound operations)
request.entityId: string GUID of the record (required for bound operations)
request.operationName: string Name of the action/function
request.operationType: 'action' | 'function' Type of operation
request.parameters: Record<string, unknown> Operation parameters (optional)
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<Record<string, unknown>> Operation result
// Bound action - operates on a specific entity record
const boundResult = await dataverseAPI.execute({
entityName: 'systemuser',
entityId: 'user-guid',
operationName: 'SetBusinessSystemUser',
operationType: 'action',
parameters: {
BusinessUnit: 'businessunits(bu-guid)',
ReassignPrincipal: 'systemusers(user-guid)',
DoNotMoveAllRecords: true,
},
})
// Unbound function - global operation
const unboundResult = await dataverseAPI.execute({
operationName: 'WhoAmI',
operationType: 'function',
})
// Multi-connection tool using secondary connection
const unboundResultSecondary = await dataverseAPI.execute(
{
operationName: 'WhoAmI',
operationType: 'function',
},
'secondary',
)
Customizations
dataverseAPI.publishCustomizations(tableLogicalName?, connectionTarget?)
Publish customizations for the current environment.
Parameters:
tableLogicalName?: string Optional table (entity) logical name to publish. If omitted, all pending customizations are published.
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<void> Successful completion
// Publish all customizations for the primary connection
await dataverseAPI.publishCustomizations()
// Publish only the account table customizations for the secondary connection
await dataverseAPI.publishCustomizations('account', 'secondary')
dataverseAPI.deploySolution(solutionContent, options?, connectionTarget?)
Deploy (import) a Dataverse solution.
Parameters:
solutionContent: string | ArrayBuffer | ArrayBufferView Base64 solution zip or binary data
options?: { importJobId?: string; publishWorkflows?: boolean; overwriteUnmanagedCustomizations?: boolean; skipProductUpdateDependencies?: boolean; convertToManaged?: boolean } Optional import settings
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<{ ImportJobId: string }> Import job identifier for tracking status
const solutionFile = await toolboxAPI.fileSystem.readBinary('/path/to/solution.zip')
const result = await dataverseAPI.deploySolution(solutionFile, {
publishWorkflows: true,
overwriteUnmanagedCustomizations: false,
})
console.log('Solution deployment started. Import Job ID:', result.ImportJobId)
dataverseAPI.getImportJobStatus(importJobId, connectionTarget?)
Get the status of a solution import job.
Parameters:
importJobId: string Import job GUID returned by deploySolution()
connectionTarget?: 'primary' | 'secondary' Optional connection target for multi-connection tools ('primary' or 'secondary'). Defaults to 'primary'.
Returns: Promise<Record<string, unknown>> Import status details and progress data
const deployResult = await dataverseAPI.deploySolution(solutionFile)
const importJobId = deployResult.ImportJobId
const status = await dataverseAPI.getImportJobStatus(importJobId)
console.log('Import status:', status)