Building a Reusable Dataset PCF Control Instead of Unsupported Grid Scripts

A technical guide for teams whose Dynamics 365 grids depend on DOM scripts: one JSON-configured dataset PCF control that honours paging and sorting, saves inline edits safely, stays fast on large views and replaces grids one view at a time.

Scripts that manipulate the DOM of a Dynamics 365 grid fail because they depend on markup Microsoft never promised to keep. They break sorting, paging and selection, and shatter on platform updates. The supported replacement is one dataset PCF control, configured per view through a JSON input property, so the same control serves many grids. Build it on the dataset API: paging with setPageSize and loadNextPage, sorting through the sorting array and refresh, and selection through getSelectedRecordIds. Save inline edits per record through the Web API with visible per-row errors. Use windowed rendering so large views stay responsive, build accessibility in from the start, and replace grids one view at a time rather than in a single cutover.

Why do custom grid scripts break sorting, paging and selection in Dynamics 365?

Because they work on the grid's rendered HTML, not on the grid. A typical script waits for rows to appear, finds cells by CSS class or position, and then colours them, injects buttons, hides columns or attaches click handlers. The grid knows nothing about any of it. When the user sorts, pages or scrolls, the grid re-renders its rows and the changes vanish or land on the wrong record; when a script intercepts clicks, selection and the command bar stop agreeing about which records are selected.

The structure those scripts depend on is not a supported interface. Microsoft changes grid markup and class names as it updates the platform, and the grid itself has moved to newer controls over time, so a script that worked last quarter can fail silently after an update. Solution checker flags this kind of DOM access, and it is one of the patterns that makes upgrades painful. Our PCF controls development guide explains why rendering new interface belongs in a component rather than a form or grid script.

  • Formatting disappears after sorting or loading the next page, because the rows were re-rendered.
  • Injected buttons act on the wrong record once the order changes.
  • Selection in the grid and in the command bar disagree after a script handles clicks.
  • Scripts that poll for rows with timers slow the page and race the grid's own rendering.
  • Each grid has its own copy of the script, so a fix has to be repeated view by view.

Should you replace the scripts with a PCF control or the Power Apps grid control?

Check the no-code route first. If the scripts exist mainly to allow editing in place or faster filtering, the editable grid and the modern Power Apps grid control, configured on the view with editing enabled, may cover it without any code. For formatting or custom editing of particular cells, the Power Apps grid can also be extended with a customizer control that supplies cell renderers and editors while the platform keeps sorting, paging and selection.

A full dataset PCF control earns its cost when the grid needs behaviour the platform grid cannot give: layout that is not rows and columns, computed or related values, process-specific bulk actions, or complex validation during editing. The costs and ownership of buying, building and customising a control are compared in PCF controls: buy, build, or customize, and that decision should come before this page.

Need behind the scriptTry firstWhen a dataset PCF control is justified
Edit values in placeEditable grid or Power Apps grid with editing enabledEditing needs cross-field validation, keyboard entry across many rows or custom editors
Colour or icons by valuePower Apps grid customizer control for cell renderingRules span several columns or related records, or the layout changes
Hide or reorder columnsConfigure the view and its columnsColumns vary by user, record type or configuration the view cannot express
Buttons per rowCommand bar commands acting on the selectionActions are per row, contextual and too many for the command bar
A different visual layoutOut of the box controls such as the board or calendar where they fitCards, groups, timelines or boards specific to the process

What makes a dataset PCF control reusable across many views?

Configuration instead of code. The per-grid logic that used to live in twenty scripts becomes data: which columns to show and in what order, formatting rules, which columns are editable and how, row actions, grouping and page size. The control reads that configuration from a text input property in the manifest holding JSON, so a maker configures a new view by pasting a validated configuration rather than asking a developer for a new build.

The manifest defines the dataset and, where the control needs to know which column plays which role, property-set elements inside the data-set element, such as a status column or a date column. Makers map those to real columns per view, so the control does not hard-code logical names.

  • Validate the JSON against a schema in init and show a clear configuration error to makers instead of a blank grid when it is invalid.
  • Version the configuration format with a version field, so older configurations still load after the control evolves.
  • Default everything: a view with minimal configuration should still render its own columns sensibly.
  • Refer to columns by logical name and check they exist in dataset.columns before using them.
  • Where configuration is large or shared by many views, consider a configuration table in Dataverse referenced by name, so it is solution-aware and not pasted into each form.
  • Keep configuration declarative; do not allow arbitrary script in it.

How does a dataset PCF control honour paging and sorting properly?

