Master the Dataverse solution framework from the ground up — publishers, prefixes, managed vs unmanaged deployment, and the layering algorithm that explains almost every mysterious behavior in multi-environment Power Platform projects. This is the architectural knowledge that separates professional delivery from accidental success.

Picture this: your team has spent three months building a polished model-driven app for a sales operations team. Custom tables, carefully tuned forms, business rules, security roles, and a site map that actually makes sense. It works beautifully in your development environment. Now you need to move it to production — and your colleague needs to do the same with their procurement app next week, and someone else is talking about deploying updates every two weeks going forward.
If you've been manually recreating components in each environment, exporting raw unmanaged solutions and importing them wholesale, or copying-and-pasting configuration across environments, you already know the pain: mysterious overwrites, components appearing in places you didn't expect, and the terrifying moment when a "quick fix" in production breaks something that worked fine in dev. This is the moment solutions stop being an afterthought and become the architectural backbone of your entire delivery process.
By the end of this lesson, you'll understand not just how to work with Dataverse solutions, but why they work the way they do — and you'll have the mental models to design a solution architecture that scales across teams, apps, and environments without turning into a nightmare to maintain.
What you'll learn:
Before diving in, you should be comfortable with:
A Dataverse solution is a container. That's the blunt version. It holds references to components — tables, columns, forms, views, business rules, security roles, flows, apps, and more — and packages them so they can be exported from one environment and imported into another.
What makes solutions interesting (and occasionally maddening) is what they don't do: they don't copy data. They move metadata and configuration. When you add a table to a solution and export it, you're exporting the schema definition of that table, not the rows inside it. This distinction matters enormously when you're thinking about deployment.
A solution also doesn't have to contain everything. You can have a table that exists in five different solutions simultaneously. The solution is a logical grouping, not an ownership boundary. This is one of the first architectural decisions you'll face: how granular should your solutions be?
Let's build up the full picture before we answer that.
When you add a component to a solution, you're either adding it as a root component or as a subcomponent. A table is a root component. The columns, forms, views, and relationships on that table are subcomponents. When you add a table to a solution in the modern interface, you get a dialog asking whether you want to include just the table definition, the table plus all subcomponents, or the table plus selected subcomponents.
This granularity is powerful and dangerous in equal measure. If you add only a table's form (not the table itself) to a solution and export it, the table must already exist in the target environment — you're shipping a customization to an existing component, not the component itself.
Key insight
Solutions don't own components — they reference them. A component can exist in many solutions simultaneously. When you export a solution, you export a snapshot of all referenced components at that moment. This is why the question "which solution is this table in?" is the wrong question. The right question is "which solutions reference this table?"
Every solution is associated with exactly one publisher. A publisher has three things that matter deeply:
The prefix is where most teams make their first and most consequential mistake.
When you create a custom column on a table inside a solution associated with a publisher with the prefix contoso, the column's schema name becomes contoso_columnname. This prefix is permanent. If you later change the publisher prefix, existing components don't get renamed — the prefix is baked into the schema name at the moment of creation.
This means that if you create components under the default Microsoft publisher (which uses the cr prefix or sometimes auto-generates something like cr8a2 based on your environment ID), those names are essentially random. When you try to move these to a managed solution for production deployment, the schema names look like chaos.
Warning
Never build production-bound customizations using the default Microsoft publisher. Create your own publisher immediately, before you create a single custom component. Changing prefix associations after the fact requires deleting and recreating components — a painful process on a live schema with existing data.
In the Power Apps maker portal, navigate to Solutions in the left sidebar, then select "Publishers" from the top navigation. Create a new publisher with:
Choose your prefix carefully. It will appear in every schema name, every API call, every Power Automate flow that references your custom columns, every JavaScript file that touches your forms. Keep it short but recognizable. If you're building a product for multiple customers, the prefix should reflect the product, not the customer.
You can also set a choice value prefix — a numeric value prepended to all global choice option values created under this publisher. This prevents collisions when two publishers both define global choices with values like 1, 2, 3. Use a number like 100000 for one publisher and 200000 for another.
One publisher per organization is the right default. Multiple publishers make sense when:
Multiple publishers in a single environment get complex quickly. When two publishers' solutions both modify the same component (say, a form), you get into solution layering territory — which we'll cover in detail shortly.
With your publisher configured, you create a solution by navigating to the Solutions area, clicking New Solution, giving it a display name, a name (used in the URL and export filename), a version number, and selecting your publisher.
Version numbers follow the format Major.Minor.Build.Revision (e.g., 1.0.0.1). They matter primarily for managed solution deployment — the import process will warn you if you try to import an older version over a newer one. For unmanaged solutions in development, version numbers are largely ceremonial, but it's good hygiene to increment them meaningfully as you reach milestones.
The components you should explicitly add to your solution:
What you typically should not add:
Tip
Use the "Add existing" workflow for components you've already created outside a solution context, and use the "New" workflow inside the solution for components you haven't created yet. The latter approach is cleaner because the component is associated with your solution from birth.
There's a genuine architectural debate about whether to use one big solution per environment versus many small solutions.
Monolithic (one solution per app): Simple to reason about, easy to deploy, and works well for teams where one person or a small group maintains everything. The risk is that a single large solution becomes hard to move incrementally — every deployment moves everything, even unchanged components.
Segmented (multiple solutions per environment): More complex but more powerful. You might have:
The segmented approach lets you deploy updates to just the form layer without touching the data model. It also lets you share the core data model across multiple apps in the same environment.
The downside is dependency management. If your Sales App solution references a column defined in Core Data Model, you need to export and import them in the right order. Dataverse tracks these dependencies and will block an import that references components not already present in the target environment.
Key insight
Dataverse maintains a dependency graph between solution components. If Solution B contains a form that uses a column defined in Solution A, then Solution A is a prerequisite for Solution B. You can inspect these dependencies in the solution interface by selecting a component and choosing "Show Dependencies." This graph becomes your deployment manifest — get familiar with it before your first production deployment.
This is the most misunderstood topic in the Dataverse solutions ecosystem, and getting it wrong creates problems that are genuinely difficult to recover from.
An unmanaged solution is a working copy. When you import an unmanaged solution into an environment, its components merge directly into that environment's customization layer. You can open those components, edit them freely, and add them to other solutions.
Crucially, deleting an unmanaged solution does not delete the components it contained. It just removes the logical grouping. The components remain in the environment.
This is why your development environment should almost always use unmanaged solutions. You want full edit access, you want to be able to iterate freely, and you're not worried about protecting the implementation from modification.
A managed solution is a locked deployment artifact. When you import a managed solution into an environment:
The managed layer is how you ship a product. When your customer's environment has your managed solution installed, they can see and use the components but they can't break your implementation by editing the underlying table structure or form logic.
Warning
You cannot convert an environment where components were created unmanaged into a "managed environment" retroactively by exporting managed. If you've been building unmanaged in a production environment directly, you're in the anti-pattern that Microsoft calls "the big ball of mud" — and extraction requires careful planning. Always build in a dedicated dev environment with unmanaged solutions, then deploy managed to production.
Here's the precise mechanical flow:
Notice that you're exporting the same solution in two formats. The managed export is a different package — it's not just a flag flip. The managed package contains a manifest that tells the target environment "this is a managed component; lock it."
You can also export as unmanaged if you're moving a work-in-progress to another developer's environment or backing up a development state. But never import unmanaged into production — once you do, you lose the ability to use managed uninstall, and you can no longer cleanly layer managed updates on top.
When a managed solution owns a component, these operations are restricted in the target environment:
Some operations are still allowed — notably, adding the managed component to an unmanaged solution and making additive customizations. This is the customization layer concept that we'll now examine in detail.
When you export a managed solution, you can configure managed properties on components to control exactly how much customization is allowed. Navigate to a component in your solution, open the managed properties (there's a button in the component's command bar), and you'll find toggles for:
For an ISV shipping a product, you might lock down your core forms while keeping tables extensible. For internal tooling, you usually leave everything customizable so that local teams can adapt the solution to their needs.
This is where solutions get genuinely sophisticated. Dataverse doesn't just have one version of a component — it maintains a stack of layers, one per solution that has touched that component. Understanding this stack explains almost every mysterious behavior you'll encounter in multi-solution environments.
For any given component (say, the main form on your custom Account Request table), Dataverse maintains layers in this order, from bottom to top:
When Dataverse renders the component, it starts from the bottom and applies each layer in sequence. The topmost layer wins on any property that conflicts.
You'll see these terms in the solution layer viewer (Solutions > select your solution > Command bar > "Solution layers" on any component). The active layer is the result — the merged, final state of the component as seen by users. The individual entries in the layer stack are the contributing sources.
Here's where it gets subtle: the unmanaged customization layer (the topmost layer) is always from your environment's own customizations. If a user goes into the environment's default solution and modifies a form that was originally shipped in a managed solution, that modification sits in the unmanaged layer and overrides the managed layer below it.
This is intentional — it's the extensibility mechanism. But it has consequences:
Key insight
The "Publish All Customizations" button in the maker portal publishes the current state of the unmanaged layer to the active layer. This is why you sometimes see environments where a managed solution has been updated but nothing appears to have changed — someone's unmanaged overrides are blocking the managed updates from surfacing.
In the Power Apps maker portal, navigate to Solutions and open any solution. Select a component (e.g., a form), and in the command bar at the top, look for the "Solution layers" button. This opens a panel showing every solution that has a stake in this component, ordered from active (top) down to base.
For each layer, you can see:
If you see an unmanaged layer from "Default Solution" sitting on top of your managed solution's layer for a form, that means someone has made manual edits to that form in the environment. This is often the source of "why isn't my update showing up?" investigations.
Imagine you deploy version 1.0 of your Sales App managed solution to production. Your main form has five sections. A power user with System Administrator role opens the form editor in production and adds a sixth section for "Internal Notes." This creates an unmanaged customization layer on top of your managed form.
Three months later, you deploy version 2.0 of your managed solution, which reorganizes the form significantly — reordering sections and renaming tabs. You import cleanly. But in production, the form looks wrong: it has your new structure plus the "Internal Notes" section from the unmanaged layer, now in an awkward position because Dataverse tried to merge an unmanaged delta onto a restructured managed base.
This is the classic layering conflict. Resolution options:
Remove the unmanaged customization: In the Solution Layers viewer for that form, delete the unmanaged layer. Your managed form takes full effect, and the "Internal Notes" section disappears. You've lost that customization.
Incorporate the customization into your managed solution: In your dev environment, add the "Internal Notes" section to your official form design and ship it in version 2.1. Now it's part of the managed layer and won't conflict.
Use a separate unmanaged solution for local customizations: Document and manage the unmanaged overrides as a deliberate "local customization layer" — an unmanaged solution that sits on top of your managed foundation and is expected to be maintained separately.
Option 2 is almost always the right answer for internal tooling. Option 3 makes sense when you're an ISV shipping to multiple customers who each have legitimate local customization needs.
For anything beyond a simple single-app deployment, you need to think about solution architecture deliberately. Here are the patterns that work in practice.
Structure your solutions like this:
Foundation Solution (Managed): Contains all custom tables, base columns, global choices, and relationship definitions. This changes infrequently — only when the data model changes. Because designing a Dataverse data model is foundational work, this solution is your most stable artifact.
App Solution (Managed): Contains forms, views, the model-driven app itself, site map, business rules, and business process flows. This changes more often — form tweaks, new views for reporting, updated business rules as requirements evolve.
Security Solution (Managed): Contains security roles. Security roles change on their own cadence — often when new features are added or when audit findings require adjustment. Separating them means you can update security roles without redeploying your entire app.
Automation Solution (Managed): Contains Power Automate flows, connection references, and environment variables. Flows change frequently and often independently of UI changes.
This separation lets you say things like "we're deploying a security role update this afternoon — it doesn't touch any forms, so there's no regression risk on the UI." That's a powerful operational statement.
Environment variables are Dataverse components that hold a schema definition (key name and type) in one solution, and a value in the environment where the solution is deployed. This means your flow can reference contoso_salesforcebaseurl as an environment variable, and in dev it resolves to your Salesforce sandbox URL, while in production it resolves to the live Salesforce URL — without any code change.
Always put environment variable definitions in your managed solution. The values are set in each environment during or after deployment, not baked into the solution package. This is critical: if you include values in the managed package, they overwrite the existing values on every import, which is almost never what you want in production.
When a Power Automate flow uses a connector (SharePoint, Outlook, Dataverse itself), the solution stores that as a connection reference — an abstract pointer that gets wired to an actual connection in the target environment. During import, you'll be prompted to either select an existing connection or create a new one for each connection reference.
Get connection references into your solution before you build flows that depend on them. If you build a flow that creates its own connection outside a connection reference, moving that flow through environments becomes a manual reconfiguration exercise.
In the maker portal, navigate to Solutions, select your solution, and click Export. You'll be asked to publish any pending customizations first — always do this, or your export may not include recent changes.
The export dialog asks:
The export downloads a .zip file. Inside is a solution.xml manifest, a customizations.xml with all component definitions, and subdirectories for more complex components like flows and canvas apps.
Tip
Keep a dated archive of every managed export. When something breaks in production and you need to roll back, you want the previous known-good .zip immediately. Store these in your source control system (Azure DevOps, GitHub) alongside any code artifacts. This also enables you to diff the XML between versions to understand exactly what changed — which is invaluable for debugging mysterious behavior after deployment.
Navigate to Solutions in the target environment, click Import, and upload the .zip. The import wizard shows you:
The import runs asynchronously. A progress indicator shows you when it's complete. Check the import result carefully — a "completed with warnings" status sometimes hides significant problems in the detail logs.
When importing a managed solution that already exists in the target environment, you get two options:
Update: Applies the new version on top of the existing managed solution. Components added in the new version are created. Components modified are updated. Critically, components removed from the solution in the new version are not deleted from the environment. This is the safer choice for incremental updates.
Upgrade: Applies the changes and then deletes components that were removed from the solution. This is the clean option for major version changes where you've deliberately dropped tables, columns, or views. The upgrade actually happens in two stages — "Stage for Upgrade" and "Apply Solution Upgrade" — which lets you verify the staged state before committing the deletion.
Use Update by default. Use Upgrade when you've explicitly removed components and want the environment to reflect those removals cleanly.
Warning
If you use Upgrade and you've removed a column from your solution that has data in production, that column and its data will be deleted. There is no confirmation dialog specifically for data loss — the system only confirms component removal. Always audit what you've removed from a solution before using Upgrade in production.
For teams doing continuous delivery, manually exporting and importing .zip files doesn't scale. The Power Platform CLI (pac) and the Power Platform Build Tools for Azure DevOps let you automate this:
# Install the CLI
npm install -g @microsoft/powerplatform-cli
# Authenticate to an environment
pac auth create --environment https://yourorg.crm.dynamics.com
# Export a solution
pac solution export --name YourSolutionName --path ./solutions/YourSolutionName.zip --managed
# Unpack the solution into source-controllable format
pac solution unpack --zipFile ./solutions/YourSolutionName.zip --folder ./solutions/YourSolutionName
The unpack command extracts the .zip into individual XML and JSON files that map to individual components. These diff cleanly in source control, making pull request reviews meaningful — you can see exactly which form fields changed, which views were added, which security role privileges were modified.
# Pack from source and import to another environment
pac solution pack --zipFile ./solutions/YourSolutionName_managed.zip --folder ./solutions/YourSolutionName --packageType Managed
pac solution import --path ./solutions/YourSolutionName_managed.zip
This is the foundation of a proper CI/CD pipeline for Power Platform. Combined with environment variables for configuration, you get a repeatable, auditable deployment process.
Dataverse solutions support a concept called patches — sub-solutions that contain only the delta from the parent solution. The idea is appealing: instead of deploying the entire solution when only one form changed, you deploy a small patch containing just that form.
In practice, patches create complexity that outweighs their benefits for most teams. The patch hierarchy is brittle, patches must be applied in order, and the tooling for working with patches is less polished than the tooling for full solutions. Most modern guidance recommends deploying full solution updates with incremented version numbers rather than managing a patch hierarchy.
The exception is when you're shipping to hundreds of customer environments and network transfer size is a real constraint. In that case, patches can meaningfully reduce deployment time. For typical internal deployments, they're not worth the overhead.
The most practical solution segmentation strategy for internal Power Platform teams is to segment by deployment cadence rather than by functional domain. Ask: "How often does this group of components change, and can I deploy them independently?"
This means you might have a CoreDataModel solution, a SalesAppUI solution, and a SalesAppFlows solution — not because they're conceptually separate domains, but because they change at different rates and you want to deploy them independently.
When you customize a standard Microsoft table — say, adding custom columns to the Contact table or modifying the Account main form — you need to add only the specific customized components to your solution, not the entire Contact or Account table.
In the Solution interface, use "Add existing" and drill into the table, then select "Add specific subcomponents." Choose only the columns you added and the form you modified. This keeps your solution lean and avoids shipping Microsoft's standard components alongside your customizations.
Be careful with forms that include subgrids or quick view components — those form components may reference tables or relationships that also need to be in your solution or present in the target environment.
Note
If your solution includes customizations to a standard table's form, and Microsoft ships an update to that form through a Microsoft solution update (which happens during major release waves), your unmanaged customizations will layer on top. Usually this works fine, but if Microsoft restructures the form significantly, the merge can produce unexpected results. After major Microsoft release waves, verify your customized standard table forms in a sandbox before updating production.
This exercise builds a properly structured solution setup from scratch. You'll need access to a Power Platform development environment where you have System Administrator rights.
Create three solutions, all using your new publisher:
Solution 1 — Core Data Model:
WSC Core Data ModelWSCCoreDataModel1.0.0.0Inside this solution, create a new custom table called Project Request with these columns:
wsc_title (Single Line of Text, required)wsc_requestedbudget (Currency)wsc_status (Choice: Draft, Submitted, Approved, Rejected)wsc_targetlaunchdate (Date Only)Also add a lookup to the standard Contact table to track the requesting contact.
Solution 2 — Project App UI:
WSC Project App UIWSCProjectAppUI1.0.0.0Add the Project Request table to this solution (add only subcomponents you'll customize: the main form and default view). Create a meaningful main form with appropriate sections. Create a view called "My Open Requests" filtered to the current user's draft and submitted requests. Build a model-driven app that exposes this table.
Solution 3 — Security:
WSC SecurityWSCSecurity1.0.0.0Create a security role called "Project Requester" that grants Create, Read, and Write access to Project Request at the User level. Add this security role to the Security solution.
WSC Core Data Model as managed.WSC Project App UI as managed.WSC Security as managed.Open the exported .zip files and examine the customizations.xml inside. Find the <Entities> section to see your table definitions. Note how the prefix appears throughout — every schema name, every attribute name.
In the same development environment (we'll simulate the layering pattern locally):
Almost always a layering issue. Check the Solution Layers viewer for the specific component (form, view, etc.). If there's an unmanaged layer from Default Solution sitting above your managed layer, your managed updates are being overridden. Either remove the unmanaged layer or incorporate those customizations into your managed solution.
The imported solution references a component that doesn't exist in the target environment. Common causes:
Check the error detail for the specific component name, find which solution it belongs to, and deploy that first.
Two common causes:
Prefixes are baked into schema names at creation time. Changing the publisher prefix doesn't rename existing components. The only fix is to create new components with the correct prefix and migrate data if needed — which is why getting the prefix right before you create anything is so important.
Business process flows are special — they're also Dataverse processes, and they need to be activated after import. After importing a managed solution that contains a BPF, navigate to Power Automate > My Flows (or the legacy Processes view) and activate the BPF. This is a known post-import step that many teams forget.
You've included environment variable values in your solution package. Solution variable values in a managed package overwrite what's in the environment on every import. Remove the values from your development solution (leave the definitions), and set values directly in each environment. This is the intended pattern — definitions travel with the solution, values are environment-specific configuration.
Tip
After any significant import to production, run a quick smoke test on the highest-traffic forms and workflows before announcing the deployment. A successful import status from the platform doesn't guarantee that every component renders correctly — layering conflicts and missing references can create runtime errors that the import process doesn't catch.
Solutions are the infrastructure layer of Power Platform delivery. Getting them right doesn't just make your deployments smoother — it's what separates a maintainable system from one that becomes a liability over time.
The key principles to carry forward:
Publisher first. Before you create a single custom component in a development environment, create your publisher with a deliberate, permanent prefix. This decision cannot be undone without destructive effort.
Managed for production. Development environments use unmanaged solutions for maximum flexibility. Test and production environments receive managed solutions that can be cleanly updated and, if necessary, uninstalled.
Understand layering. When a component behaves unexpectedly, the solution layer viewer is your first diagnostic tool. Unmanaged layers on top of managed ones are the most common source of "why isn't my update working?" problems.
Segment by cadence. Structure multiple solutions based on how often components change and whether they can be deployed independently, not just by conceptual grouping.
Use environment variables and connection references. These are the mechanisms that make a solution truly environment-agnostic. Hard-coding URLs, credentials, or connection details into flows or components eliminates the value of the solution transport mechanism.
From here, the logical next steps in your model-driven app journey involve deepening the components that travel inside these solutions. If you haven't fully explored the security layer, Dataverse Security: Business Units, Security Roles, and Teams covers how security roles interact with the solution framework in detail. And if you're building complex, multi-stage workflows that need to travel with your solution as managed components, Business Process Flows: Guiding Users Through Multi-Stage Processes in Power Apps covers the patterns that work cleanly in a managed deployment context.