The TypeScript Type Trap in Dynamics 365

by Jun 6, 2025Blog

Everyone loves IntelliSense.

It’s comforting. It’s helpful. It’s… kinda overhyped?

Especially when it comes to adding strong types for form fields in Dynamics 365 or Dataverse.

Let me explain.

In the Dynamics 365 and Dataverse world, developers can generate form type declarations—TypeScript definitions that describe form attributes, controls, and context—giving them strong typing and IntelliSense while coding.

Popular tools like XrmDefinitelyTyped and dataverse-ify make it possible to generate these type definitions automatically based on your environment’s metadata.

It sounds great in theory—code that practically writes itself, with fewer errors and better tooling support.

But is it worth the cost?

The Promise of TypeScript Form Declarations

Here’s what the TypeScript fanclub says:

  • IntelliSense helps you discover fields.
  • Build fails if someone removes a field from a form.
  • Type safety means fewer bugs. Yay!

All true. But…

Let’s Think Practically

Do you really need IntelliSense for every form field?

  • In most cases, you’re only referencing each field once or twice in your script.
  • Even if your script uses multiple fields, it’s rare that one field is used many times.
  • Declaring them as constants is quick, readable, and avoids unnecessary build steps.

Example:

const fieldName = "new_specialfield";
formContext.getAttribute(fieldName)?.getValue();

That’s enough for 95% of what we do.

And let’s be real: if I can’t remember the field name, I’ll just open the form designer. It takes 10 seconds.

Keep in mind, the schema name never changes—because Dynamics 365 doesn’t allow it. So there is no fear of accidentally using an outdated name.

Which means this whole IntelliSense argument is really about convenience when writing the code—not actual stability.

And if you only reference each field once or twice? That convenience saves barely any time at all.

So we’re not just overengineering—we’re investing in tooling for a problem that barely exists.

But What If a Field Gets Removed?

Here’s the other big argument:
“If someone removes the field, the build will fail!”

Sounds great… until you realize:

  • How often does that actually happen? Rarely.
  • And if it does: was that field really critical?
  • Defensive coding protects against this anyway.

If a field is essential to your logic, then write logic that checks for its existence and shows a warning if it’s gone. You still need that check—even if you use generated types.

So what are we doing here? Making everything more complex for a rare edge case?

Even if the build fails—it just shifts the pain from runtime to CI/CD time. You still need a human to make a decision. And most of the time, that decision is: “Oh, that field was deprecated months ago. We forgot to clean up the code.”

But What About The Client-Side SDK?

Even without creating our own type definitions, there are the TypeScript declarations available for the Model-driven-apps JavaScript SDK.

You can find them at @types/xrm, and they provide IntelliSense and type safety for core APIs like getAttribute, getControl, and execution contexts. These types are actively maintained by the community—so if you’re one of those people keeping it alive: thank you!

They make it easier to write reliable, consistent client-side scripts. And they’re especially helpful when you’re exploring the SDK or building something complex.

But here’s the twist: you don’t need to use TypeScript to benefit from them.

Most modern IDEs like Visual Studio Code will use these type definitions for IntelliSense—even if your script is plain JavaScript.

To enable type checking and IntelliSense in JavaScript files in VSCode, you can:

  1. Add a jsconfig.json to your project root:
{
  "compilerOptions": {
    "checkJs": true
  },
  "include": ["./scripts/**/*"]
}
  1. Add a // @ts-check comment at the top of your JavaScript file.

For full details, see the official VSCode guide on JavaScript type checking.

So if your only goal is smart autocomplete and helpful hints while writing JavaScript… you already have it.

No build pipeline. No transpiling. No ceremony.

The Maintenance Nightmare

There’s another problem: all this ceremony is hard to maintain.

I’ve been brought into many projects where the original developers used fancy generators, build tools, wrappers, and strong typing.

But years later?

  • The tools no longer work.
  • The generated types are out of date.
  • Nobody knows how it was all stitched together.

Before I can even start fixing the real issue, I have to upgrade the old tooling just to make the build pass again.

It’s like buying a house and needing to rewire the whole place before you can even switch on the lights.

Even worse: sometimes there’s no access to the source code.

The horror.

In those cases, I’m always thankful if the production JavaScript in Dynamics 365 just looks like… plain old JavaScript.

Readable. Debuggable. Rebuildable.

If I can read the script, I can recreate the logic. No magic needed.

But if what I find is 4000 lines of minified mystery built from a TypeScript project using long-forgotten tools? Good luck. Now we’re reverse-engineering an entire dev stack from the past just to fix one onLoad bug.

Multiply that by every form in the system, and you’ve got yourself a legacy system nobody wants to touch. Which, ironically, is the opposite of what all this ceremony was supposed to prevent.

What I Do Instead

  • I code defensively.
  • I don’t generate types unless I really need them. I write them myself.
  • I keep scripts simple and maintainable.

That means writing code that checks if a field exists before using it. It means handling missing values gracefully. And yes—it means trusting myself and the future maintainers of this script to keep things clean, without needing an auto-generated crutch.

I’ve found that a bit of thoughtful, manual typing goes a long way. It’s often faster than setting up the whole toolchain and just as safe when used with care.

Simple is better than safe-but-complicated.

Because safety through complexity… is just a fancy way of saying “trust issues.”

The Real Problem: Trust

A lot of this ceremony—generating types, enforcing builds, adding wrappers—isn’t about better code.

It’s about not trusting others:

  • Not trusting the team to communicate changes.
  • Not trusting testers to catch obvious issues.
  • Not trusting ourselves to write code that fails gracefully.

That lack of trust leads to overengineering.

And overengineering leads to fragile, bloated, hard-to-maintain solutions.

We end up protecting against rare problems with heavyweight tools—when a simple if (field) check would do just fine.

Let’s flip the mindset:

  • Default to trust.
  • Expect change.
  • Build lightweight.

Because software that bends doesn’t break.

And trust-driven code is not just simpler. It’s smarter.

It’s also future-proof, easier to maintain, and kind to the next developer who inherits your work.

Even if that next developer… is you.

And if you’re still worried? Just leave a comment in the code:
“If this field ever disappears, blame Remy.”

That’s what I call accountability-driven architecture 😄

Remy van Duijkeren

Remy van Duijkeren

The Marketing Developer

I build automation and integrations that remove the annoying stuff—using Power Platform, Dynamics 365 & Azure.

Get My Thoughts on Automation & Development

Join my personal newsletter and get practical insights on building faster, integrating smarter, and removing friction in IT systems — especially on Power Platform and Dynamics 365.

Related