npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mohsinonxrm/dataverse-sdk-metadata

v1.0.0

Published

Complete TypeScript metadata management for Microsoft Dataverse - create, read, update, and delete entities, columns, relationships, and choices with full type safety.

Downloads

16

Readme

@mohsinonxrm/dataverse-sdk-metadata

Complete TypeScript metadata management for Microsoft Dataverse - create, read, update, and delete entities, columns, relationships, and choices with full type safety.

Features

  • Entity definitions - create, read, update, delete custom tables
  • Column definitions - all attribute types with strict typing:
    • Text (Single Line, Multiple Lines)
    • Numbers (Integer, Decimal, Currency)
    • Date & Time
    • Choices (Local & Global)
    • Yes/No (Boolean)
    • Lookup (Single-table & Polymorphic)
    • File & Image
  • Relationships - one-to-many (1:N), many-to-one (N:1), many-to-many (N:N)
  • Global choices (option sets) - create, update, delete global choice lists
  • Option value management - insert, update, delete, reorder choice options
  • Polymorphic lookups - customer and multi-table lookup columns
  • Relationship eligibility - check which entities can participate in relationships
  • Metadata change tracking - RetrieveMetadataChanges with version stamps
  • Strict typing - separate Read/Create/Update types, internal fields excluded
  • ESM-only - works in Node 18+ and browsers

Installation

pnpm add @mohsinonxrm/dataverse-sdk-metadata @mohsinonxrm/dataverse-sdk-core

Quick Start

import { DataverseClient } from "@mohsinonxrm/dataverse-sdk-core";
import { MetadataClient } from "@mohsinonxrm/dataverse-sdk-metadata";

const client = new DataverseClient({
  baseUrl: "https://yourorg.crm.dynamics.com",
  tokenProvider: yourTokenProvider,
});

const metadataClient = new MetadataClient(client);

// Create a custom table
const entityId = await metadataClient.createEntityDefinition({
  "@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata",
  SchemaName: "new_Project",
  DisplayName: {
    LocalizedLabels: [{ Label: "Project", LanguageCode: 1033 }],
  },
  DisplayCollectionName: {
    LocalizedLabels: [{ Label: "Projects", LanguageCode: 1033 }],
  },
  OwnershipType: 1, // UserOwned
  HasActivities: true,
  HasNotes: true,
  Attributes: [
    // Primary name column (required)
    {
      "@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata",
      SchemaName: "new_Name",
      RequiredLevel: { Value: "ApplicationRequired" },
      MaxLength: 100,
      DisplayName: {
        LocalizedLabels: [{ Label: "Name", LanguageCode: 1033 }],
      },
      IsPrimaryName: true,
    },
  ],
});

Entity Operations

Create Entity

const metadataId = await metadataClient.createEntityDefinition({
  "@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata",
  SchemaName: "new_Task",
  DisplayName: { LocalizedLabels: [{ Label: "Task", LanguageCode: 1033 }] },
  DisplayCollectionName: { LocalizedLabels: [{ Label: "Tasks", LanguageCode: 1033 }] },
  Description: { LocalizedLabels: [{ Label: "Project task tracking", LanguageCode: 1033 }] },
  OwnershipType: 1, // UserOwned
  IsActivity: false,
  HasActivities: true,
  HasNotes: true,
  Attributes: [
    {
      "@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata",
      SchemaName: "new_Name",
      DisplayName: { LocalizedLabels: [{ Label: "Task Name", LanguageCode: 1033 }] },
      RequiredLevel: { Value: "ApplicationRequired" },
      MaxLength: 100,
      IsPrimaryName: true,
    },
  ],
});

Read Entities

// Get all custom entities
const customEntities = await metadataClient.getEntityDefinitions({
  filter: "IsCustomEntity eq true",
  select: ["LogicalName", "DisplayName", "SchemaName"],
});

// Get specific entity with attributes expanded
const projectEntity = await metadataClient.getEntityDefinition("new_project", {
  expand: "Attributes",
});

Update Entity

// Update entity display name
await metadataClient.updateEntityDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata",
  DisplayName: { LocalizedLabels: [{ Label: "Project (Updated)", LanguageCode: 1033 }] },
  Description: { LocalizedLabels: [{ Label: "Manage projects and tasks", LanguageCode: 1033 }] },
});

Delete Entity

await metadataClient.deleteEntityDefinition("new_obsoleteentity");

Column (Attribute) Operations

Text Columns

Single Line of Text

