Dataverse API

Complete HTTP client for interacting with Microsoft Dataverse.

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)

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.

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()

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()

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)

Was this page helpful?