You’ve hit a real (and very poorly documented) quirk of Quick View Forms, and yes — what you’re seeing is expected behavior, even though the documentation strongly implies otherwise.
Short version:
isLoaded() only tells you the Quick View UI is loaded, not that its data is bound and available to the client API.
That distinction is the root of the problem.
What’s actually happening (important)
What isLoaded() really means
quickForm.isLoaded() returns true when the Quick View frame is rendered, not when the underlying Dataverse data has been hydrated into the form context.
So this sequence is completely valid (and common):
-
Main form loads
-
Quick View frame loads → isLoaded() === true
-
Quick View data binding happens later (async, internal)
-
Attribute values become available only after binding completes
The API does not expose an event for step 3.
Why switching forms “fixes” it
When you switch forms:
-
the form context is reinitialized
-
Quick View binding completes earlier in the lifecycle
-
attributes are now cached in memory
-
getAttribute().getValue() suddenly works
That’s why:
This is not related to caching bugs or your code — it’s a lifecycle timing issue.
Why the documentation is misleading
The doc you referenced strongly implies:
If isLoaded() is true, attributes are accessible.
But in reality:
This has been raised multiple times internally and in the community — but the docs have never been corrected.
So yes: the behavior you’re seeing is expected, even though the docs suggest otherwise.
Why getAttribute() returns null
Two separate things can be true at the same time:
Quick View Forms do not participate in the main form’s attribute lifecycle the way regular fields do.
They are effectively:
-
iframe-backed
-
async-loaded
-
partially isolated
What does not work (and never will)
Just to save you time:
-
❌ isLoaded() checks
-
❌ polling in a tight loop
-
❌ setTimeout(0) / setTimeout(100)
-
❌ onLoad of the main form
-
❌ moving the code to different events
None of these guarantee data binding.
Supported and reliable workarounds
✅ Option 1 (recommended): Read from the source entity, not the Quick View
If you know:
Then retrieve it directly:
const lookup = formContext.getAttribute("lookupfield").getValue();
if (!lookup) return;
const id = lookup[0].id.replace(/[{}]/g, "");
Xrm.WebApi.retrieveRecord(
lookup[0].entityType,
id,
"?$select=myAttrName"
).then(r => {
const val = r.myAttrName;
});
✔ deterministic
✔ supported
✔ no timing issues
✔ works on first load
This is Microsoft’s actual recommended pattern, even though it’s rarely stated explicitly.
✅ Option 2: Defer execution using a delayed retry (pragmatic)
If you must read from the Quick View:
function tryGetQuickViewValue(qv, retries = 5) {
const attr = qv.getAttribute("myAttrName");
const val = attr?.getValue();
if (val !== null) {
return val;
}
if (retries > 0) {
setTimeout(() => tryGetQuickViewValue(qv, retries - 1), 300);
}
}
This works because:
⚠️ Not elegant, but commonly used.
❌ What you cannot do
There is no:
This is a known gap.
Why this hasn’t been fixed
Quick View Forms are:
Microsoft’s direction is:
Which explains why this API edge case has never been fully resolved.
Final answer (directly to your question)
Is this expected behavior?
Yes.
Unfortunately, it is expected, even though the documentation strongly implies otherwise.
Does isLoaded() guarantee attribute availability?
No.
It only guarantees the Quick View is rendered — not that its data is accessible via the client API.
Best practice takeaway
Never rely on Quick View Forms as a data source for logic.
Use them for display only, and retrieve related data explicitly when needed.