What it does
A virtual table is a table definition in Dataverse with no physical storage behind it; rows are fetched from an external system at the moment they're needed. The data provider is what does the fetching — either one of the providers that ships with the platform, or a set of plug-ins you write yourself and register against the CRUD events.
Key facts
- Two providers ship with Dataverse. The OData v4 provider is installed by default and supports create, read, update and delete. An Azure Cosmos DB provider is available from the marketplace.
- A custom provider is a plug-in per operation —
Create,Update,Retrieve,RetrieveMultiple,Delete— running in stage 30, the main core transaction stage that ordinary plug-in steps can't use. - You register the assembly and the plug-in types in the Plug-in Registration tool but you don't register steps. Configuration lives in the
EntityDataProvidertable. Retrievegets a description of the columns and related tables to fetch.RetrieveMultiplegets aQueryExpression, and the framework provides aQueryExpressionVisitorclass for walking it.- The
Microsoft.Xrm.Sdk.Data.dllassembly, from theMicrosoft.CrmSdk.DataNuGet package, provides the mapping framework and a set of exception types —AuthenticationException,EndpointException,InvalidQueryException,ObjectNotFoundException,TimeoutExceptionand others — that you're expected to throw rather than inventing your own. - Two development shapes: a generic provider that translates FetchXML into whatever the source understands and can be reused across instances, or a targeted LINQ provider tied to one known schema, which is far less work but must be rebuilt when that schema changes.
- Only organisation-owned tables are supported. User-owned security filtering doesn't work, and column-level security isn't supported. Access is on or off per security role.
- Every external row needs a GUID primary key that the provider can supply and map. The cleanest arrangement is for the source to actually use GUIDs.
- Column selection in
RetrieveandRetrieveMultipleisn't honoured — all attributes are returned regardless. - Not supported on virtual tables: auditing, Dataverse search, charts and dashboards, queues, offline caching, calculated and rollup columns, activities and business process flows. Virtual table lookup columns can be shown on a grid but can't be filtered or sorted on.
- A virtual table can't be converted to a standard table afterwards, or the reverse.
When to use / skip
Use a virtual table when the external system must stay the system of record, the data volume makes replication unattractive, and users need to see it inside a model-driven app alongside real Dataverse data. Before writing a custom provider, seriously consider putting an OData v4 interface in front of the source instead and using the shipped provider — Microsoft suggests this themselves, and it converts a substantial .NET development effort into an integration layer that other things can also consume. Skip virtual tables entirely if the client needs search, charts, auditing, row-level security or offline on that data, because none of it works and there's no workaround.
Configuration decisions
- Adapt the source to OData v4 and use the built-in provider, or write a custom one. This is the big decision and the OData route is right more often than teams expect.
- Generic FetchXML-translating provider versus targeted LINQ provider, which is a trade of upfront effort against future schema fragility.
- Which operations to support. Read-only is a legitimate and much simpler answer.
- How the external identity maps to a GUID, and whether the source can be changed to hold one rather than you deriving one.
- Where the connection credentials live and which identity the provider connects as, given that Dataverse row-level security won't be filtering anything.
- Timeout and failure behaviour, since a slow external system now makes a Dataverse form slow.
Gotchas
- Because column selection is ignored, every retrieve pulls every attribute. On a wide table over a chatty source, that's the performance problem you'll spend the project chasing.
- Virtual table lookup columns on a grid are documented as slow, and Microsoft's own advice is to limit how many you include. Users add them to views and then report the view is broken.
- Row-level security genuinely doesn't exist here. If the external data has per-user visibility rules, they have to be enforced inside the provider or not at all — and the provider connects as one identity.
- No auditing means no answer to "who changed this and when" for anything on a virtual table. That's a compliance conversation, not a technical limitation to note in passing.
- The provider runs in stage 30 in the transaction, so anything slow or unavailable upstream surfaces as a Dataverse error in the user's face.
- The virtual-versus-standard decision is permanent. Getting it wrong means new tables and a migration.
Consultant notes
- The honest positioning is that virtual tables are for surfacing external data in the UI, not for building on. As soon as a client wants to report on it, search it or secure it per user, the answer changes to replication.
- Cost the custom provider properly. Microsoft's own prerequisites list is domain knowledge of the source, the Dataverse metadata model, the event framework and plug-in development — that's a senior developer for weeks, plus ongoing maintenance.
- Get the external system's availability and latency SLA before agreeing to this. A virtual table inherits both, and the users will blame Dataverse.
- Prototype the read path against realistic volumes early. The all-attributes behaviour is the thing that kills these projects, and it doesn't show up on a table with fifty test rows.
Worth revisiting if Microsoft adds column selection or row-level security to virtual tables — neither is supported as of July 2026.