dynamics365

n8n community node for Microsoft Dynamics 365 / Dataverse (on-premises, NTLM) Web API

Documentation

@marefati110/n8n-nodes-dynamics365

n8n community nodes for Microsoft Dynamics 365 / Dataverse on-premises (CRM 9.x), using the Web API with NTLM (Windows) authentication.

The package has no runtime dependencies. NTLMv2 (including MD4) is implemented in the package itself, because n8n's HTTP helpers do not support NTLM.

The nodes don't hard-code any tables or messages. Tables, columns, option sets, relationships, views, workflows, and all actions and functions are read from the server's metadata at runtime. Custom tables and custom actions work with no extra setup.

Tested against Dynamics 365 9.1.12.17 on-premises with n8n 2.36.7.

Nodes

Node Purpose
Dynamics 365 (On-Premises) Records, actions/functions, metadata, $batch, raw requests. Also available as an AI agent tool.
Dynamics 365 (On-Premises) Trigger Polling trigger for created and/or updated records.

Resources and operations

Record

  • Create, Update, Upsert (create-or-update, create-only, update-only), Delete, Get, Get Many
  • Query (FetchXML): automatic paging cookie handling. Very long queries are sent through $batch to avoid URL length limits.
  • Get View Records: system (savedQuery) or personal (userQuery) views
  • Get Related: navigate any 1:N, N:1 or N:N relationship
  • Associate / Disassociate: collection-valued and single-valued navigation properties
  • Assign (user/team), Set State (statecode/statuscode)
  • Get / Set / Clear Field Value
  • Download / Upload File or Image (image columns such as entityimage)
  • Count: exact count, optionally filtered

Action / Function

  • Execute Action: any of the ~2000 system and custom actions, unbound, bound to a record, or bound to a table
  • Execute Function: any function, with OData parameter aliases built automatically
  • Run Workflow: on-demand (classic) workflows via ExecuteWorkflow

Parameters are shown as typed fields based on the CSDL (booleans, numbers, dates, enum dropdowns, record references, collections, complex types), or can be passed as JSON.

Metadata: list tables, get a table definition, list columns, list relationships, get option sets (column or global), list global option sets, list/get actions and functions, clear the metadata cache.

Batch: send many requests in one $batch call, optionally in an atomic change set, with continue-on-error. Each response is mapped back to its input item.

Custom API Call: any method and path against the Web API with the NTLM credentials. Supports JSON, raw or binary bodies, binary responses, and following @odata.nextLink.

Common options

  • Annotations: formatted values (option set labels, lookup names, formatted money and dates) or all annotations
  • Impersonate User ID: sent as MSCRMCallerID
  • Enable Duplicate Detection, Bypass Custom Plugins
  • Timeout

Installation

In n8n go to Settings → Community Nodes → Install and enter @marefati110/n8n-nodes-dynamics365.

To install offline instead, build a tarball:

pnpm install
pnpm pack         # creates marefati110-n8n-nodes-dynamics365-1.0.3.tgz

Then install it into n8n's community nodes folder, for example in a Docker image:

FROM n8nio/n8n:2.36.7
COPY marefati110-n8n-nodes-dynamics365-1.0.3.tgz /tmp/
RUN mkdir -p /home/node/.n8n/nodes && cd /home/node/.n8n/nodes \
 && npm install /tmp/marefati110-n8n-nodes-dynamics365-1.0.3.tgz

Or on a plain installation:

cd ~/.n8n/nodes && npm install /path/to/marefati110-n8n-nodes-dynamics365-1.0.3.tgz

Restart n8n afterwards. The package has no dependencies; n8n provides n8n-workflow at runtime.

Credentials

Create Dynamics 365 On-Premises (NTLM) API credentials:

Field Example
Organization URL http://crm-server/OrganizationName. A full …/api/data/v9.1 URL also works.
API Version 9.1
Username crmadmin, DOMAIN\crmadmin or crmadmin@domain
Password
Domain optional; defaults to the domain in the username or the one announced by the server

The Test button calls WhoAmI().

The server must offer NTLM, either directly or through Negotiate. Kerberos-only servers are not supported.

Value formats

Lookup columns (Create / Update / Upsert, Map Columns mode) accept:

  • a GUID: 5e80e09c-b131-e911-8148-000c29f57f53 (single-table lookups; Owner defaults to systemuser)
  • table:GUID: account:5e80…
  • entityset(GUID): accounts(5e80…) or /accounts(5e80…)
  • null to clear the lookup

The node turns these into the right navigationproperty@odata.bind reference. For multi-table lookups such as parentcustomerid or regardingobjectid, it picks the navigation property for the referenced table. Clearing is done with DELETE …/$ref, because 9.x rejects a null @odata.bind.

In JSON mode the body is sent as-is, except that "nav@odata.bind": null is also converted into a DELETE …/$ref.

Record parameters of actions and functions (for example Target) accept the same notations, or a full JSON object such as {"@odata.type": "Microsoft.Dynamics.CRM.account", "accountid": "…"}. Collection parameters accept a JSON array or a comma-separated list.