By asking the platform to do the work instead of doing it in the browser. The dataset hands the control a page of records, never the whole view, so sorting or filtering the page you happen to hold gives answers that are wrong for everything you do not hold. These are the dataset API members that matter; behaviour such as whether loading the next page appends or replaces records can differ between model-driven apps and canvas apps, so verify it in your host and against current Microsoft documentation.

NeedDataset APICommon mistake
Page sizedataset.paging.setPageSize(n), then refreshSetting a very large page size to avoid paging and freezing the form
Next pagedataset.paging.hasNextPage and dataset.paging.loadNextPage()Looping loadNextPage on load to fetch the whole table
Back to the startdataset.paging.reset()Keeping stale page state after sort or filter changes
Total countdataset.paging.totalResultCountAssuming it is always known; handle an unknown count
SortingUpdate dataset.sorting with column name and direction, then dataset.refresh()Sorting dataset.records in memory for the current page only
Filteringdataset.filtering to set or clear a filter expression, then refreshFiltering the loaded page and reporting it as the result
Loading statedataset.loading and dataset.errorRendering a half loaded dataset or ignoring load errors
Row orderdataset.sortedRecordIdsIterating the records object and losing the server order

How should selection and opening records work in the control?

Through the dataset, so the platform and the control always agree. When a user selects rows, call dataset.setSelectedRecordIds with the ids; the command bar then acts on the same records, and ribbon rules that depend on selection keep working. Read dataset.getSelectedRecordIds when rendering so a selection made or cleared elsewhere is reflected in the control, and clear it deliberately after actions that remove rows.

To open a record, call dataset.openDatasetItem with the record's entity reference rather than building a URL, so navigation respects the app, the form and the host. Keep keyboard equivalents for both: selecting with the space key and opening with enter, matching what users expect from the standard grid.

How should inline editing save changes safely?

Treat the dataset records in a model-driven app as read only and save edits through the Web API, one record at a time, with every failure visible on the row it belongs to. The control declares the WebAPI feature in its manifest and calls context.webAPI.updateRecord with the table, the record id and only the changed columns. The update goes through the platform pipeline, so plug-ins, entity-scope business rules, auditing and security apply; form scripts do not run, because no form is open.

Saving several rows is not one transaction. If ten edits are saved and two fail, eight have been written, and the control must say so clearly instead of showing a generic error or silently discarding the two.

  • Track dirty cells per record in the control's state and show them as changed until saved.
  • Validate in the control for fast feedback, but rely on server-side validation such as a plug-in for anything that must hold.
  • Save per record, sequentially or with limited parallelism, and record success or the error message against each row.
  • Keep failed edits in place with the Dataverse error text so the user can correct and retry, rather than losing their input.
  • After saving, call dataset.refresh() so the grid reflects server values, including anything a plug-in changed.
  • Decide on concurrency explicitly. The PCF Web API update does not expose conditional update options, so either accept last write wins for low-risk columns or compare modifiedon before saving where overwriting someone else's change matters.
  • Respect read only state: check context.mode.isControlDisabled and column security before rendering an editor.

How do you keep large views fast with virtualisation?

Render only what is on screen. A control that creates DOM nodes for every loaded row will slow down as users load more pages, and a wide grid multiplies that by the number of columns. Windowed rendering, with a library such as react-window or the virtualisation built into the grid component you use, keeps the number of rendered rows close to what fits in the viewport, recycling them as the user scrolls.

Virtualisation and paging work together. Paging limits how many records are fetched; virtualisation limits how many are rendered. Load the next page when the user scrolls near the end or asks for more, never ahead of time for the whole view.

  • Use fixed or measured row heights so the virtual list can calculate positions without rendering every row.
  • Memoise row components and keep updateView from recreating the whole tree when only one record changed.
  • Keep editing state outside rendered rows, so a row scrolled out of view does not lose unsaved changes.
  • Test with the largest real view, as a user with the production role, not with sample data.

How do you build accessibility into a custom grid from the start?

Design it in rather than retrofitting it, because grid keyboard behaviour is expensive to add later. Use the grid pattern: the grid, row and gridcell roles, one tab stop into the grid, arrow keys between cells, and clear focus styling. With virtualisation, set aria-rowcount and aria-rowindex so screen readers announce the real position in the full result rather than in the rendered window. Announce sorting changes, page loads and save results through a live region, and give every inline editor an accessible name that includes the column. Drag interactions need a keyboard alternative.

The broader method for keyboard, focus, names, announcements and contrast, and how to evidence it, is in retrofitting accessibility into PCF controls.

How do you roll out the control one view at a time?