await metadataClient.createAttributeDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata",
  SchemaName: "new_ProjectCode",
  DisplayName: { LocalizedLabels: [{ Label: "Project Code", LanguageCode: 1033 }] },
  RequiredLevel: { Value: "None" },
  MaxLength: 50,
  Format: "Text",
});

Multiple Lines of Text

await metadataClient.createAttributeDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.MemoAttributeMetadata",
  SchemaName: "new_Description",
  DisplayName: { LocalizedLabels: [{ Label: "Description", LanguageCode: 1033 }] },
  RequiredLevel: { Value: "None" },
  MaxLength: 2000,
  Format: "TextArea",
});

Number Columns

Whole Number

await metadataClient.createAttributeDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.IntegerAttributeMetadata",
  SchemaName: "new_Priority",
  DisplayName: { LocalizedLabels: [{ Label: "Priority", LanguageCode: 1033 }] },
  RequiredLevel: { Value: "None" },
  MinValue: 1,
  MaxValue: 100,
  Format: "None",
});

Decimal Number

await metadataClient.createAttributeDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.DecimalAttributeMetadata",
  SchemaName: "new_CompletionPercentage",
  DisplayName: { LocalizedLabels: [{ Label: "Completion %", LanguageCode: 1033 }] },
  RequiredLevel: { Value: "None" },
  MinValue: 0,
  MaxValue: 100,
  Precision: 2,
});

Currency

await metadataClient.createAttributeDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.MoneyAttributeMetadata",
  SchemaName: "new_Budget",
  DisplayName: { LocalizedLabels: [{ Label: "Budget", LanguageCode: 1033 }] },
  RequiredLevel: { Value: "None" },
  MinValue: 0,
  MaxValue: 1000000000,
  Precision: 2,
  PrecisionSource: 2, // Precision = 2
});

Date & Time

await metadataClient.createAttributeDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.DateTimeAttributeMetadata",
  SchemaName: "new_DueDate",
  DisplayName: { LocalizedLabels: [{ Label: "Due Date", LanguageCode: 1033 }] },
  RequiredLevel: { Value: "None" },
  Format: "DateOnly", // or 'DateAndTime'
  ImeMode: "Disabled",
});

Choice (Local)

await metadataClient.createAttributeDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.PicklistAttributeMetadata",
  SchemaName: "new_Status",
  DisplayName: { LocalizedLabels: [{ Label: "Status", LanguageCode: 1033 }] },
  RequiredLevel: { Value: "None" },
  OptionSet: {
    "@odata.type": "Microsoft.Dynamics.CRM.OptionSetMetadata",
    IsGlobal: false,
    Options: [
      { Value: 1, Label: { LocalizedLabels: [{ Label: "Not Started", LanguageCode: 1033 }] } },
      { Value: 2, Label: { LocalizedLabels: [{ Label: "In Progress", LanguageCode: 1033 }] } },
      { Value: 3, Label: { LocalizedLabels: [{ Label: "Completed", LanguageCode: 1033 }] } },
    ],
  },
});

Yes/No (Boolean)

await metadataClient.createAttributeDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.BooleanAttributeMetadata",
  SchemaName: "new_IsActive",
  DisplayName: { LocalizedLabels: [{ Label: "Is Active", LanguageCode: 1033 }] },
  RequiredLevel: { Value: "None" },
  DefaultValue: true,
  OptionSet: {
    "@odata.type": "Microsoft.Dynamics.CRM.BooleanOptionSetMetadata",
    TrueOption: { Value: 1, Label: { LocalizedLabels: [{ Label: "Yes", LanguageCode: 1033 }] } },
    FalseOption: { Value: 0, Label: { LocalizedLabels: [{ Label: "No", LanguageCode: 1033 }] } },
  },
});

Lookup (Single-table)

// Create 1:N relationship (creates lookup column on related entity)
const relationshipId = await metadataClient.createOneToManyRelationship({
  "@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata",
  SchemaName: "new_project_tasks",
  ReferencedEntity: "new_project",
  ReferencedAttribute: "new_projectid",
  ReferencingEntity: "new_task",
  Lookup: {
    SchemaName: "new_ProjectId",
    DisplayName: { LocalizedLabels: [{ Label: "Project", LanguageCode: 1033 }] },
    RequiredLevel: { Value: "ApplicationRequired" },
  },
});

File Column

await metadataClient.createAttributeDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.FileAttributeMetadata",
  SchemaName: "new_Attachment",
  DisplayName: { LocalizedLabels: [{ Label: "Attachment", LanguageCode: 1033 }] },
  RequiredLevel: { Value: "None" },
  MaxSizeInKB: 32768, // 32 MB
});