Examples:

  • Execute Action → GrantAccess
    • Target: contact:<id>
    • PrincipalAccess: {"Principal": {"@odata.type": "Microsoft.Dynamics.CRM.team", "teamid": "<id>"}, "AccessMask": "ReadAccess"}
  • Execute Function → RetrieveTotalRecordCount
    • EntityNames: account,contact

Filters

Get Many, Count, Get Related, and the trigger have a Filter setting with three modes:

Mode What you enter
None Nothing. All records are returned.
Build Conditions One or more conditions (column, operator, value), combined with AND or OR.
Raw OData Expression A $filter expression, for example statecode eq 0 and contains(name,'contoso').

In Build Conditions mode, the node formats each value from the column's metadata:

  • Text columns get quotes. Numbers and choice columns use the option value, for example 1.
  • Lookup columns take a GUID and are compared through _<column>_value.
  • Dates take ISO values such as 2026-01-31.
  • A column that isn't in the list, such as the lookup path primarycontactid/fullname, can be set with an expression. Its value type is inferred.

Operators:

Group Operators Value
Comparison equals, not equals, greater/less than (or equal) one value
Empty is empty, is not empty none
Text contains, starts with, ends with, and their negations text
Lists in list, not in list, choices contain / don't contain values comma-separated values
Range between, not between two comma-separated values
Dates today, yesterday, this/last/next week, month, and year, last/next 7 days none
Dates on, on or before, on or after a date
Dates last/next X hours, days, weeks, months, years; older than X minutes to years a whole number
User equals current user, current user's teams, current user or teams, current business unit none
Hierarchy above, under (or equal), not under a record ID

Lists, ranges, dates, users, and hierarchy use Dynamics query functions such as Microsoft.Dynamics.CRM.LastXDays.

Trigger

The trigger polls on createdon (created) or modifiedon (updated / created or updated):

  • On activation it starts from "now".
  • On a manual test it returns the most recent record.
  • Records with identical timestamps are not emitted twice.

You can add an extra OData filter and limit the selected columns.

Metadata cache

Table, column, relationship, and CSDL metadata are cached in memory for 30–60 minutes per credential. After customizing the CRM, run Metadata → Clear Cache, or restart n8n.

The first action/function lookup downloads $metadata, which is about 13 MB on a typical organization, and takes a few seconds.

Development

The project uses pnpm (see packageManager in package.json).

pnpm install
pnpm build
pnpm test                   # offline unit tests (NTLM vectors, CSDL, OData helpers, batch)

# Live tests (read-only unless DYNAMICS_TEST_WRITE=1)
DYNAMICS_URL=http://server/org DYNAMICS_USERNAME=user DYNAMICS_PASSWORD=secret \
  pnpm test:integration

# Include write tests; supply the columns your org requires
DYNAMICS_TEST_WRITE=1 DYNAMICS_TEST_RECORD='{"lastname":"n8n-node-test","new_type":100000005}' \
  DYNAMICS_URL=… DYNAMICS_USERNAME=… DYNAMICS_PASSWORD=… pnpm test:integration

Release a new version

pnpm release does the whole release in one command:

  1. Checks that the git working tree is clean.
  2. Bumps the version, starting from the newest version on npm.
  3. Runs the unit tests.
  4. Publishes to npm.
  5. Commits, tags (v<version>), and pushes.
pnpm release              # patch, for example 1.1.0 -> 1.1.1
pnpm release minor        # 1.1.0 -> 1.2.0
pnpm release major        # 1.1.0 -> 2.0.0
pnpm release 1.4.0        # explicit version
pnpm release --dry-run    # check everything without publishing or committing

If npm asks for a one-time password, confirm it in the browser or terminal. If publishing fails, the version change is reverted.

pnpm-workspace.yaml disables the build script of isolated-vm, a native module that n8n-workflow pulls in. Only the TypeScript types of n8n-workflow are needed to build this package.

Source layout:

credentials/                         NTLM credential type
nodes/Dynamics365/
  transport/ntlm.ts                  NTLMv2 + MD4
  transport/httpClient.ts            keep-alive connection pool with NTLM handshake
  transport/index.ts                 Web API requests, errors, paging, cache
  helpers/csdl.ts                    $metadata parser (actions, functions, enums)
  helpers/metadata.ts                EntityDefinitions: tables, columns, option sets, relationships
  helpers/odata.ts                   keys, lookups, parameter literals, $batch, FetchXML paging
  methods/                           dropdowns, field mappers, credential test
  descriptions/                      UI definitions per resource
  actions/                           execution per resource
nodes/Dynamics365Trigger/            polling trigger

Limitations

  • HTTP proxies are not supported. The node connects directly to the CRM server.
  • File columns larger than one request (chunked upload) are not implemented. 9.1 on-premises does not have file columns; image columns work.
  • FetchXML aggregates are limited by the server to 50,000 records (AggregateQueryRecordLimit). The Count operation falls back to paging in that case.

License

MIT

Discussion