Erpy for Craft CMS

Erpy for Business Central

Erpy for Business Central connects Craft Commerce to Microsoft Dynamics 365 Business Central through the standard API v2.0, authenticated with an Entra ID application. The add-on is free; it needs Erpy, which is the paid part and owns the sync engine, the identity map, mapping, the queue and the log.

Install

composer require justinholtweb/craft-erpy-businesscentral
php craft plugin/install erpy-businesscentral

Then Erpy → Connections → New connection and pick Dynamics 365 Business Central. The add-on has no settings screen of its own; everything lives on the connection.

What it syncs

EntityDirectionDelta syncPage size
CustomersERP → CommerceYes, on lastModifiedDateTime100
ProductsERP → CommerceYes, on lastModifiedDateTime100
PricesERP → CommerceNo. Needs a published page (see below)100
InventoryERP → CommerceYes100
OrdersCommerce → ERP
Order statusERP → CommerceYes100
ShipmentsERP → CommerceFull read each run. Needs a published page100
InvoicesERP → CommerceYes100
CreditERP → CommerceNo100

The connector declares multi-company support and that Business Central offers a sandbox: point a connection at a sandbox environment first.

What the standard API does not have

Business Central's API v2.0 covers items, customers, orders, invoices and financials. It does not expose sales prices or posted shipment tracking numbers; those live on pages Microsoft has never surfaced. Both are optional settings here:

  • Sales prices endpoint. Publish a page as an API in your own extension and name it here, relative to the company. It must expose itemNo and unitPrice, and optionally customerNo, customerPriceGroup, minimumQuantity, startingDate and endingDate.
  • Posted shipments endpoint. The same, exposing orderNo and no, and optionally packageTrackingNo, shippingAgentCode and postingDate.

Leave either blank and Erpy does not sync that entity. The run log says why rather than reporting an empty success.

Credit limit is similar. If you have surfaced Credit Limit (LCY) on your customers API page, name the field in Credit limit field.

Connecting

Entra ID application. Register an application in Entra ID and grant it the Business Central API permission. Then grant that application access inside Business Central too, under Microsoft Entra Applications. Registering it in Entra ID alone is not enough.

FieldWhat to put there
Tenant IDThe GUID of your Microsoft 365 tenant.
Application (client) IDFrom the app registration.
Client secretFrom the app registration. Secrets expire; note when.
Environment nameAs it appears in the Business Central admin centre: Production, Sandbox, or whatever yours is called. It is case-sensitive.
CompanyThe company to read and write, exactly as it appears in the company list.
Sales prices endpointOptional; see above.
Posted shipments endpointOptional; see above.
Credit limit fieldOptional; see above.

The form also has a Webhook secret field. Leave it blank: this connector does not declare webhook support, so Erpy polls Business Central on its schedule.

Test connection interprets the common failures for you:

  • 401. The client secret has expired, or the application was never granted access inside Business Central.
  • 403. The application exists but has no permission set in Business Central. Give it D365 BUS FULL ACCESS, or a narrower set that covers the entities you sync.
  • 404. The environment name is wrong. It is the name from the admin centre, not the company name.

Things to know about Business Central

Client credentials, no consent screen. The connector uses the OAuth 2.0 client credentials grant, which is the one Microsoft steers integrations towards. Nobody sits in front of a nightly stock sync, so there is nobody to click through a consent screen.

Throttling is per environment. Business Central answers 429 with a Retry-After it means, and the transport honours it.

Stock is company-wide. The item record carries its own inventory figure, so stock costs no extra request. Only items of type Inventory are stock-tracked; Service and Non-Inventory items come through without it.

blocked is not a yes/no. On a customer it is blank when not blocked, and Ship, Invoice or All for different degrees of stop. Any of the three brings the customer through on hold.

Your order number is the external document number. Business Central has no idempotency key, so the Commerce order number goes out as externalDocumentNumber (up to 35 characters). It is indexed and it is what your staff will search for. Before creating an order the connector asks whether one with that number already exists. Order status and shipments come back matched on the same field.

Lines are separate requests. The order header is created first and each line is added after it. A line Business Central refuses leaves the header behind, so the failure names the line, and somebody has to finish that order in Business Central.

Shipping is an item line. A Business Central sales order has no shipping field of its own. On the order mapping, turn on Send shipping as an order line and set Shipping SKU to the item that represents it.

A wrong company is refused, not retried. If the company name cannot be resolved, the order is marked as refused with a message naming the setting, so the queue does not resend it until it gives up.

Correcting a field

A published page is yours, so its field names are whatever your developer chose. If your sales prices page calls the price salesPrice rather than unitPrice, correct it on the Prices mapping instead of republishing the page:

raw.salesPrice   →   unitPrice        transform: number

The same works for the item: the connector reads itemNo, then itemNumber. If yours is itemCode, map raw.itemCode → sku.

See Field mapping for the rule syntax and the full transform list, Syncing for runs and watermarks, and Troubleshooting when something does not arrive.