Image Column

await metadataClient.createAttributeDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.ImageAttributeMetadata",
  SchemaName: "new_Logo",
  DisplayName: { LocalizedLabels: [{ Label: "Project Logo", LanguageCode: 1033 }] },
  RequiredLevel: { Value: "None" },
  MaxSizeInKB: 10240, // 10 MB
  CanStoreFullImage: true,
});

Relationship Operations

One-to-Many (1:N) Relationship

// Create relationship (creates lookup on "many" side)
const relationshipId = await metadataClient.createOneToManyRelationship({
  "@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata",
  SchemaName: "new_account_projects",
  ReferencedEntity: "account", // "One" side (parent)
  ReferencedAttribute: "accountid",
  ReferencingEntity: "new_project", // "Many" side (child)
  Lookup: {
    SchemaName: "new_AccountId",
    DisplayName: { LocalizedLabels: [{ Label: "Account", LanguageCode: 1033 }] },
    RequiredLevel: { Value: "None" },
  },
});

Many-to-Many (N:N) Relationship

// Create N:N relationship (creates intersect table)
const relationshipId = await metadataClient.createManyToManyRelationship({
  "@odata.type": "Microsoft.Dynamics.CRM.ManyToManyRelationshipMetadata",
  SchemaName: "new_project_contact",
  Entity1LogicalName: "new_project",
  Entity2LogicalName: "contact",
  IntersectEntityName: "new_project_contact",
  Entity1NavigationPropertyName: "new_project_contact_projects",
  Entity2NavigationPropertyName: "new_project_contact_contacts",
});

Read Relationships

// Get all 1:N relationships for an entity
const oneToManyRels = await metadataClient.getOneToManyRelationships("new_project", {
  select: ["SchemaName", "ReferencedEntity", "ReferencingEntity"],
});

// Get all N:N relationships
const manyToManyRels = await metadataClient.getManyToManyRelationships("new_project");

Global Choice (Option Set) Operations

Create Global Choice

const optionSetId = await metadataClient.createGlobalOptionSet({
  "@odata.type": "Microsoft.Dynamics.CRM.OptionSetMetadata",
  Name: "new_projecttype",
  DisplayName: { LocalizedLabels: [{ Label: "Project Type", LanguageCode: 1033 }] },
  IsGlobal: true,
  OptionSetType: "Picklist",
  Options: [
    { Value: 100000, Label: { LocalizedLabels: [{ Label: "Internal", LanguageCode: 1033 }] } },
    { Value: 100001, Label: { LocalizedLabels: [{ Label: "External", LanguageCode: 1033 }] } },
    { Value: 100002, Label: { LocalizedLabels: [{ Label: "Research", LanguageCode: 1033 }] } },
  ],
});

Use Global Choice in Column

// Reference global choice when creating attribute
await metadataClient.createAttributeDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.PicklistAttributeMetadata",
  SchemaName: "new_Type",
  DisplayName: { LocalizedLabels: [{ Label: "Type", LanguageCode: 1033 }] },
  RequiredLevel: { Value: "None" },
  GlobalOptionSet: {
    "@odata.bind": `/GlobalOptionSetDefinitions(Name='new_projecttype')`,
  },
});

Insert Option Value

// Add new option to existing global choice
const result = await metadataClient.insertOptionValue({
  OptionSetName: "new_projecttype",
  Label: { LocalizedLabels: [{ Label: "Maintenance", LanguageCode: 1033 }] },
  Value: 100003, // Optional, auto-assigned if omitted
});

console.log("New option value:", result.NewOptionValue);

Polymorphic (Multi-table) Lookup Operations

Customer Lookup

// Create customer lookup (targets account OR contact)
const result = await metadataClient.createCustomerRelationships({
  Lookup: {
    "@odata.type": "Microsoft.Dynamics.CRM.LookupAttributeMetadata",
    SchemaName: "new_CustomerId",
    DisplayName: { LocalizedLabels: [{ Label: "Customer", LanguageCode: 1033 }] },
    RequiredLevel: { Value: "ApplicationRequired" },
  },
  OneToManyRelationships: [
    {
      "@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata",
      SchemaName: "new_account_projects",
      ReferencedEntity: "account",
      ReferencedAttribute: "accountid",
      ReferencingEntity: "new_project",
    },
    {
      "@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata",
      SchemaName: "new_contact_projects",
      ReferencedEntity: "contact",
      ReferencedAttribute: "contactid",
      ReferencingEntity: "new_project",
    },
  ],
});

Custom Polymorphic Lookup