Replace grids in waves, with the old script and the new control never active on the same view, and every step reversible. A single cutover of twenty views multiplies risk: any defect in the shared control hits every team at once.

  • Inventory every script: which views, subgrids and forms it touches, what it does, and who relies on it.
  • Group the behaviours into configuration features of the one control, and build those first, not per-view special cases.
  • Pick a first view that is used daily but is not business critical, configure the control on it in a development environment, and remove that view's script in the same solution change.
  • Test with real users and real volumes, then release through the normal managed solution path.
  • Keep the previous managed solution available so a view can be returned to the standard grid quickly if needed.
  • Move on view by view, adding configuration rather than code where possible, and retire each script once its last view has moved.
  • Check which hosts each view is used in, such as web and the mobile app, and configure the control for each where it applies.

Should grid-heavy work stay on Dynamics 365 or move to a custom-built CRM?

For most organisations on Dynamics 365 Customer Engagement or Power Platform, a supported dataset control is a contained fix: the data model, security and processes stay where they are, and the fragile scripts disappear. Solzet ships dataset controls of its own; the Solzet Kanban Board, for example, is a dataset control that presents the records of a view as cards on a drag and drop board, and it shows the same principle of one configurable control serving many views.

We recommend the right solution - whether that's Microsoft Dynamics 365, Power Platform, or a custom-built CRM. Some businesses need the Microsoft ecosystem. Others need full control without licensing. We deliver both. If the grids are a symptom of a system that fights users at every step, and Microsoft licensing is not the right fit, a custom-built CRM on React, Node.js, PostgreSQL or .NET puts the whole interface under your control.

How does Solzet help replace grid scripts with a reusable dataset PCF control?

We start by inventorying the grid scripts and the views they touch, checking what the editable grid or Power Apps grid control already covers, and agreeing which behaviours the dataset control must provide. Then we build one configurable control in TypeScript and React on the dataset API, with paging, sorting, selection, safe inline saving, windowed rendering and keyboard support, write the JSON configuration for the first view, and replace grids one view at a time through managed solutions, with source code and configuration documentation handed over.

The work is done by senior consultants and full-stack developers delivering remotely from Yerevan, Armenia, with 8+ years of Dynamics 365 Customer Engagement and Power Platform work, directly for your team or white-label for Microsoft partners. Wider component work sits with our PCF controls development service, and controls already failing in production are covered in PCF controls in production.

What do people ask us?

Why does sorting or paging break our custom grid script in Dynamics 365?

Because the script changes the grid's rendered HTML, and the grid re-renders rows whenever the user sorts, pages or scrolls, discarding or misplacing the changes. The markup is not a supported interface and changes with platform updates. Replace the script with the Power Apps grid control where it covers the need, or with a dataset PCF control that uses the dataset API.

How do paging and sorting work in a dataset PCF control?

The dataset gives the control one page of records. Use dataset.paging.setPageSize, hasNextPage, loadNextPage and reset for paging, and change dataset.sorting with a column name and direction followed by dataset.refresh() for sorting, so the server sorts the whole view. Never sort or filter only the loaded page in memory, and verify host-specific paging behaviour in current Microsoft documentation.

Can a dataset PCF control edit records inline?

Yes, but in a model-driven app treat dataset records as read only and save edits with context.webAPI.updateRecord per record, with the WebAPI feature declared in the manifest. Saves across rows are not one transaction, so show success or the error on each row, keep failed edits for retry, and refresh the dataset afterwards. Plug-ins and entity-scope business rules run; form scripts do not.

How can one PCF control serve many different views?

Drive it from configuration. Add a text input property holding JSON that defines columns, formatting rules, editable columns, row actions and page size, validate it against a schema, and use property-set elements for columns that play a specific role. Each view then gets configuration rather than a separate build, and the control shows a clear error when configuration is invalid.

How do we stop a large view freezing the browser in a PCF grid?

Combine paging and windowed rendering. Fetch a sensible page size and load more only when the user scrolls near the end or asks, and render only the rows in view with a virtualisation library such as react-window. Memoise rows, keep editing state outside rendered rows, and test with the largest real view.

Should we use the Power Apps grid control instead of building a PCF control?

Often yes. If the scripts mainly provide editing in place or filtering, the editable grid or Power Apps grid control on the view may be enough, and a customizer control can supply custom cell rendering while the platform handles sorting and paging. Build a full dataset control when the layout, validation or actions go beyond what the platform grid supports.

How do we migrate twenty scripted grids without a big-bang cutover?

Inventory the scripts and views, turn their behaviours into configuration features of one control, then move one view at a time: configure the control and remove that view's script in the same solution change, test with real users and data, release, and keep the previous managed solution for rollback. Retire each script when its last view has moved.

Which solution is right for your business?

Tell us what you need. A senior consultant replies within one business day with a recommendation - Dynamics 365, Power Platform, or a custom-built CRM - not a sales script.