Refactoring a Tangled Power Apps Solution Without a Rewrite

A technical guide for the developer who inherited a canvas app where changing one form breaks three screens: freeze behaviour, extract shared logic, one data access layer, feature flags, concurrency and the order to do it in on a live app.

When changing one form breaks three screens and the original builder has gone, refactor the Power Apps app incrementally rather than rewriting it. First freeze current behaviour with a thin regression harness: a scripted checklist of key journeys, automated with Test Studio or Test Engine where they fit. Then extract shared logic into named formulas and components, route reads and writes through one data access layer instead of scattered Patch calls, and untangle nested Power Fx into readable steps. Ship rebuilt screens behind a feature flag, one at a time. Add optimistic concurrency checks and lightweight record locking, enforced server side, so simultaneous users stop overwriting each other. On a live app, the harness and concurrency protection come first.

Why does changing one form in a large Power Apps app break other screens?

Because the screens are not really separate. Canvas apps make it very easy to couple things invisibly, and an app that grew to dozens of screens under deadline pressure has usually done all of it. The breakage feels random, but it almost always traces back to a short list of patterns, and each of them has a specific refactoring move later on this page.

The practical consequence is that nobody can predict the blast radius of a change by reading the screen they are editing. The fix is not more care. It is making the dependencies explicit so a change can only affect what it visibly references.

  • Global variables set with Set() on several screens and read on others, so the value a screen sees depends on the path the user took to reach it.
  • Controls referencing controls on other screens, such as a form reading a gallery selection from a different screen, so renaming or rebuilding one control silently breaks another screen.
  • An App.OnStart that loads collections and sets dozens of variables, which every screen quietly depends on and nobody dares change.
  • The same business rule copied into the OnSelect of several buttons, then edited in one place and not the others.
  • Patch, Remove and SubmitForm calls against the same tables spread across many screens, each with slightly different validation and defaults.
  • Deeply nested If and Switch formulas that nobody can read, so a small change in one branch alters behaviour in another.

Should you refactor the app or rewrite it from scratch?

For a live app that people depend on, refactor first. A rewrite has to rediscover every rule the current app enforces, including the undocumented ones users rely on, and it delivers nothing until it is finished, while the old app keeps changing underneath it. An incremental strangler refactor replaces the app one piece at a time: new, cleanly built screens and shared logic grow around the old ones, traffic moves across screen by screen, and the old screens are deleted once nothing routes to them. The app keeps working throughout, and every step can be reversed.

A rewrite becomes the better option when the data model underneath is wrong, when the app is small enough to rebuild in less time than it takes to understand, or when the platform itself no longer fits, which is covered further down. If the problem is that galleries are slow rather than that changes break things, start with our guide to fixing slow canvas app galleries with delegation instead, because that is usually a formula fix rather than a refactor.

How do you freeze current behaviour before changing anything?

Build a thin regression harness around what the app does today, including the quirks users rely on, before you touch a formula. It does not need to be comprehensive. It needs to cover the journeys whose failure would be noticed first, and it needs to be cheap enough to run after every change. At the same time, map the coupling so you know where to start.