// Create lookup targeting multiple custom entities
const result = await metadataClient.createPolymorphicLookupAttribute({
  OneToManyRelationships: [
    {
      "@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata",
      SchemaName: "new_task_regarding_project",
      ReferencedEntity: "new_project",
      ReferencedAttribute: "new_projectid",
      ReferencingEntity: "new_task",
    },
    {
      "@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata",
      SchemaName: "new_task_regarding_milestone",
      ReferencedEntity: "new_milestone",
      ReferencedAttribute: "new_milestoneid",
      ReferencingEntity: "new_task",
    },
  ],
  Lookup: {
    SchemaName: "new_RegardingId",
    DisplayName: { LocalizedLabels: [{ Label: "Regarding", LanguageCode: 1033 }] },
    RequiredLevel: { Value: "None" },
  },
});

Relationship Eligibility Checks

// Check if entity can be referenced (parent in 1:N)
const canBeReferenced = await metadataClient.canBeReferenced("new_project");
console.log("Can be parent:", canBeReferenced.CanBeReferenced);

// Check if entity can reference others (child in 1:N)
const canBeReferencing = await metadataClient.canBeReferencing("new_task");
console.log("Can be child:", canBeReferencing.CanBeReferencing);

// Check if entity can participate in N:N
const canManyToMany = await metadataClient.canManyToMany("new_project");
console.log("Can do N:N:", canManyToMany.CanManyToMany);

// Get valid entities for N:N relationships
const validEntities = await metadataClient.getValidManyToMany("new_project");
console.log("Valid N:N partners:", validEntities.EntityNames);

RetrieveMetadataChanges

Track metadata changes using version stamps for incremental sync:

// Initial query (get all + version stamp)
const result = await metadataClient.retrieveMetadataChanges(
  {
    Criteria: {
      FilterOperator: "And",
      Conditions: [{ PropertyName: "IsCustomEntity", Value: true }],
    },
    Properties: {
      PropertyNames: ["LogicalName", "DisplayName", "Attributes"],
    },
  },
  "Entity" // Track entity deletions
);

console.log("Entities:", result.EntityMetadata);
console.log("Server version:", result.ServerVersionStamp);

// Later: get only changes since last sync
const changes = await metadataClient.retrieveMetadataChanges(
  {
    Criteria: {
      FilterOperator: "And",
      Conditions: [{ PropertyName: "IsCustomEntity", Value: true }],
    },
    Properties: {
      PropertyNames: ["LogicalName", "DisplayName"],
    },
  },
  "Entity",
  result.ServerVersionStamp // Pass previous stamp
);

console.log("Changed entities:", changes.EntityMetadata);
console.log("Deleted entities:", changes.DeletedMetadata);

Important Notes

Update Operations Use PUT

Per Microsoft documentation, all metadata update operations use HTTP PUT (not PATCH). This means you must send the complete definition with all properties, not just the changed ones:

// Update entity - requires complete definition
await metadataClient.updateEntityDefinition("new_project", {
  "@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata",
  DisplayName: {
    LocalizedLabels: [{ Label: "Updated Project", LanguageCode: 1033 }],
  },
  Description: {
    LocalizedLabels: [{ Label: "Updated description", LanguageCode: 1033 }],
  },
  // Include all other properties you want to preserve
});

The SDK automatically sets the MSCRM.MergeLabels: true header to merge labels instead of replacing them, preserving labels for other languages.

Strict Typing

The metadata package uses strict type separation:

  • Read types - include all properties returned from the server (e.g., EntityMetadata)
  • Create types - exclude read-only and internal properties (e.g., EntityMetadataCreate)
  • Update types - similar to Create but may have different required fields (e.g., EntityMetadataUpdate)

This ensures you cannot accidentally send read-only fields like MetadataId, ObjectTypeCode, or internal-use fields in create/update operations.


TypeScript Examples

All examples in this README use TypeScript. For pure JavaScript, omit type annotations:

// JavaScript equivalent
const metadataClient = new MetadataClient(client);

const entityId = await metadataClient.createEntityDefinition({
  "@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata",
  SchemaName: "new_Project",
  DisplayName: {
    LocalizedLabels: [{ Label: "Project", LanguageCode: 1033 }],
  },
  // ... rest of definition
});

License

This package is part of the dataverse-sdk-typescript monorepo and is licensed under GNU AGPL v3.0. See LICENSE for details.

Publishing Changes

Metadata changes must be published before they take effect in the application. Use PublishXml or PublishAllXml actions from the messages package.

License

GNU AGPL v3.0

Related Packages