Skip to content

Webresources

pete-mc edited this page Jul 9, 2026 · 9 revisions

Web Resources

Build Dataverse web resources in TypeScript, with strong typing, bundling, testing, and one-click deploy:

  • Write web resources as TypeScript classes and bundle them to one JavaScript library with webpack.
  • Deploy straight to Dataverse via the Web API — no external tooling.
  • Generate strongly-typed Xrm typings with XrmDefinitelyTyped.
  • Unit test with Jest and xrm-mock.

Web resource menu

Steps: PrerequisitesGenerate typingsCreate a classBuildDeployRegister on a formTest.


Prerequisites

  • Node.js and the web resource npm globals (jest webpack webpack-cli typescript) — see Getting Started. The project also restores XrmDefinitelyTyped via paket on first run.
  • A connected environment.

Cross-platform note: everything here works on Windows, macOS, and Linux except Generate Typings, which runs XrmDefinitelyTyped.exe (a Windows-only .NET Framework tool). On macOS/Linux you can still write, build, and deploy — you just won't get the generated form typings.


Step 1 — Generate typings

Run Dataverse PowerTools: Generate Typings first. It runs XrmDefinitelyTyped to produce, into a typings folder:

  • Strongly-typed definitions for the Web API and each form's FormContext (so you get IntelliSense for the exact fields on a form).
  • The bundled XrmQuery helper library (webresources_src/lib/dg.xrmquery.web.min.js) that the webpack build references.

Re-run it whenever the schema or a form's fields change.

Verify: a typings/XRM folder appears and webresources_src/lib/ contains dg.xrmquery.web.min.js.

Generate typings before your first build — the webpack config imports the XrmQuery library that this step produces.


Step 2 — Create a class

Right-click the webresources_src folder (or use Create Web Resource Class). You'll be asked for the class name, the Dataverse table, and the form the code runs on. The library.ts entry point is updated to export the new class so webpack bundles it, and a matching test is scaffolded.

export class Account {
  static async OnLoad(executionContext: Xrm.ExecutionContext<unknown, unknown>): Promise<void> {
    const form = <Form.account.Main.Information>executionContext.getFormContext();
    this.BindEvents(form);
    this.OnLoadLogic(form);
  }

  static async OnLoadLogic(form: Form.account.Main.Information) {
    switch (form.ui.getFormType().valueOf()) {
      case Xrm.FormType.Create: /* new records */ break;
      case Xrm.FormType.Update: /* existing records */ break;
    }
  }

  static async BindEvents(form: Form.account.Main.Information) {
    form.data.entity.addOnSave(() => this.OnSave(form));
  }

  static async OnSave(form: Form.account.Main.Information) { /* on save */ }
}

Casting the form as Form.account.Main.Information gives full IntelliSense for the fields on that form — that type comes from Step 1.


Step 3 — Build

Run Dataverse PowerTools: Build Webresources. webpack bundles the TypeScript into bin/<prefix>_library.js (where <prefix> is your solution's publisher prefix), with a source map for debugging.

Verify: bin/<prefix>_library.js exists.


Step 4 — Deploy

Run Dataverse PowerTools: Build and Deploy Webresources. It builds, then upserts every file in bin/ directly to Dataverse via the Web API and publishes. If a solution is configured (webresourceSolutionName), the resources are added to it.

Verify: the log shows Deployed webresource: <prefix>_library.js and Webresource deployment complete; the file appears in the maker portal under Web Resources.


Step 5 — Register on a form

You can register the handler on a form in two ways:

Let the extension do it — add a PowerTools.RegisterEvent block with Add Form Registration, then run Register Form Events to add the library and wire the handlers on the form automatically:

<PowerTools.RegisterEvent[]>[
  {
    formId: "f182e4c6-6bff-ed11-8f6d-00224897cd92",
    event: "onload",
    executionContext: true,
    triggerId: "d8b98c6a-d2a3-4465-bfec-aa75adf73c15",
    function: "prefix.Account.OnLoad",
  },
];

Or do it manually in the form editor — add the web resource and register the function:

  • Library: <prefix>_library.js
  • Function: <prefix>.ClassName.OnLoad
  • Pass the execution context as the first parameter.

See Configure a form to use a web resource.


Form intersections

Use the Form Intersects view to generate typings that target multiple similar forms at once (see the XrmDefinitelyTyped Form intersections docs). Click + to add an intersect, add the tables/forms to it, then re-run Generate Typings.


Testing

Create tests with Create New Test. Tests use Jest with xrm-mock as a fake Xrm implementation, so web resources run locally without Dataverse:

import { Account } from "../Account";
import { XrmMockGenerator } from "xrm-mock";

XrmMockGenerator.initialise();
XrmMockGenerator.Attribute.createString("someattribute", "somevalue");
const form = XrmMockGenerator.eventContext.getFormContext() as Form.account.Main.Information;
Account.OnLoad(XrmMockGenerator.eventContext);

it("keeps the value", () => {
  expect(form.getAttribute("someattribute").getValue()).toBe("somevalue");
});

XrmQuery

Alongside form typings, the project bundles XrmQuery — a fluent, strongly-typed wrapper over the Web API:

const accounts = await XrmQuery.retrieveMultiple(x => x.accounts)
  .select(x => [x.accountnumber])
  .filter(x => Filter.equals(x.name, "Contoso"))
  .promise();

Troubleshooting

Symptom Fix
Build fails on a missing dg.xrmquery.web.min module Run Generate Typings first — it produces that file.
Generate Typings fails with XrmDefinitelyTyped.exe … not found The tool restores via paket on project creation; run Restore Dependencies, or recreate the project. It's a Windows-only tool.
Type errors like Namespace 'Form' has no exported member '<table>' Re-run Generate Typings so the form/table types exist.
Deploy can't connect Re-connect — see Refreshing a stale connection.

Migrating from spkl

Older web resource projects used spkl for deployment. If a spkl.json is present, run Upgrade from Spkl — it reads the solution name into dataverse-powertools.json and removes the old spkl config. Deployment then uses the built-in Web API upsert above.


Learn more

Clone this wiki locally