Naming Conventions & ALM
Consistent names and a clean solution/environment strategy are what let a Power Platform estate scale past one maker. Naming patterns for every component type, plus a practical, source-controlled ALM pipeline — pac CLI, Azure DevOps, GitHub Actions, deployment settings, versioning, and rollback.
Why conventions matter
Names are the cheapest documentation you have. Consistent, predictable names make flows searchable, make support handoffs painless, and make ALM (moving solutions between environments) deterministic. Set the standard once and enforce it in review.
Naming conventions
| Object | Pattern | Example |
|---|---|---|
| Cloud flow | <Domain> - <Trigger> - <Outcome> | HR - New Hire - Provision Accounts |
| Child flow | <Domain> - CHILD - <Function> | HR - CHILD - Send Welcome Email |
| Scope / action | PascalCase, verb-first | Get_Open_Orders |
| Variable | camelCase + type prefix | varCustomerEmail, intRetryCount |
| Solution | <Org><Domain> (no spaces) | ContosoHrOnboarding |
| Publisher prefix | 3–8 lowercase chars | contoso → contoso_ |
| Environment variable | prefix_PascalCase | contoso_GraphClientId |
| Connection reference | prefix_Connector_Purpose | contoso_Office365_Mailbox |
| Dataverse table | Singular PascalCase | PurchaseRequest |
| Dataverse column | prefix_PascalCase | contoso_ApprovalStatus |
The publisher prefix is forever
Your solution publisher’s customization prefix is baked into every schema name and cannot be changed later. Pick a short, organisation-wide prefix before you create the first table — not the default cr xxx.
Naming — every component type
The baseline above covers the components you touch daily. The tables below extend it across the full Dataverse + Power Platform surface so a reviewer never has to guess. The filter box on each table jumps you to a component fast.
Dataverse — relationships, choices, roles & rules
| Component | Pattern | Example |
|---|---|---|
| 1:N relationship | prefix_<Parent>_<Child> | contoso_Account_Orders |
| N:N relationship | prefix_<TableA>_<TableB> | contoso_Project_Resource |
| Lookup column | prefix_<Target>Id | contoso_AccountId |
| Global choice (option set) | prefix_PascalCase | contoso_OrderStatus |
| Choice value (label) | Title Case, human-readable | In Review |
| Choice value (integer) | Publisher-banded, leave gaps | 693650000, 693650001 … |
| Security role | <App> – <Persona> | Onboarding – Approver |
| Business rule | <Table> - <Condition/Action> | Account - Require Phone When Active |
| Business process flow | <Domain> - <Process> | Sales - Lead To Cash |
| Web resource | prefix_/<area>/<name>.<ext> | contoso_/scripts/Account.Form.js |
Leave gaps in choice values
Option-set integer values are permanent. Start at your publisher’s option-value prefix (the platform offers a banded default like 693650000) and increment by 1 — but never renumber an existing value, because rows already store the integer, not the label.
Pro-code — plugins, steps & assemblies
| Component | Pattern | Example |
|---|---|---|
| Plugin assembly | <Company>.<Domain>.Plugins | Contoso.Sales.Plugins |
| Plugin class | <Table><Event>Plugin | AccountPreCreatePlugin |
| Registered SDK step | <Table>: <Message> <Stage> | Account: Create PostOperation |
| Custom API | prefix_<VerbNoun> | contoso_RecalculatePricing |
| Custom connector | <Org> <Service> | Contoso Billing API |
| PCF control | <Namespace>.<ControlName> | Contoso.RatingStars |
Apps & analytics — canvas, model-driven, Power BI
| Component | Pattern | Example |
|---|---|---|
| Canvas app | <Domain> - <Purpose> | HR - Onboarding Portal |
| Model-driven app | <Domain> <Hub/App> | Sales Hub |
| App unique name | prefix_PascalCase | contoso_OnboardingPortal |
| Power BI workspace | <Org> <Domain> [Env] | Contoso Sales [PROD] |
| Power BI semantic model | <Domain> - <Subject> | Sales - Pipeline |
| Power BI report | <Domain> - <Audience> - <Subject> | Sales - Exec - Pipeline |
| Environment variable | prefix_PascalCase | contoso_GraphClientId |
| Connection reference | prefix_Connector_Purpose | contoso_Office365_Mailbox |
Env vars and connection references belong in the solution
Name them with the same prefix discipline and add them to the solution so each environment binds its own value and credential at import time — that is exactly what the deployment settings file in the ALM section populates.
Solution layering
- Author unmanaged in DEV; deploy managed to TEST and PROD. Never edit components directly in a downstream environment.
- Use one publisher across related solutions so prefixes and dependencies stay coherent.
- Keep solutions focused — segment by application/domain rather than one mega-solution, so deployments are independent.
- Add environment variables and connection references to the solution so each environment binds its own config and credentials at import.
Solution strategy
How you carve up solutions decides how independently teams can ship. The goal is small, single-purpose managed layers that import in a predictable order, not one monolith that forces every change through one release.
Core vs feature solutions
- A core (base) solution holds shared tables, global choices, security roles, and connection references that several apps depend on. Version it deliberately and deploy it first.
- Feature solutions layer app-specific flows, apps, and columns on top of the core. They take a *dependency* on core components rather than redefining them.
- Put each component in exactly one solution that owns it; reference (don’t re-add) it everywhere else. Two solutions both claiming ownership is the classic source of layering conflicts.
Segmentation & dependencies
- Segment by adding only the *specific* subcomponents you changed (e.g. three columns of a table) instead of the whole table, so you don’t take ownership of fields another team controls.
- Managed solutions import in dependency order — the platform blocks an import whose required components are missing. Deploy core before features; never create circular dependencies between solutions.
- Use Solution → Show dependencies (or
pac solutionchecks) before promoting, so a missing global choice or connection reference fails in review, not in PROD.
Patches, upgrades & holding solutions
| Method | What it does | When to use |
|---|---|---|
| Update | Overwrites the managed layer in place | Routine changes; fastest, but does not delete removed components |
| Patch | Small child solution on top of a parent version | Hotfix one or two components without re-shipping everything |
| Upgrade (stage + upgrade) | Imports as a holding solution, then merges and deletes removed components | Clean releases where you want removed components actually gone |
Upgrade is the only path that deletes
A plain update leaves orphaned components behind. A staged upgrade imports a *holding* solution (_Upgrade suffix), then applies it — that merge step is what removes components you deleted in DEV. Use upgrades when correctness matters more than speed.
Environment strategy
| Environment | Purpose | Solution state |
|---|---|---|
| DEV | Makers build and iterate | Unmanaged |
| TEST / UAT | Integration + user acceptance | Managed |
| PROD | Live business workloads | Managed, locked |
Keep the Default environment clean
Treat the Default environment as personal-productivity only. Provision dedicated, governed environments for any solution that matters, with DLP policies scoped per environment.
Environment strategy — deep dive
Managed environments
- Turn on Managed Environments for any environment that holds a real workload — it unlocks weekly usage insights, maker onboarding controls, and solution-checker enforcement that can *block* imports that fail security rules.
- Use sharing limits to cap who a canvas app can be shared with (e.g. no security groups, or a hard ceiling) so a maker can’t accidentally broadcast an app tenant-wide.
- Enable pipelines and maker onboarding messaging at the environment level so governance travels with the environment, not a wiki page.
DLP & data boundaries
- Scope DLP policies per environment — production data connectors (Dataverse, SQL, custom APIs) in Business, social/consumer connectors in Blocked, everything else Non-business. Connectors in different groups cannot be combined in one flow or app.
- Set a tenant-wide default DLP that is restrictive, then relax per environment where a workload genuinely needs a connector — default-deny beats default-allow.
- Classify custom connectors explicitly; an unclassified custom connector defaults into a group you may not intend.
Default environment hygiene
Everyone in the tenant can create in the Default environment and it can’t be deleted. Apply a strict DLP to it, route serious work into governed environments, and periodically sweep it for orphaned flows. Never let a production process live there.
Source control
Solutions belong in Git as unpacked source, not as opaque zip blobs. Unpacking turns a release into a reviewable diff and gives you history, branching, and PR gates — the same discipline you’d apply to any codebase.
- Branching: trunk-based with short-lived
feature/*branches offmain; each branch maps to one work item. Tagmainat every PROD release. - PR review: export from DEV →
pac solution unpack→ commit → open a PR. Reviewers read the component XML/YAML diff, not the zip. Require at least one approval before merge. - Solution Checker in CI: run the Power Apps Checker (
pac solution check, or the build-tool/action equivalent) on every PR and fail the build on high-severity findings — this is enforced automatically in Managed Environments. - Conflict handling: unpacked solutions are many small files, so Git can merge most changes. For genuinely conflicting components, resolve in DEV (re-import, fix, re-export) rather than hand-editing solution XML.
Prefer YAML unpack for cleaner diffs
Recent pac CLI can unpack to a YAML source-control format (the solutions/<name>/… layout) that diffs far more cleanly than the legacy Other/Solution.xml form — especially for canvas apps. Native Dataverse Git integration produces the same shape.
ALM & pipelines
- Source-control solutions by unpacking them (
pac solution unpack) into Git — review diffs, not zip blobs. - Promote with Power Platform Pipelines (in-product) for a low-friction path, or Azure DevOps / GitHub Actions with the Power Platform Build Tools for full CI/CD.
- Bind environment variables and connection references per environment at deploy time — never hardcode.
- Gate PROD imports behind approval; keep deployments one-way (DEV → TEST → PROD) and reproducible.
Power Platform Pipelines (in-product)
- Install the Power Platform Pipelines app in a dedicated *host* environment, then define your DEV → TEST → PROD stages once.
- Makers run a deployment from inside the DEV environment — no YAML, no service principal to manage — and the host orchestrates export, build, and managed import.
- Attach pre-/post-deployment approvals and a deployment settings record per stage so connection references and env vars bind automatically. Graduate to Azure DevOps/GitHub only when you need custom build steps.
pac CLI cheat sheet
| Command | What it does | Example |
|---|---|---|
| pac auth create | Add an auth profile for an environment | pac auth create --environment https://contoso-dev.crm.dynamics.com |
| pac auth list / select | Switch between saved profiles | pac auth select --index 2 |
| pac env list / select | List or target an environment | pac env select --environment https://contoso-test.crm.dynamics.com |
| pac solution list | List solutions in the current env | pac solution list |
| pac solution export | Pull a solution zip from Dataverse | pac solution export --name ContosoHrOnboarding --managed --path ./out |
| pac solution unpack | Explode a zip into source files | pac solution unpack --zipfile sol.zip --folder ./src --packagetype Both |
| pac solution pack | Rebuild a zip from source | pac solution pack --zipfile sol.zip --folder ./src --packagetype Managed |
| pac solution import | Push a solution into an environment | pac solution import --path sol.zip --settings-file settings.json --activate-plugins |
| pac solution create-settings | Generate a deployment settings file | pac solution create-settings --solution-zip sol.zip --settings-file settings.json |
| pac solution version | Bump build / revision number | pac solution version --solution-name ContosoHrOnboarding --revision |
| pac solution upgrade | Stage-and-merge a managed upgrade | pac solution upgrade --solution-name ContosoHrOnboarding --async |
| pac solution check | Run Power Apps Checker | pac solution check --path sol.zip |
# Authenticate once, then target DEV
pac auth create --environment https://contoso-dev.crm.dynamics.com
# Export the unmanaged solution and explode it into Git-friendly source
pac solution export \
--name ContosoHrOnboarding \
--path ./out/ContosoHrOnboarding.zip
pac solution unpack \
--zipfile ./out/ContosoHrOnboarding.zip \
--folder ./src/ContosoHrOnboarding \
--packagetype Both
# Rebuild a managed zip from source for a downstream import
pac solution pack \
--zipfile ./build/ContosoHrOnboarding_managed.zip \
--folder ./src/ContosoHrOnboarding \
--packagetype ManagedALM tooling — settings & pipelines
Deployment settings file
Generate it with pac solution create-settings, commit one file per target environment, and pass it at import so connection references and environment variables bind without any interactive prompts.
{
"EnvironmentVariables": [
{
"SchemaName": "contoso_GraphClientId",
"Value": "11111111-2222-3333-4444-555555555555"
},
{
"SchemaName": "contoso_ApiBaseUrl",
"Value": "https://api.contoso.com"
}
],
"ConnectionReferences": [
{
"LogicalName": "contoso_office365_mailbox",
"ConnectionId": "9f66d1d455f3474ebf24e4fa2c04cea2",
"ConnectorId": "/providers/Microsoft.PowerApps/apis/shared_office365"
}
]
}Don’t source-control secrets
Environment variable *values* export with the solution. Keep secrets out of them — reference Azure Key Vault, or inject values from pipeline secret variables / GitHub secrets at deploy time instead of committing them.
Azure DevOps pipeline (Power Platform Build Tools)
trigger:
branches:
include: [ main ]
pool:
vmImage: 'windows-latest'
steps:
- task: PowerPlatformToolInstaller@2
- task: PowerPlatformPackSolution@2
inputs:
SolutionSourceFolder: '$(Build.SourcesDirectory)/src/ContosoHrOnboarding'
SolutionOutputFile: '$(Build.ArtifactStagingDirectory)/ContosoHrOnboarding_managed.zip'
SolutionType: 'Managed'
- task: PowerPlatformImportSolution@2
inputs:
authenticationType: 'PowerPlatformSPN'
PowerPlatformSPN: 'Contoso-PROD' # service connection
SolutionInputFile: '$(Build.ArtifactStagingDirectory)/ContosoHrOnboarding_managed.zip'
UseDeploymentSettingsFile: true
DeploymentSettingsFile: '$(Build.SourcesDirectory)/config/settings.PROD.json'
AsyncOperation: true
MaxAsyncWaitTime: '60'GitHub Actions (Power Platform Actions)
name: deploy-to-prod
on:
push:
branches: [ main ]
jobs:
release:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install Power Platform tools
uses: microsoft/powerplatform-actions/actions-install@v1
- name: Pack managed solution
uses: microsoft/powerplatform-actions/pack-solution@v1
with:
solution-folder: src/ContosoHrOnboarding
solution-file: out/ContosoHrOnboarding_managed.zip
solution-type: Managed
- name: Import to PROD
uses: microsoft/powerplatform-actions/import-solution@v1
with:
environment-url: https://contoso-prod.crm.dynamics.com
app-id: ${{ secrets.PP_APP_ID }}
client-secret: ${{ secrets.PP_CLIENT_SECRET }}
tenant-id: ${{ secrets.PP_TENANT_ID }}
solution-file: out/ContosoHrOnboarding_managed.zip
activate-plugins: trueVersioning & rollback
A solution version is major.minor.build.revision. Treat it like any release artifact: bump it on every build, tag the commit, and keep the previous managed zip so you always have a known-good artifact to fall back to.
| Segment | Bump when | Driven by |
|---|---|---|
| major | Breaking schema / contract change | Manual, deliberate |
| minor | New feature, backward-compatible | Manual per release |
| build | Each CI build | Pipeline build number |
| revision | Hotfix / patch on a build | Patch or pipeline |
- Stamp the build in CI with
pac solution version(or the Set Solution Version task) so the deployed version always traces back to a commit and pipeline run. - Roll forward, not back, by default: the cleanest "rollback" is re-importing the previous managed solution artifact you kept from the last green release — never hand-edit PROD.
- Blue/green-ish promotion: validate the new managed version in TEST/UAT (the "green" slot) against production-like data before you import to PROD; if PROD import fails, the prior managed layer is still intact.
- Keep artifacts immutable: publish every managed zip as a pipeline/Git artifact so any prior version can be re-imported without rebuilding from source.
Managed delete is your real rollback boundary
Removing a managed solution deletes its components *and their data*. For a true rollback, re-import the previous version rather than uninstalling — reserve uninstall for environments you can afford to rebuild.
Tip
Every cheat table above is searchable
Use the filter box on each table to jump to a component or command fast. This whole guide is generated from one structured dataset, so it stays in sync with the MCP reference.