Dynamics 365 Solution Design: Stop Putting Everything in One Solution
How you structure your Dynamics 365 solutions determines how painful your deployments will be. Here's a layered approach that actually scales.
If you’ve inherited a Dynamics 365 environment that’s been running for a few years, there’s a good chance you’ve seen it: one solution called something like “Company Customizations” that contains every single component in the system. Tables, flows, apps, security roles, site maps — all of it, in one bucket.
Deploying that solution is a 45-minute gamble. Nobody knows what it’ll break. Nobody wants to touch it.
Here’s how to avoid building that, and how to think about solution architecture from the beginning.
The Core Principle: Segment by Lifecycle
The main question when deciding how to split solutions is: which components change together?
Components that change together should live in the same solution. Components with different deployment cadences should live in different solutions.
This sounds obvious until you’re in the middle of a project and someone says “just throw it in the main solution,” and you do, and six months later you regret it.
A Layered Solution Architecture
The pattern I recommend is three layers:
Layer 1: Foundation
Contains your data model and shared configuration:
- Custom tables and columns
- Relationships
- Global option sets / choices
- Connection references
- Environment variables (definitions only, not values)
- Security roles (base permissions)
This solution changes rarely. When it does change, it’s intentional and reviewed carefully. Managed in production.
Layer 2: Core Business Logic
Contains automation and process:
- Cloud flows (Power Automate)
- Business rules
- Plugins (registered in this solution)
- Custom APIs
- Workflows (classic, if you’re still on them)
This changes more frequently than the foundation but is still gated. Changes here are tested thoroughly before deployment. Managed in production.
Layer 3: User Experience
Contains everything users see:
- Model-driven apps
- Canvas apps (if embedded)
- Dashboards
- Views
- Forms
- Charts
- Site map
This changes most frequently. UX iterations happen fast. Having this separate means you can deploy a form update without touching your table definitions or flows. Managed in production.
Plus: Configuration Solution
A separate solution for environment-specific values:
- Environment variable values
- Connection references (actual values, not definitions)
This solution is unmanaged and deployed manually in each environment. It’s not part of your CI/CD pipeline — it contains the things that differ between environments by design.
Dependency Management
When you have multiple solutions, dependencies matter. Foundation is a prerequisite for Core Logic, which is a prerequisite for UX.
This means:
- When deploying, always deploy in order: Foundation → Core Logic → UX
- When you export, do it in the same order
- Never create a component in UX that references something that should live in Foundation — it’ll force an implicit dependency
Power Platform displays dependencies in the solution explorer under Components → Missing Dependencies. Check this before every export.
Naming Conventions
Use a consistent prefix for your publisher and all components. If your publisher prefix is xyz:
- Solutions:
XYZ_Foundation,XYZ_CoreLogic,XYZ_UX,XYZ_Config - Tables: singular noun —
xyz_customerrequest, notxyz_customerrequests - Columns: describe what it contains —
xyz_approvaldate, notxyz_field1. Booleans as questions:xyz_isapproved,xyz_requiresreview - Flows:
[XYZ] Action - Trigger— e.g.,[XYZ] Send Email - Order Created - Apps:
[XYZ] App Name— e.g.,[XYZ] Customer Service
The prefix in component names is permanent. You can’t change it after creation without recreating the component. Choose it carefully.
Avoid
crmas a prefix — Microsoft’s own Dynamics 365 components usecrminternally. Using it for your custom components creates confusion when reading schema names.
Connection References and Environment Variables
Two components that should be in every solution from day one:
Connection references replace direct connector connections in your flows. Instead of a flow being tied to “John’s Outlook connection,” it uses a reference that gets remapped during deployment. Name them clearly: xyz_SharedDataverse, xyz_OutlookShared. Every flow that touches a connector should use one — otherwise your deployments will break when the original connection isn’t available.
Environment variables store values that differ between environments: API URLs, notification email addresses, feature flags, thresholds. Define the variable in your Foundation solution; set the value in your Configuration solution. Never hardcode environment-specific values in flows or apps.
What About Canvas Apps?
Canvas apps are a special case. They don’t participate in the solution dependency graph the same way model-driven components do. A canvas app that connects to a Dataverse table doesn’t create a formal dependency that shows up in the solution checker.
This means it’s easy to accidentally break a canvas app deployment by changing a column it relies on. My approach:
- Keep canvas apps in the UX solution
- Document Dataverse dependencies manually (or use the App Checker in Power Apps Studio)
- Before changing a column used by canvas apps, search for it using the dependency tool
Managed vs. Unmanaged: A Quick Reminder
Unmanaged solutions in non-production environments are fine — you need them to make changes. But what gets deployed to production should always be managed. Managed solutions:
- Can be uninstalled cleanly
- Prevent direct edits in production (a feature, not a bug)
- Support upgrade and update flows
If your production environment has unmanaged customizations sitting on top of managed solutions, that’s technical debt. At some point you’ll need to reconcile them.
Solution Upgrade vs. Update
When deploying a new version:
- Update: applies changes from the new version on top of the existing managed solution. Faster, but can leave orphaned components if you deleted something in the new version.
- Upgrade: fully replaces the old solution with the new one. Slower, but clean. Orphaned components get removed.
Use Update for small incremental changes. Use Upgrade when you’ve deleted or significantly reorganized components. Always test the upgrade path in a staging environment first.
One More Thing: Publisher Matters
Every solution has a publisher. Every component inherits the publisher’s prefix. If you have multiple publishers in your environment, you’ll end up with components with different prefixes — which is a mess to maintain and support.
Use one publisher per customer/environment. If you’re an ISV, use one publisher for your product. Don’t let the default “Default Publisher” touch anything you actually care about.
One more thing — if you’re still exporting and importing solution zips manually, look at the Power Platform CLI (pac). pac solution clone, pac solution export, pac solution import from the command line. Combined with pac solution unpack for source control, it makes the whole process scriptable. Once you’ve tried it, you won’t go back to the portal.
Related articles
D365 Data Migration: What the Docs Skip
After 50+ implementations, here's what actually works for migrating data into Dataverse — the tools, the phases, the gotchas with lookups and deduplication, and the mistakes I still see teams make.
Writing Dataverse Plugins in C#: What the SDK Docs Leave Out
When Power Automate isn't fast enough and business rules aren't powerful enough, you write a plugin. Here's how to build, debug, and deploy server-side logic in Dataverse — with real code and real opinions.
5 Plugin Errors You'll Hit in Dataverse (and What's Actually Wrong)
Exact error messages, root causes, and fixes for the five Dataverse plugin errors you will hit sooner or later. Save yourself the 2am debugging session.