Domain 1: Create a Technical Design
- Start by analysing the technical architecture: break the requirement into solution components and decide how each is implemented, because the exam repeatedly asks you to choose an approach rather than to write code. Name the component, then justify the extension point.
- Prefer low-code (business rules, calculated/rollup columns, flows) over pro-code; only write plug-ins when requirements exceed declarative capabilities (complex server logic, integrations, strict performance needs).
- Decide where business logic belongs by where it must hold. Business rules run on the client and for some scopes on the server; calculated and rollup columns run in the platform; a plug-in runs server-side for every caller including integrations; a cloud flow runs asynchronously across connectors. Validation that must apply to every client belongs server-side.
- Calculated columns automatically compute and store a value when a record is created or updated, with no code; they are the lowest-maintenance choice when the formula fits supported functions.
- Rollup columns aggregate values from related child records (sum, count, min, max, avg) and refresh on a system schedule rather than instantly.
- A synchronous plug-in on Create and Update can populate a column before save when the calculation exceeds what calculated columns support, at the cost of added complexity and performance.
- Choose the table type deliberately: standard tables for data Dataverse owns, virtual tables to surface external data without copying it (accepting that source latency and capability limits apply), elastic tables for very high volume and variable schema with eventual consistency, and a connector when the data should stay in the other system entirely.
- Design the authentication and authorisation strategy up front. External applications authenticate with a Microsoft Entra ID app registration using OAuth 2.0 server-to-server and are mapped to a Dataverse application user holding a least-privilege security role; interactive users come through their own identity and security roles.
- Assess the impact of the platform security features on the design, because they constrain what the code can do: data loss prevention policies decide which connectors may be combined at all, security roles and access levels decide what a caller sees, teams and business units decide the ownership boundary, and row sharing grants exceptions record by record.
- Design reusable Power Apps components before building them: canvas components and component libraries for low-code reuse, Power Apps component framework code components for behaviour that canvas controls cannot express, and client scripting for model-driven form logic. Each has a different deployment and versioning story.
- Design Dataverse code components as a set: Power Fx functions for low-code expression reuse, plug-ins for server-side logic on the event pipeline, and custom APIs for logic you want callable as a named message from clients, flows and integrations.
- Design custom connectors when a REST API must be usable by makers rather than only by developers, and decide up front on its authentication, its actions and triggers, and whether a policy or an Azure Function is needed to reshape requests or responses.
- Design automations by trigger type: automated flows on a Dataverse event, instant flows from a user action, and scheduled flows on a timetable. Child flows carry reusable logic, and error handling belongs in the design rather than added after the first failure.
- Design inbound and outbound integrations separately. Inbound means external systems calling the Dataverse Web API or a custom API; outbound means Dataverse publishing events to Azure Service Bus, Event Hub or a webhook. The direction decides where authentication and retry logic live.
Domain 2: Build Power Platform Solutions
- Solutions are the unit of packaging: develop in unmanaged solutions, deploy managed solutions downstream. Managed solutions are read-only in target environments and support clean uninstall.
- Manage solution layers deliberately: each managed solution and every unmanaged customisation forms a layer, and an unmanaged layer on top of a managed component overrides it and blocks later managed updates from taking effect. Removing that unmanaged layer is how you restore the managed behaviour.
- Import dependent (parent) solutions before solutions that reference them; missing dependencies cause import failure.
- Use segmented (partial) solutions that contain only the changed components instead of full exports to reduce import time and risk.
- Never hardcode secrets in code or solutions; store them as environment variables backed by Azure Key Vault references, never embedding the raw secret value.
- Environment variables externalise per-environment configuration - connection details, URLs, IDs - so the same managed solution is deployed unchanged and the value is supplied per environment. Secrets use an Azure Key Vault reference rather than a stored value.
- Power Platform Pipelines provide in-product ALM: define the pipeline and its stages in a host environment, then makers deploy solutions through it from within the environment. Extend it with the pipeline extensibility hooks when a deployment needs approvals or custom steps.
- Use the Power Platform CLI (pac) for ALM: pac auth create --url, pac solution export/import, pac solution unpack/pack with --packagetype Both|Managed|Unmanaged.
- pac solution import --path MySolution.zip --async --max-async-wait-time 60 imports asynchronously; pac solution unpack --packagetype Both produces source you can commit to version control.
- Automate deployments with Power Platform Build Tools for Azure DevOps or the Power Platform GitHub Actions; these wrap pac CLI tasks like who-am-i, export, import, and pack/unpack.
- Use separate environments per stage (dev, test, prod). Developer environments are free, single-user, and can be reset or deleted when no longer needed.
- Govern environments with environment-creation policies (Power Platform admin center) and routinely delete or reset unused dev/trial environments to control sprawl.
- Apply Data Loss Prevention (DLP) policies and least-privilege security roles to govern connectors and protect data across environments.
- Configure Dataverse security roles to support code components rather than as an afterthought: a plug-in runs in the calling user's context unless it is registered to run as a specific user, so a component that works for an administrator and fails for everyone else is almost always a missing privilege on the role rather than a bug in the code.
- Troubleshoot operational security issues by identifying whose context the operation ran in. Check the security role's privilege and access level for the table, whether the record is in the user's business unit, whether column-level security is masking a field, and whether the application user behind an integration has the role you think it has.
- Avoid unnecessary synchronous plug-in steps and design against infinite trigger loops (e.g., a plug-in on Update that updates the same record without filtering attributes).
- Configure audit retention and auto-deletion policies, and tune plug-in trace log retention, to manage storage and meet governance requirements.
- Monitor with Power Platform admin center analytics, plug-in trace logs, and Application Insights integration for telemetry on flows and apps.
Domain 3: Implement Power Apps Improvements
- Complex Power Fx is a published sub-topic in its own right. Be comfortable with With() to scope intermediate values, ForAll() for row-wise operations, Patch() and Collect() for writes, Sequence() for generation, and error handling with IfError() and Errors().
- Use named formulas and consistent naming for reusable Power Fx logic to keep canvas apps maintainable.
- Build reusable component libraries so a component is authored once and consumed by many apps. Consuming apps do not pick up a new version automatically - the maker must open the app and accept the update - which is the single most commonly tested fact about libraries.
- Call a Power Automate cloud flow from a canvas app to run business logic the app should not do itself: long-running work, calls needing elevated permissions, or anything that must continue after the user closes the app. The flow returns values to the app through a Respond action.
- Troubleshoot canvas and model-driven apps with Monitor, which records every data call, its duration and its result while you reproduce the problem. Pair it with the browser developer tools for network and console errors on the model-driven side.
- For canvas apps, favor delegable data sources and functions so filtering happens at the source; non-delegable operations are capped at the data row limit (default 500, max 2000).
- Delegation is the difference between filtering at the source and downloading rows. A delegation warning means results may be incomplete, not merely slow: non-delegable operations return only up to the data row limit, so the app quietly shows wrong answers. Rewrite the query with delegable functions rather than raising the limit.
- Use asynchronous Web API calls and request only required columns; defer non-essential data with lazy loading (load a screen's data only when navigated to).
- Cache reference data once in a variable or collection and reuse it instead of re-querying on every interaction.
- Pre-load what the first screen needs and defer the rest: use Concurrent() so independent data calls run in parallel rather than in sequence, keep OnStart small, and never load a large table into a collection just to filter it locally.
- Optimise model-driven apps through configuration rather than code: reduce the number of columns and tabs loaded on a form, collapse sections that are not immediately needed, avoid unnecessary subgrids and quick-view forms on the default tab, and keep default views narrow so the initial query is cheap.
- Optimise views and queries by returning only the columns needed and filtering server-side. A view that sorts or filters on a non-indexed column across a large table is a common cause of a model-driven app that feels slow only in production.
Domain 4: Extend the User Experience
- JavaScript is the only supported language for client-side logic in model-driven forms; it runs in the browser on events like OnLoad, OnChange, and OnSave via the Client API.
- Retrieve the form context with executionContext.getFormContext(); pass executionContext as the first parameter to handlers and avoid the deprecated global Xrm.Page.
- Common Client API calls: formContext.getAttribute("telephone1").setRequiredLevel("required"); formContext.getControl("creditlimit").setVisible(false); formContext.data.entity.getId() returns the record GUID.
- Register event handlers on the form rather than wiring them in code where possible: choose the event (OnLoad, OnSave, OnChange, OnRecordSelect for grids), select the library and function, and tick "Pass execution context as first parameter" - forgetting that tick is why getFormContext() fails at runtime.
- Client scripting can call the Dataverse Web API directly with Xrm.WebApi.retrieveMultipleRecords and friends, which is how a form fetches related data without a plug-in. Prefer the async forms and request only the columns needed with $select.
- Configure commands and buttons in the modern command designer using either Power Fx for low-code commands or JavaScript for existing libraries, and use visibility rules so a button appears only when it applies. Power Fx commands are the current default for new work.
- Navigate to a custom page from client script with Xrm.Navigation.navigateTo, specifying the page type and target - a dialog, a side pane or full page - which is how a model-driven app hands off to a canvas-authored custom page.
- Power Apps Component Framework (PCF) builds reusable code components in TypeScript/React for both canvas and model-driven apps; the framework calls init, updateView, getOutputs, and destroy in the control lifecycle.
- Know the PCF lifecycle methods and what each is for: init sets up the component and receives the context, updateView runs whenever data or layout changes, getOutputs returns values back to the host, and destroy cleans up listeners. Work that belongs in init should not be in updateView, which runs repeatedly.
- The component manifest (ControlManifest.Input.xml) declares the component's properties, whether it is a field or a dataset component, its resources, and the features it needs. A feature such as WebAPI or Device must be declared there before the corresponding context API is available.
- Implement the component interfaces the framework expects and use the context features rather than reaching outside them: context.webAPI for Dataverse calls, context.device for camera, location and file pickers, and context.utils for metadata and dialogs.
- Package, deploy and consume a code component through solutions: build with pac pcf push for development, or build the solution and import it for a controlled deployment, then bind the component to a column or grid in the form or view designer.
- In a PCF dataset component, request only needed columns and use delegable queries with filters/Top so paging happens server-side; minimize work inside updateView since it is called frequently.
- Business rules provide no-code field logic (show/hide, require, set value, validate) that runs on the client and, for some scopes, on the server, without JavaScript.
- Form scripts and PCF components are distributed as web resources packaged inside solutions, keeping them under ALM and version control.
Domain 5: Extend the Platform
- Dataverse plug-ins are .NET assemblies that run server-side on the event pipeline in response to messages (Create, Update, Delete, Retrieve, RetrieveMultiple, custom messages).
- The pipeline has stages: pre-validation (stage 10, outside the database transaction), pre-operation (stage 20, inside the transaction before commit), and post-operation (stage 40, after commit).
- The platform passes IPluginExecutionContext carrying InputParameters (including the Target entity), OutputParameters, pre/post entity images, MessageName, Depth, and the initiating/user IDs.
- Throw InvalidPluginExecutionException in pre-validation or pre-operation to cancel an operation and surface a validation error to the user.
- Plug-ins run in a sandbox with a hard 2-minute execution limit and restricted resources; offload long-running or heavy integration work to async flows, async plug-ins, or Azure Functions.
- Register plug-in steps and images with the Plug-in Registration Tool (or pac plugin), specifying message, primary entity, stage, execution mode (sync/async), and filtering attributes.
- Register and read a pre-image instead of issuing a Retrieve to get prior values, reducing round-trips and improving performance.
- Specify filtering attributes on an Update step so the plug-in fires only when the relevant columns change, preventing needless executions and trigger loops.
- Use SharedVariables on the context to pass data between plug-in steps/stages within the same pipeline execution.
- For bulk server-side work use ExecuteMultipleRequest for batching mixed requests, or the optimized CreateMultiple/UpdateMultiple messages for same-table bulk operations.
- Detect bulk or unwanted invocations via the execution context (e.g., check Depth, message, or a custom 'suppress' flag in SharedVariables) and exit early to avoid recursion.
- Query efficiently with QueryExpression or FetchXML using column subsets and paging; on the Web API use $select to return only needed columns and $top for paging.
- Custom APIs and custom actions expose callable server-side logic as endpoints invokable from clients, flows, and integrations, with defined request/response parameters.
- Develop plug-ins in unmanaged solutions in dev, source-control the code, then export and deploy as managed solutions; cache reusable reference data outside the plug-in (e.g., in a Function or cache) with a sensible expiry.
- Configure a custom API message to expose server-side logic as a named, callable message with typed request parameters and response properties, then implement it with a plug-in registered against that message. It is the supported successor to custom actions for new development.
- Dataverse business events publish a defined business-level event that other systems can subscribe to, as distinct from row-level create and update events. Use them when the contract you want to expose is a business occurrence rather than a table change.
- Custom connectors wrap a REST API so Power Apps and Power Automate can consume it; you can define them from an OpenAPI (Swagger) 2.0 JSON definition, a Postman collection, or from scratch.
- Build a custom connector from an OpenAPI definition, from a Postman collection, or from scratch, and import existing definitions from Azure services or GitHub. Extending the OpenAPI definition is how you add actions the imported definition did not describe.
- Custom connector definitions include actions/triggers, authentication, and policies; configure pagination and define the environment scope in which the connector is available.
- Define connector authentication (e.g., OAuth 2.0) in the connector itself rather than embedding API keys in flows; supported types include No auth, API key, Basic, and OAuth 2.0.
- Policy templates modify connector behaviour at runtime without changing the backend: set or rewrite a header, change the host or route, convert a data type, or forward a value. They are the answer when an API is nearly right but needs shaping for makers.
- Develop an Azure Function behind a custom connector when the transformation is beyond a policy - reshaping a payload, aggregating several calls, or adapting an API that does not fit the connector model - and secure it so only the connector can call it.
- The Dataverse Web API is OData v4 over REST and is the standard interface for external apps to query and manipulate data using OAuth 2.0 bearer tokens.
- Send the OAuth token in the Authorization header as 'Bearer <token>'; never embed user credentials in requests.
- Perform operations against the Organization service from server-side code with IOrganizationService (Create, Retrieve, Update, Delete, Associate and the Execute message for everything else), and against the Web API from client and external code with OData.
- Honor HTTP 429 (Too Many Requests) by reading the Retry-After header and applying exponential backoff with jitter before retrying.
- Implement retry policies for the service protection API limits: honour Retry-After, use exponential backoff with jitter, and make the operation idempotent so a retry cannot double-write. The limits are per user per server, measured on request count, execution time and concurrency.
- Send records in a single bulk request (batch) instead of looping one Web API call per record to stay within service protection API limits.
- Optimise for concurrency and transactions: use ExecuteMultiple for mixed batches, CreateMultiple and UpdateMultiple for bulk of one type, and ExecuteTransaction when the operations must succeed or fail together. Optimistic concurrency with a row version prevents a blind overwrite.
- Use Azure Functions on the Consumption plan for pay-per-execution integration logic that needs to scale beyond the plug-in sandbox limits.
- Implement scheduled and event-driven triggers in Azure Functions - a timer trigger for scheduled work, an HTTP trigger for a webhook or connector call, and a Service Bus or Event Hub trigger for events published from Dataverse - so long-running work leaves the plug-in sandbox entirely.
- Authenticate from Azure to Power Platform with a managed identity rather than a stored secret: the function gets a token for its own identity, which is mapped to a Dataverse application user with a least-privilege role. There is then no credential to rotate or leak.
- Power Automate cloud flows (automated, instant, scheduled) orchestrate processes across hundreds of connectors with low-code conditional logic, approvals, and event triggers.
- Configure Dataverse connector triggers and actions precisely: choose the right trigger scope (Organization, Business Unit, User), set the filtering attributes and a trigger condition so the flow does not run on every change, and use the correct action for related data rather than looping.
- Reference flow outputs with expressions like outputs('Get_account')?['body/accountid']; use Compose and Scope actions to structure and trace flow logic.
- Manage sensitive input and output parameters by marking them secure so their values are hidden from run history, and read secrets from Azure Key Vault at run time rather than storing them in the flow definition.
- Implement flow control and error handling with scopes and run-after configuration: a Scope for the try block, a following action configured to run after failure or timeout as the catch, and terminate with a status so the run reports honestly rather than showing green.
- Develop reusable logic with child flows so common steps are maintained once, and remember a child flow must live in the same solution and returns to the parent through a Respond action.
- Use Microsoft Entra ID service principals for unattended connections so a flow does not depend on a named person's account, which is what breaks when that person leaves.
- Plug-in assemblies should run in Sandbox (isolation) mode, which restricts network and resource access and is the secure default; Full Trust is only for on-premises and broadens access.
- Custom connector advanced topics tested include authentication setup, pagination handling, and the environment scope where the connector can be used.
Domain 6: Develop Integrations
- Publish a Dataverse event from a plug-in with IServiceEndpointNotificationService, which posts the execution context to a registered service endpoint. This is the code-side route to emitting an event to Azure from inside the pipeline.
- Register the service endpoint and its step with the Plug-in Registration Tool: create the service endpoint for the destination, then register a step on the message and table that should publish. No custom assembly is needed for the endpoint itself.
- Register an asynchronous service endpoint to Azure Service Bus or Event Hub via the event pipeline for durable, decoupled messaging with built-in retry and dead-lettering.
- Use Dataverse webhooks to POST event data to an external HTTPS endpoint synchronously or asynchronously; Service Bus is preferred for high-volume, guaranteed delivery.
- Choose the listening option to match the delivery requirement: Azure Service Bus for durable, ordered, decoupled messaging with retries; Event Hub for high-volume telemetry-style streams; a webhook for a simple HTTPS endpoint you control; and a Power Automate trigger when the consumer is a low-code process.
- Webhooks and service endpoints publish row created/updated/deleted events from the pipeline to an external consumer for near-real-time integration.
- Replace polling triggers with event-driven Dataverse triggers/webhooks so flows run only on actual changes, reducing API consumption.
- Use asynchronous processing (async plug-ins, async flows) for long-running or non-blocking work so it does not delay the user's transaction; use synchronous logic when results must be enforced before save.
- Server-side validation that must apply regardless of client (web, mobile, API, integrations) belongs in a synchronous pre-operation or pre-validation plug-in, because client-side JavaScript can be bypassed.
- Synchronise data with change tracking rather than by polling for modified timestamps: enable it on the table, then request changes with a delta token and receive only creates, updates and deletes since the last call. Deletes are the part a timestamp scan silently misses.
- Use alternate keys so external records can be addressed by their own identifier - an ERP number, an email - instead of the Dataverse GUID. That removes the lookup round-trip and the need to store Dataverse IDs in the other system.
- UpsertRequest creates the row if the key does not match and updates it if it does, in one call. Combined with an alternate key it is the standard pattern for idempotent synchronisation, and it is what makes a retried batch safe.
- Cache and deduplicate external responses, calling only on real data changes, to minimize cost and throttling against rate-limited APIs.
- In Power Automate, the 'Invoking user' connection runs Dataverse actions under the triggering user's security context; sharing a flow only grants run permission and does not change the run-as identity.
- Authenticate external app access with a Microsoft Entra ID app registration using OAuth 2.0 server-to-server (S2S), mapped to a Dataverse application user with least-privilege roles.
- Diagnose with the Plug-in Trace Log and ITracingService; combine with Visual Studio remote debugging or the Plug-in Registration Tool profiler for step-through debugging.
PL-400 exam tips
- Extend the platform is 30-35% on its own - roughly a third of the exam, and more than twice any other area. Plug-ins, custom connectors, the platform APIs, Azure Functions and Power Automate cloud flows all live there, so that is where preparation time belongs.
- Master the plug-in event pipeline cold: pre-validation (stage 10, outside the transaction), pre-operation (stage 20, inside it) and post-operation (stage 40), synchronous against asynchronous, and what pre-images and post-images give you at each point.
- Know the pac CLI verbs and key flags by heart (auth create, solution export/import/pack/unpack, pcf push, plugin init) - the exam tests exact command syntax rather than the general idea.
- When a question asks for the lowest-effort or lowest-code solution, follow the ladder: business rule or calculated/rollup column first, then a cloud flow, then Power Fx, then JavaScript or PCF, and only then a plug-in. Reach for code only when the requirement genuinely exceeds the tier below.
- Delegation questions have a trap in them: a delegation warning means results may be incomplete, not just slow. The fix is a delegable query, never a raised row limit.
- For integration questions, decide asynchronous against synchronous by whether the work is long-running and whether it must block the save, then pick Service Bus or Event Hub for durable external delivery and a webhook for a simple endpoint.
- For security questions, default to OAuth 2.0 with a Microsoft Entra app registration mapped to a least-privilege Dataverse application user, with secrets in Azure Key Vault referenced through environment variables - or a managed identity, so there is no secret at all.
- Data synchronisation has three named techniques worth memorising together: change tracking to read deltas including deletes, alternate keys to address rows by an external identifier, and UpsertRequest to make the write idempotent.
Study guide FAQ
How is the PL-400 scored and what do I need to pass?
A score of 700 or greater is required to pass, on Microsoft's scaled 1-1000 range, and it is not a simple percentage of questions answered correctly. Microsoft states that you will have 100 minutes to complete the assessment. That is the assessment time rather than the length of the appointment, which also covers check-in and the agreements. Microsoft does not publish a fixed question count for PL-400, and it varies between forms, so prepare against the published skills rather than to a number. There is no penalty for a wrong answer, so answer every question.
What are the six skill areas and how are they weighted?
Create a technical design (10-15%), build Power Platform solutions (10-15%), implement Power Apps improvements (10-15%), extend the user experience (10-15%), extend the platform (30-35%), and develop integrations (10-15%), as of the skills measured dated 19 March 2026. Extend the platform is by far the largest. Two areas were renamed in this revision: "Configure Microsoft Dataverse" became "Build Power Platform solutions" and "Create and configure Power Apps" became "Implement Power Apps improvements", so older study material may use the previous names.
When should I use a plug-in versus a Power Automate cloud flow?
Use a synchronous plug-in for server-side logic and validation that must run inside the transaction and apply regardless of client. Use an asynchronous flow (or async plug-in) for longer-running, non-blocking orchestration across connectors. If work exceeds the 2-minute sandbox limit or needs to scale, offload it to an Azure Function.
Do I need to write actual C# and JavaScript code on the exam?
You will not write large programs, but you must read and reason about code snippets: identify the correct Client API call, the right plug-in stage, the proper context property (such as Target or a pre-image), and correct pac CLI commands. Knowing exact method names and parameters matters.
How current is the exam, and does it still reference Azure AD or Common Data Service?
The exam uses current Microsoft branding: Microsoft Entra ID (not Azure AD) and Dataverse (not Common Data Service). It emphasizes the pac CLI, Power Platform Build Tools/GitHub Actions, PCF, custom connectors, and managed-solution ALM. Always review the official skills-measured document for the latest weighting before your exam.
Official exam sources
The domain names and weightings on this page follow the published exam blueprint. Each source below records what it confirmed and when it was read, so the split can be checked rather than taken on trust.
- Microsoft Learn - Exam PL-400 study guideBlueprint revised March 19, 2026 · link and content verified 8 September 2026Six skills areas: 10-15 / 10-15 / 10-15 / 10-15 / 30-35 / 10-15.