Get the app source into version control first, so every refactoring step is a reviewable diff. Microsoft has been moving canvas app source handling from pac canvas unpack towards Git integration with readable source files, so check which route is current for your environment. The environment path, managed solutions and pipeline are set out in our guide to Power Platform ALM with the PAC CLI. Once the source is readable, search it for Set(, Patch(, Remove(, SubmitForm( and cross screen control references to produce a coupling inventory.

Harness layerWhat it gives youCaveat
Scripted manual checklistNamed journeys with exact steps, test data and expected outcome, including which Dataverse rows change and to what values. Runs anywhere, today.Slow to run, so keep it to the journeys that matter and run it before every release.
Test StudioRecorded canvas app tests for repeatable journeys, run from within Power Apps.Check current Microsoft guidance on its status and on the recommended successor before investing heavily.
Power Apps Test EngineTest plans defined as files, run from the command line or a pipeline, which suits source control and automation.Parts of it have been in preview; confirm what is supported for your app type and version.
Data outcome snapshotFor each journey, a query of the rows it should create or change, compared before and after the refactor.Catches silent data changes that a screen based test would miss.
Monitor tracesA recording of the data calls and formula errors for a key journey, kept as a baseline.Useful for comparison, not a pass or fail test on its own.

How do you extract shared logic into named formulas and components?

Move each piece of logic that is copied or depended on across screens into one named place, without changing what it does. Behaviour stays identical, the harness should still pass, and from then on a rule changes in one location.

Named formulas, defined in the App.Formulas property, are the first tool. They declare a value once, such as the current user record, a permission check or a filtered reference list, and the platform recalculates them when their inputs change. Unlike a global variable set in OnStart, a named formula cannot be overwritten from a random screen, which removes a whole class of hidden coupling. User-defined functions extend the same idea to reusable calculations with parameters; check the current Microsoft documentation for their status and for whether functions with side effects are supported in your version before depending on them.

Canvas components are the second tool, for repeated pieces of interface such as a customer header, an address block or a status badge. Give each component explicit input and output properties rather than letting it read global state, and move components that several apps need into a component library so they are maintained once.

  • Start with values read on many screens: current user, role checks, lookup lists and configuration. These are the safest to move into named formulas.
  • Replace each global variable that is only ever computed, never set by user action, with a named formula of the same meaning.
  • Extract repeated calculations next, as user-defined functions where available, or as named formulas where the inputs are app level.
  • Turn repeated clusters of controls into components with declared input properties, and remove any reference inside a component to controls outside it.
  • Run the harness after each extraction and commit each one separately, so a regression points at one small diff.

How do you replace scattered Patch calls with a single data access layer?

Give every business operation one implementation, and make every screen call it. Scattered Patch calls are where tangled apps do their real damage: each copy applies slightly different defaults and validation, so the same record is saved differently depending on which screen the user happened to use.

For reads, named formulas are a natural data access layer: one definition of "open orders for this customer" or "active inspections" that every gallery and dropdown uses. Keep them delegable, as described in the delegation guide, or you will trade coupling for wrong results.

For writes, there are three reasonable homes, and the choice depends on how much the operation needs to be guaranteed. Operations that write several tables together, or that must never half complete, belong on the server: a Dataverse custom API backed by a plugin runs inside a transaction, which a sequence of Patch calls or a cloud flow does not. Power Automate flows called from the app suit orchestration and integration where transactional behaviour is not required. Simple single table writes can stay in Power Fx, but in one place, such as a behaviour function where your version supports them, rather than copied across screens.

Operation typeRecommended homeWhy
Shared read, filter or lookup listNamed formulaOne definition, recalculated automatically, cannot be overwritten from a screen.
Simple single table save with shared defaultsOne Power Fx implementation called from every screenRemoves copy and paste differences without adding server components.
Multi table write that must succeed or fail as a wholeDataverse custom API with a pluginRuns in a Dataverse transaction and enforces rules for every client, not only this app.
Orchestration, approvals or calls to other systemsPower Automate flow called from the appRetries and run history, but no transaction across steps, so design for partial failure.
Validation that must never be bypassedServer side: plugin, business rule or column constraintsA rule enforced only in the app can be bypassed by any other app, import or integration.

How do you untangle nested Power Fx into readable steps?

Make formulas read top to bottom, name the intermediate values, and remove state that crosses screens. None of these moves change behaviour on their own, which is what makes them safe inside a live app.

  • Use With() to name intermediate values inside a formula, so a nested expression becomes a short sequence of named steps that a reviewer can follow.
  • Replace long nested If chains on a single value with Switch, and move the decision table itself into a named formula or a configuration table where it changes over time.
  • Shrink App.OnStart. Values that can be declared belong in named formulas; data that each screen needs belongs on that screen or in a named formula; and the landing screen logic belongs in App.StartScreen rather than a Navigate call in OnStart.
  • Replace global variables used to pass a selected record between screens with navigation parameters or context variables, so each screen declares what it receives.
  • Remove cross screen control references. A screen should read a named formula or a value passed to it, never a control that lives on another screen.
  • Use IfError and the Errors function around saves so failures are handled in one visible place rather than ignored.
  • Run the App checker and fix the formula and accessibility warnings in each screen you touch, rather than across the whole app at once.

How do you ship the refactor one screen at a time behind a feature flag?

Build the new version of a screen alongside the old one, then route users to it with a flag you can switch off without a redeployment. Navigation is the natural seam in a canvas app: every route to the old screen goes through one named navigation point, and that point checks the flag.

The flag can be an environment variable read by the app, which suits an on or off switch per environment, or a small Dataverse configuration table with the flag name, whether it is on, and optionally which team or security role sees it, which lets a pilot group use the new screen while everyone else stays on the old one. Read the flag once into a named formula so every navigation point uses the same value.

The sequence per screen is fixed: build the new screen on the shared formulas and data access layer, run the harness against it, switch it on for a pilot group, watch for errors and complaints, widen to everyone, and delete the old screen only after the flag has been fully on for a release cycle without being switched back. If something goes wrong, switching the flag off is the rollback.

How do you stop fifty simultaneous users overwriting each other in Power Apps?

By default a canvas app does not protect you. When a user saves with Patch or a form, the columns being saved are written without checking whether someone else changed the row since this user loaded it, so the last save wins and the earlier change disappears without a warning. With many people editing the same records, that is lost data rather than a performance problem. There are three levels of protection, and a live app with real contention usually needs the first two, with the third where the business cannot accept any overlap.

Optimistic concurrency is the lightest. Keep the Modified On value the user loaded, and immediately before saving read the current row again; if the value has changed, stop, tell the user the record was updated by someone else, and show them the current values. Check Errors() after the save as well. This client side check leaves a small window between the check and the write, so it reduces lost updates rather than guaranteeing against them.

For a true guarantee, the check has to happen on the server. A plugin on update, or a custom API that the app calls instead of Patch, can compare the row version the client sent with the current one and reject the save when they differ; the Dataverse SDK supports a conditional update based on row version for exactly this. Offline apps have a different version of the same problem, covered in our guide to Power Apps offline data loss.

ApproachHow it worksGuaranteeUse it when
Last save wins (default)Patch or SubmitForm writes the columns without checking for changes by others.None.Records are only ever edited by one person.
Optimistic check in the appCompare the loaded Modified On with a fresh read before saving, and handle Errors() afterwards.Reduces lost updates; a small race window remains.Occasional overlap, where a clear warning to the user is enough.
Lightweight record lockLocked By and Locked Until columns set when a user starts editing, cleared on save or cancel, expiring automatically, with the check enforced on the server.Prevents overlapping edits when acquired and checked server side.Long edits on shared records, such as a case or an inspection being worked on for minutes.
Server side conditional updateA plugin or custom API rejects a save when the row version the client sent is no longer current.Strong, for every client and integration.Money, stock, bookings or anything where a silent overwrite is unacceptable.

In what order do you refactor a Power Apps app that is live and cannot go down?

In the order that removes risk fastest while changing the least user facing behaviour. The early steps change nothing users can see; the later ones change one screen at a time and can be switched back. Resist the temptation to start with the screen that annoys the team most.

  • Put the app into a solution, source control and a development, test and production path, so every later step is a reviewed, reversible release.
  • Build the regression harness and the data outcome snapshot for the journeys that matter.
  • Stop active data loss: add the server side concurrency check or lock to the tables where users overwrite each other, which protects the live app before any refactor lands.
  • Map the coupling from the source: global variables, cross screen references, Patch call sites and OnStart dependencies.
  • Extract named formulas and components with no behaviour change, one commit at a time, running the harness after each.
  • Introduce the data access layer, moving multi table and must not fail writes to the server first.
  • Add the feature flag at the navigation points, then rebuild and release screens one at a time, highest change frequency first.
  • Delete old screens, variables and OnStart logic once their flags have been fully on for a release cycle.
  • Decide whether the result should stay one app or be split.

When is splitting one Power Apps app into several apps the right move?

Split when the app serves groups of users with different jobs who never use each other's screens, when different teams need to release their part independently, or when the app has become hard to load and edit because of its sheer size. Several smaller apps over the same Dataverse tables, sharing a component library and a server side data access layer, are easier to test, release and own than one large one. Apps can link to each other with Launch and pass context with Param, so a user can still move between them.

Do not split to escape the coupling. If the shared logic still lives in copied formulas, splitting just multiplies the copies. Extract the named formulas, components and data access layer first, and the split then becomes mostly a matter of moving screens. For data heavy back office work, a model-driven app with a few custom pages is often a better home than more canvas screens.

Sometimes the honest conclusion is that the application has outgrown the platform, for example because it needs complex transactional behaviour, heavy offline use or licensing that does not fit its user base. 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. When Power Platform is the wrong fit, a custom-built application on React, Node.js, PostgreSQL or .NET is the alternative, and it is a decision to make on evidence rather than frustration.

How does Solzet help refactor a tangled Power Apps app?

We work in the order on this page. First the source into version control and a proper environment path, the regression harness and a coupling inventory, so you can see what the app actually depends on. Then the concurrency protection where users are losing data, followed by the extraction of named formulas, components and a data access layer, and the screen by screen rebuild behind flags, with your team reviewing every change so the knowledge stays with you.

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. What a wider Power Platform engagement covers is on our Power Platform developer page, and if the app is one symptom of a project that has stalled more widely, start with the project rescue and takeover service.

What do people ask us?

How do I refactor a Power Apps canvas app with dozens of screens without breaking it?

Refactor incrementally rather than rewriting. Put the app under source control, build a thin regression harness of scripted journeys and expected data outcomes, then extract shared logic into named formulas and components one change at a time, running the harness after each. Replace scattered Patch calls with one data access layer, untangle nested Power Fx with With() and named values, and release rebuilt screens one at a time behind a feature flag you can switch off without redeploying.

Why does changing one form in my Power Apps app break other screens?

Usually because of hidden coupling: global variables set on several screens and read on others, controls that reference controls on a different screen, a large App.OnStart that every screen depends on, and business rules or Patch calls copied into many places. A change to one screen alters a value or control another screen silently relies on. Named formulas, navigation parameters, components with explicit properties and a single data access layer make those dependencies visible.

What are named formulas in Power Apps and why do they help a refactor?

Named formulas are values declared once in the App.Formulas property, such as the current user, a role check or a filtered list, which Power Apps recalculates when their inputs change. Unlike global variables set in OnStart, they cannot be overwritten from a screen, so they remove a common source of hidden coupling and shrink OnStart. User-defined functions extend the idea to reusable calculations; check current Microsoft documentation for their status in your version.

Does Patch in a canvas app prevent users overwriting each other?

No. By default Patch and form saves write the supplied columns without checking whether another user changed the row since it was loaded, so the last save wins silently. You can add an optimistic check in the app by comparing the loaded Modified On value with a fresh read before saving and handling Errors() afterwards, but that leaves a small race window. For a true guarantee, enforce the check on the server with a plugin or a custom API.

How do I implement record locking in a Power Apps app?

Add Locked By and Locked Until columns to the table. When a user starts editing, acquire the lock by setting both, clear it on save or cancel, and let it expire automatically so an abandoned session does not block the record forever. Acquire and check the lock on the server, through a custom API or a plugin that rejects updates from anyone other than the lock holder, because two app clients can both read a record as unlocked at the same moment.

How do I use a feature flag to release a rebuilt Power Apps screen?

Route every navigation to the old screen through one navigation point that checks a flag, read once into a named formula. Store the flag as an environment variable for a simple per environment switch, or in a Dataverse configuration table if you want a pilot team to see the new screen first. Widen it once the pilot is clean, keep the old screen until the flag has been fully on for a release cycle, and switch the flag off to roll back.

Can Power Apps apps be tested automatically?

Partly. Test Studio records repeatable tests for canvas apps, and Power Apps Test Engine runs test plans defined as files from the command line or a pipeline, although parts of it have been in preview, so check current Microsoft guidance for your app type. Most teams still need a scripted manual checklist and a comparison of the Dataverse rows each journey should change, which together catch the silent data changes screen tests miss.

Should I split a large Power Apps app into several apps?

Split when distinct user groups never use each other's screens, when teams need to release parts independently, or when the app's size makes it hard to load and edit. Extract shared named formulas, components and the data access layer first, otherwise splitting only multiplies copied logic. Smaller apps can link with Launch and Param, share a component library and use the same Dataverse tables and server side rules.

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.