Domain 1: Build Microsoft Power Platform solutions
- Analyse the architecture by naming each component and the extension point that implements it. A code app, for example, has three runtime parts - your code, the Power Apps client library for code apps and the Power Apps host - and the host, not your code, handles end-user sign-in, app loading and the messages shown when an app fails to load, so a design that builds its own sign-in page is solving a problem the platform already owns.
- In the REST API plus custom connector reference architecture, complex logic and multi-source aggregation move out of canvas formulas into an API that makers call through a connector: the Azure Function is protected by one Microsoft Entra ID app registration, a second registration is configured on the connector, telemetry comes from Application Insights, and the registrations are kept separate per environment.
- Design authentication by deciding whose identity each call runs under. Server-to-server authentication maps an Entra ID app registration to an unlicensed Dataverse application user, so every operation runs as that application user rather than the person using the app and the app itself must control who can reach which data; prefer a managed identity over a stored secret wherever the calling service supports one.
- Before writing code, check whether configuration already meets the requirement - business rules, formula columns, cloud flows, Power Fx commands in the command designer - and reach for plug-ins, PCF controls or code apps only when the requirement exceeds them. Questions that ask for the least development effort are usually testing this ladder.
- Place business logic where it must hold: client script and form logic can be bypassed by integrations, so rules that apply to every caller belong server-side in a plug-in or custom API. A Dataverse message operation must complete within 2 minutes, a limit that includes every registered synchronous and asynchronous plug-in, so long-running work belongs in Azure Functions or an asynchronous flow.
- Choose the table type from the workload. Standard tables give relational modelling and transactions; virtual tables surface external data without copying it; elastic tables run on Azure Cosmos DB and scale horizontally for spiky, high-volume data, but they offer strong consistency only within a logical session, don't support multi-record transactions, and can expire rows automatically through the ttlinseconds column; a connector keeps the data in the other system entirely.
- Assess how platform security shapes the design. Managed environments are a premium suite that adds limit sharing, data policies, pipelines, solution checker, IP firewall and Conditional Access on individual apps; data policies classify connectors as Business, Non-Business or Blocked and suspend a flow that mixes them; owner and Entra group teams can hold security roles and own records, while access teams hold no roles and share records.
- Treat business logic as either deterministic or non-deterministic. Mission-critical or irreversible actions such as a payment or a deletion belong in the deterministic layer - a strictly authored topic, agent flow or workflow where the same input always gives the same output - while a hybrid layer lets AI draft an action that an approval or rule must intercept before it takes effect.
- Decision boundaries classify every agent action as runnable without confirmation, needing in-conversation confirmation, or needing offline approval. Generative prompts can be used as nodes inside a deterministic automation, but AI-generated content should get human review before it reaches a customer or drives a business decision.
- Match the user experience to the tool: a code app for a pixel-specific single-page app written in React or Vue that calls connectors directly; a generative page when a model-driven page can be described in natural language; a PCF control for a reusable field or dataset control; a web resource for HTML, script or images used in forms, the site map or the ribbon; an app assistant or autonomous agent for interactive help inside a model-driven app.
- An MCP app widget (preview for Power Apps) is a self-contained HTML file that renders an MCP tool's JSON output as cards, charts or tables inside a chat. The data always arrives at result.structuredContent in app.ontoolresult, and the widget can call the tool again through app.callServerTool only if the tool name was supplied when it was generated; otherwise it is read-only.
- Choose the agent platform with the decision tree: adopt a SaaS agent if one meets the requirements, otherwise build. Copilot Studio is the low-code SaaS option with prebuilt connectors and built-in responsible AI features; Microsoft Foundry is the PaaS option for choosing your own models and running code-first hosted agents; a hybrid connects a Copilot Studio agent to a Foundry agent (preview), and one- to two-week prototypes validate the choice.
- Design generative pages within their limits: in-browser authoring links up to six Dataverse tables, needs an environment in the United States, Great Britain, Australia or Singapore and currently offers only GPT-4.1, so the recommended path is an AI code generation tool such as GitHub Copilot CLI. Connectors, custom APIs and Application Insights telemetry on a page are preview, and a page embedded on a form must accept the recordId input.
- Design MCP servers for Copilot Studio around its rules: it supports the Streamable transport (SSE is no longer supported after August 2025), consumes tools and resources but not prompts, and requires generative orchestration. Authenticate with an API key or OAuth 2.0, and with OAuth add the callback URL that Copilot Studio issues to the app registration.
- Design Dataverse code components as a pair: a plug-in runs server-side on a message in the event pipeline for every caller, while a custom API defines a new named message with typed request parameters and response properties that clients, flows, generative pages and integrations can call, with a plug-in supplying its main operation logic.
- Design agent automations by object type. A flow becomes an agent tool only as a solution flow with the When an agent calls the flow trigger and the Respond to the agent action, and it must answer within two minutes unless Asynchronous response is turned on; Copilot Studio workflows run on the GitHub Copilot harness, consume Copilot Studio capacity per action, and a standard-harness agent flow can't be converted into one.
- Design integrations by direction. Inbound means external code calling the Dataverse Web API, a custom API or the MCP endpoint under an Entra identity; outbound means Dataverse publishing events to Azure Service Bus, Event Hubs or a webhook, and the direction decides where authentication, retries and listeners live.
- Design code apps knowing their boundaries: compiled assets are served from a public endpoint that doesn't support IP-based restrictions, so use Entra Conditional Access; code apps aren't supported in Power Apps for Windows or SharePoint forms, follow canvas app sharing limits, need a Power Apps Premium licence or equivalent for end users, and admins must enable them per environment.
- Design custom agents that act on Power Platform data: the Power Apps MCP server (preview) lets an agent ask a human through request_assistance when stakes are high or log_for_review when it can proceed, an autonomous trigger-based agent's tool must run with maker-provided credentials, and from 1 May 2026 the agent feed supports only agents that create tasks through the Power Apps MCP server.
- Design monitoring in two layers. Application Insights export from the admin center (managed environments only) carries Dataverse diagnostics, Power Automate and Copilot Studio telemetry, while Power Platform alerts compare metrics against thresholds you define, can be placed only on managed environments and evaluate 24-hour aggregates, with a 1-hour review period in preview for code apps only.
- Manage solution dependencies before you ship: a component can't be deleted while others depend on it, import fails when a required component is neither in the solution nor already in the target, and a component in a managed solution can depend only on another managed component.
- Environment variables externalise per-environment settings with the types Decimal number, Text, JSON, Two options, Data source and Secret, where Secret is backed by Azure Key Vault. A current value wins over a default value, both are limited to 2,000 characters, and pipelines and imports prompt for any variable without a value.
- Solution layers decide runtime behaviour: all unmanaged customisations share one unmanaged layer that sits above the managed layers, patches stack on their base solution, and the top layer is what users see. An unexpected behaviour after a managed update usually means an unmanaged layer is still sitting on top of the component.
- Power Platform Pipelines deploy solutions plus connections, connection references and environment variables from a host, prevalidating dependencies first. Extend them with gated extensions - Pre-export step required (OnDeploymentRequested, UpdatePreExportStepStatus), Pre-deployment step required (OnPreDeploymentStarted, UpdatePreDeploymentStepStatus) and delegated deployment under a service principal or the pipeline stage owner - where the cloud flow sets the step status to 20 to complete it or 30 to reject it.
- Automate CI/CD with Power Platform Build Tools for Azure DevOps or the matching GitHub Actions: the Tool Installer task is added once before any other Build Tools task, followed by tasks such as WhoAmI, Checker, Export, Unpack, Pack, Import and Apply Solution Upgrade. Code apps don't support Power Platform Git integration, so their ALM runs through solutions, pac and pipelines.
Domain 2: Extend the user experience
- Client scripts target the Client API object model through the form context: pass the execution context as the first parameter and call executionContext.getFormContext(), then work with formContext.getAttribute and formContext.getControl. The global Xrm.Page object is deprecated, although it still works for backward compatibility.
- Choose the event handler registration approach by need: register OnLoad, OnChange and OnSave handlers in the form designer with Pass execution context ticked, or add them in code with methods such as formContext.ui.addLoaded. In OnSave, getEventArgs().preventDefault() cancels the save, getSaveMode() shows which button saved, and the event can wait for promises returned by async handlers.
- Call the Dataverse Web API from client script with Xrm.WebApi methods such as retrieveMultipleRecords, requesting only the columns you need. For actions and functions use Xrm.WebApi.online.execute with a request whose getMetadata sets boundParameter to null for an unbound operation, undefined for a CRUD request and entity for a bound one.
- Configure modern commands with Power Fx or JavaScript. Power Fx commands use Self.Selected.Item to act on the selected record, Patch to update it and a Visible formula such as CountRows(Self.Selected.AllItems) > 0 to show a button only when rows are selected; Dataverse is currently the only data source for Power Fx commands, not every function is supported, and commands can't be shared between apps.
- Publishing Power Fx commands can take a few minutes even after the publish appears complete, so refresh the app before deciding a command failed. Classic visibility rules aren't all available in Power Fx, so a migrated command may keep its classic rules, and JavaScript remains the route for commands that reuse existing script libraries.
- Implement navigation, dialogs and notifications with the Client API: Xrm.Navigation.navigateTo opens a page with a pageType such as custom or generative and target 1 for inline or 2 for a dialog, openAlertDialog and openConfirmDialog prompt the user, formContext.ui.setFormNotification shows an ERROR, WARNING or INFO message tied to a uniqueId, and Xrm.App.addGlobalNotification shows an app-level notification.
- To open a generative page from client script, call Xrm.Navigation.navigateTo with pageType generative and the page ID, and pass recordId and entityName when the page must load a specific record; the page itself must be set up to accept those parameters.
- Know what each PCF lifecycle method is for: init(context, notifyOutputChanged, state, container) runs once to set up the control and call trackContainerResize if layout is needed, updateView runs whenever data or layout changes, getOutputs returns values after the control calls notifyOutputChanged, and destroy removes listeners. Keep work out of updateView because it runs repeatedly.
- Follow the code component best practices: request network resources such as metadata in init rather than waiting for updateView, expect null property values in updateView while data isn't ready, bundle the modules you need instead of loading scripts through a SCRIPT tag, and deploy production builds, because development builds hurt performance and can be blocked by their size.
- The manifest (ControlManifest.Input.xml) declares the control, its field or data-set properties, resources and platform libraries, and the features it uses in uses-feature elements inside feature-usage. Declare each feature the control uses, such as WebAPI or device.captureImage, in its own uses-feature element; the feature-usage wrapper is needed only when at least one is declared.
- Implement the right interface: a standard control renders into the container div it receives in init, while a React control implements ReactControl, gets no container div and returns a ReactElement from updateView. React controls share the platform React and Fluent libraries instead of bundling their own copies.
- Package and deploy code components through solutions: pac pcf push is the quick development path, and a solution build and import is the controlled ALM path. Once imported, the component is bound to a column or a grid in the form or view designer.
- Use the context features rather than reaching outside the framework: context.device offers captureImage, captureAudio, getBarcodeValue, getCurrentPosition and pickFile; context.utils offers helpers such as hasEntityPrivilege; context.webAPI offers createRecord, retrieveMultipleRecords and the other record operations; and context.Copilot (preview) lets a control call Copilot Studio topics.
- Connect a code app to a connector in two steps: pa connection create --connector <id> creates the connection in the environment set in power.config.json and returns its ID, then pa app add data-source --connector <id> --connection-id <id> generates typed model and service files the app imports. Excel Online (Business) and Excel Online (OneDrive) are the connectors not yet supported.
- Tabular data sources need --table and --dataset (for SharePoint the list name and the site URL), and connectors that offer both tables and actions take --as action or --as table, which a non-interactive CI/CD run must supply. Bind to a connection reference with --connection-ref and its logical name plus --solution-id so the app stays portable across Dev, Test and Prod.
- Add a native Dataverse table with pa app add data-source --connector dataverse --table <logical name>, one table per command. Code apps delegate filter, sort and top to Dataverse, page with maxPageSize and skipToken in getAll, and return formatted choice labels, but polymorphic lookups, FetchXML, alternate keys and metadata CRUD aren't yet supported.
- Use the generated service correctly: get and delete take the primary key value, and a create payload must leave out system-managed and read-only columns such as accountid and the ownerid fields. Updates should send only changed properties, because resubmitting the whole retrieved record updates every column and can fire business logic and audit entries needlessly.
- getMetadata on a generated table service wraps the Web API table definition query and returns typed entity, attribute and relationship definitions. Request schema.columns to read Attributes[].DisplayName.UserLocalizedLabel.Label for localised labels, set schema.manyToOne for lookup relationships, and filter on IsRequiredForForm to build validation, but don't expect choice options, which derived attribute types hold.
- Call Dataverse actions and functions from a code app with @microsoft/power-apps 1.1.1 or later: pa app find-dataverse-api --search matches operation names, and pa app add dataverse-api --api-name <name> reads $metadata and generates a service class. For a bound action such as AddToQueue the first argument is the record ID, and you rerun the add command to regenerate after the operation changes.
- Code apps call only solution-aware instant flows that use the Power Apps trigger. pa app list-flows shows only solution-aware flows, pa app add flow --flow-id generates a typed service with a static Run method whose result carries success, data and error, and end users need Dataverse permissions such as the App Opener role to invoke the flow.
- Reach a Copilot Studio agent from a code app through the Microsoft Copilot Studio connector and call ExecuteCopilotAsyncV2 (Execute Agent and wait), which returns responses synchronously; ExecuteCopilot only returns a conversation ID. The call needs message, agentName (case-sensitive, usually with the publisher prefix, from the published agent's Web app connection string) and a notificationUrl placeholder, and code should check completed before reading lastResponse.
- Bind code app data sources to environment variables by prefixing their schema names with @envvar: in arguments such as --dataset and --table; power.config.json keeps the reference so the app resolves each environment's value after the solution moves. pa app list-environment-variables lists what is available, and getContext() exposes the environment ID for code that must branch per environment.
- Code apps enforce a Content Security Policy by default, with connect-src set to 'none', so a fetch to an external API fails until its origin is added. Custom values are appended to a directive's default except when the default is 'none', which they replace; embedding the app in a model-driven app or another site needs that host added to frame-ancestors.
- CSP for code apps is set per environment by an environment administrator in the admin center or through the Power Platform API settings PowerApps_CSPEnabledCodeApps, PowerApps_CSPReportingEndpoint and PowerApps_CSPConfigCodeApps. An API update replaces the whole directive collection, and turning on reporting while enforcement is off gives a report-only rollout.
- Monitor a code app with Application Insights by installing @microsoft/applicationinsights-web with the resource's connection string and passing a logger to setConfig, whose logMetric receives the built-in sessionLoadSummary and networkRequest metrics. Telemetry starts only after the app loads, so startup failures need Power Platform Monitor, and an enforced CSP may block telemetry until the endpoint is added to connect-src.
- Trend app-open performance by querying sessionLoadSummary for successful launches and taking the 75th percentile of timeToAppInteractive rather than the average, and summarise networkRequest by URL and day to find slow data sources. appLoadResult reports optimal or other, with a reason such as throttled or interactionRequired.
- Publish with pa app push: on first publish without --solution-id the app goes to the preferred solution, then the Default solution. In CI/CD set PA_CLI_SOLUTION_ID to the solution GUID, and for service principal publishing set the PA_CLI_SP variables and run --non-interactive after a maker has shared the app once with pa app share --principal <Enterprise Application object ID> --access edit.
- Troubleshoot code apps in browser DevTools by filtering Network on apihub.net for connector calls and dynamics.com for Dataverse calls. When pa app add data-source fails, compare power.config.json environmentId with pa auth status and switch accounts with pa auth switch; in a Fiddler trace, a 401 or 403 means authentication or permission, while no response suggests a proxy or firewall.
- The maker-portal Monitor page lists the top three underperforming apps per metric, with a code apps tab showing app open success rate, session count, time to interactive and time to full load. getContext() exposes host.sessionId, which changes each time the app opens, for correlating a session with platform telemetry.
- Optimise code app resource use by always passing select to getAll, caching table metadata at app start and requesting a column list instead of all columns. Client library v1.0 removed initialize, so migrated apps drop the isInitialized gate and make data calls directly.
Domain 3: Extend Microsoft Power Platform
- The event pipeline has PreValidation (before the database transaction, the place to cancel cheaply), PreOperation (inside the transaction, the place to change values being saved) and PostOperation (inside the transaction, the only stage that can run asynchronously). An exception at any synchronous stage inside the transaction rolls the whole operation back.
- A plug-in receives IPluginExecutionContext: InputParameters carries the Target, OutputParameters the results, SharedVariables passes data between steps in the same pipeline, Depth reveals recursion, and PreEntityImages and PostEntityImages hold registered snapshots. Read what the context already supplies before issuing another request.
- Implement validation by throwing InvalidPluginExecutionException in PreValidation or PreOperation so the user sees the message and nothing is saved. Remember that the whole message operation, including all registered plug-ins, must complete within 2 minutes.
- Register pre-images and post-images on the step to get earlier or later column values without a Retrieve, and request only the columns the logic needs. A pre-image gives the value before the change, which is how an Update plug-in compares old and new.
- Inside a plug-in, get IOrganizationService from the service factory and use Create, Retrieve, Update, Delete and Execute; the step's Run in User's Context setting defaults to Calling User, and impersonating another user is the fix when callers lack the privileges the step needs. Trace with ITracingService and read the plug-in trace log when diagnosing.
- Optimise plug-ins by setting filtering attributes on Update steps so they fire only when relevant columns change, exiting early on unexpected Depth to avoid loops, avoiding unnecessary synchronous steps, and considering merging plug-ins into fewer assemblies for performance and maintainability.
- Configure a custom API message carefully, because Is Function, Binding Type, Bound Entity Logical Name, Unique Name and Enabled for Workflow can't be changed after saving. A bound custom API gets its table parameter automatically, Execute Privilege Name requires an existing privilege, and Enabled for Workflow needs Is Function set to false.
- Register assemblies, steps and images with the Plug-in Registration tool: choose the message, primary table, stage, execution mode and filtering attributes, and use Execution Order when several steps share a stage. The tool also registers service endpoints and custom APIs.
- The sandbox event framework supports plug-ins built for .NET Framework 4.8, and legacy 4.6.2 and 4.7 builds still run; plug-ins and custom workflow activities keep using the Microsoft.CrmSdk.CoreAssemblies packages rather than the ServiceClient package used by external .NET applications.
- A custom API's main operation plug-in is registered as a plug-in type with no step: it reads InputParameters and writes OutputParameters at the main operation stage. Configuration can't be passed to it, so a plug-in that needs configuration is registered on a PostOperation step with Allowed Custom Processing Step Type set at creation, and a private custom API can't be used from a plug-in.
- The Plug-in Registration tool and the Plug-in Profiler can't select the main operation stage for debugging, so the documented workaround is to register the plug-in type on the PostOperation stage of the custom API message while you debug it.
- Business events expose a business occurrence for subscribers. A table or custom API becomes a business event only when catalogued - a root catalog, category catalogs and CatalogAssignment rows - and is then consumed through the Dataverse When an action is performed trigger, which users other than Environment Makers can see only with Read on Catalog and CatalogAssignment.
- A custom API that represents an external business event has no main operation plug-in, Allowed Custom Processing Step Type set to Async Only, and request parameters only. Good business events have clear intent, are specific so subscribers don't filter, stay lightweight, and are never used to transfer or export data.
- Assigning a table to a catalog category exposes its Create, Update and Delete events, and a user-owned table also exposes GrantAccess, ModifyAccess and RevokeAccess, which an organisation-owned table does not. Row create, update and delete events don't appear in the When an action is performed trigger because the When a row is added, modified or deleted trigger covers them.
- Power Platform managed identity lets a Dataverse plug-in reach Azure resources such as Key Vault without stored credentials: sign the assembly or package with a certificate (self-signed for development only), add a federated identity credential to the app registration or user-assigned managed identity, create the managed identity record, bind the plug-in to it and grant it access to the resource.
- In plug-in code, request a token with IManagedIdentityService.AcquireToken(scopes). Use managed identity version 2, which hashes the full certificate DN; version 1 fails on certificate names with commas or non-ASCII characters, pac managed-identity upgrade-version moves an existing plug-in to version 2, and AADSTS700213 means the runtime subject matches no federated credential.
- Configure the federated identity credential with the Other issuer option, the issuer https://login.microsoftonline.com/<tenant ID>/v2.0, an explicit subject identifier built from the encoded tenant, environment ID and certificate hashes, and the case-sensitive audience api://AzureADTokenExchange; a mismatch in any segment produces AADSTS700213.
- The Dataverse Web API is an OData v4 RESTful endpoint for external and client code: query with $select, $filter and paging, call actions with POST and functions with GET, and use conditional headers such as If-Match for concurrency. Request only the columns you need on every query.
- On the Organization service, ExecuteMultipleRequest batches independent requests with ContinueOnError and ReturnResponses settings, while ExecuteTransactionRequest runs requests in one transaction that rolls back on failure and reports FaultedRequestIndex. ExecuteMultiple can contain ExecuteTransaction but not the other way round.
- Service protection limits are evaluated per user over a 5-minute window. A throttled Web API request returns 429 with a Retry-After header in seconds, the .NET SDK returns the Retry-After TimeSpan in OrganizationServiceFault.ErrorDetails, and retry logic must wait that long; Xrm.Tooling.Connector versions after 9.0.2.16 pause and resend automatically.
- Optimise for throughput and concurrency: use CreateMultiple and UpdateMultiple for bulk operations on one table, send parallel requests up to the x-ms-dop-hint value (the concurrent-request limit is 52 by default) with server affinity disabled, and set ConcurrencyBehavior to IfRowVersionMatches so a stale update fails rather than overwriting.
- Elastic tables partition rows by the partitionid column: once set at creation it can't change, Retrieve, Update and Delete must address the row with the KeyForNoSqlEntityWithPKPartitionId alternate key, and the partitionId parameter is ignored on Create, Update and Upsert. Elastic tables don't make ExecuteTransaction or $batch changesets atomic and don't support deep insert.
- Authenticate to Dataverse with OAuth through Microsoft Entra ID using MSAL (ADAL is no longer supported), sending the access token as a bearer token. Code that acts for a signed-in user uses delegated sign-in, while server-to-server code authenticates as an app registration mapped to an application user.
- Background operations (preview) run a custom API asynchronously: send it with Prefer: respond-async to get 202 Accepted, a Location status monitor URL and x-ms-dyn-backgroundoperationid, or execute ExecuteBackgroundOperation through an OrganizationRequest in the SDK. The plug-in still has the two-minute limit, and the caller needs read and write privileges on backgroundoperations.
- Track background operations properly: poll the status monitor at most once a minute with GET (it also accepts DELETE), expect a completion callback to carry no output parameters, and use a self-sufficient SAS URL when the callback needs authentication. An operation can be cancelled only before it starts, and OnBackgroundOperationComplete accepts only asynchronous steps.
- The Dataverse SDK for Python (pip install PowerPlatform-Dataverse-Client, Python 3.10 or later) creates DataverseClient(environment URL, credential) with any Azure Identity TokenCredential: interactive or Azure CLI credentials for development, client secret or certificate credentials for unattended code. It retries transient failures and supports read-only SQL SELECT queries with paging.
- In the Python SDK, client.records.create with a list calls CreateMultiple and returns GUIDs, update with one dictionary applies the same change to every ID, upsert takes UpsertItem with an alternate key, and client.dataframe.update skips nulls unless clear_nulls=True. New .NET applications use ServiceClient from Microsoft.PowerPlatform.Dataverse.Client, while plug-ins keep the core assemblies.
- Move long-running work out of the plug-in sandbox into Azure Functions, and know the timeouts: an HTTP-triggered function has 230 seconds to respond regardless of functionTimeout, which defaults to 5 minutes (maximum 10) on Consumption and 30 minutes on Flex Consumption, Premium and Dedicated plans. Durable Functions manage state, checkpoints and retries for longer workflows.
- Consumption is now a legacy hosting plan, and new serverless function apps use Flex Consumption. Pick the plan by timeout and scaling needs rather than by habit, and return an HTTP response quickly while deferring the long work.
- Timer triggers use six-field NCRONTAB with a leading seconds field, so 0 */5 * * * * runs every five minutes. Schedules run in UTC unless WEBSITE_TIME_ZONE is set (not on Linux Consumption or Flex Consumption), and only one instance of a timer function runs across a scaled-out app.
- A TimeSpan schedule works only on an App Service plan and sets the interval between invocations; if a run takes longer than the interval, the timer fires again immediately. A timer function doesn't fire again while an invocation is still running.
- For event-driven triggers on Dataverse messages, a Service Bus trigger with AutoCompleteMessages false lets code settle messages itself, while Service Bus handles poison messages; an Event Hubs trigger checkpoints when the function completes, even after an exception, so delivery is at-least-once, the consumer group defaults to $Default and cardinality many delivers batches.
- Authenticate Functions with managed identities instead of secrets. A system-assigned identity belongs to one app and is deleted with it, a user-assigned identity can be shared by several apps, identity-based connections use settings such as __credential=managedidentity plus __clientId for a user-assigned identity, and the identity needs an RBAC role on the target service.
- Flow expressions reward precision: substring takes a zero-based start and a length, formatDateTime uses MM for month and mm for minutes, dayOfWeek returns 0 for Sunday, contains and replace are case-sensitive, first on an empty array returns null, and the ?['key'] operator stops a missing level from throwing.
- Implement error handling with a Try scope and a Catch scope configured to run after failure or timeout, use Filter array over result('Try') to isolate the failed action, end with Terminate and a chosen status, and build a run link from workflow() metadata for notifications.
- Configure Dataverse triggers deliberately: Select columns applies only to Update, doesn't support lookup columns and fires when a listed column is in the update even if unchanged; concurrency control can't be turned off once enabled; and the default retry policy sends up to 12 retries on Medium and High performance profiles but two on Low.
- A polling trigger picks up the events that happened while the flow was turned off when it is turned back on, whereas a webhook trigger processes only new events. The Dataverse trigger's Delay until property delays the run to a UTC time and never expires, unlike the Delay until action.
- A child flow must be in a solution with the Manually trigger a flow trigger and is called with Run a Child Flow; it returns values with Respond to a Power App or flow or the Response action. Connections can't be passed from the parent, so set run-only users to Use this connection, and keep parent and child in the same solution so they stay linked after import.
- A parent waits for its child for the flow's lifetime - one year for flows that use only built-in connections and Dataverse, 30 days for others - and a child flow doesn't inherit Process licence capacity from the parent's flow group, so add both flows to the same flow group to share it.
- The Dataverse connector supports Client Certificate Auth, OAuth and Service Principal authentication, and an OAuth connection can't be shared, whereas a service principal connection can be shared with the app. A service principal can own a flow once its application user exists and it replaces the owner, but a premium flow it owns needs a Process or per-flow licence or another licensing route.
- The Dataverse MCP server lives at https://<org>.crm.dynamics.com/api/mcp; only the Copilot Studio client is enabled by default, and other clients such as GitHub Copilot are enabled per environment in the admin center. delete_record and delete_table need explicit user approval, read_query runs Dataverse SQL SELECT queries, and use by agents created outside Copilot Studio is charged from 15 December 2025.
- Non-Microsoft MCP clients connect through the @microsoft/dataverse npm local proxy (Node.js 18 or later) or directly to /api/mcp with a custom Entra app added to the allowed clients. Allow-listing applies only to the /api/mcp entry point, so custom APIs named after MCP are ordinary Dataverse APIs that it doesn't restrict.
- When a Foundry agent uses an MCP tool, choose its authentication by access model: key-based or Entra authentication for one shared identity, Entra user delegation to reuse the caller's identity, or OAuth identity passthrough so each user signs in. require_approval set to always produces mcp_approval_request items the app answers, and allowed_tools limits the callable tools.
- Connect a Foundry agent to Copilot Studio (preview) only if it was built in the new Microsoft Foundry portal and its Activity protocol endpoint is enabled through the REST API or Python SDK. Use A2A for agents that implement the Agent-to-Agent protocol (enter the communication endpoint, not the agent card URL), custom connectors for plain APIs and MCP for MCP tools.
- The MCP client and A2A client channels (preview) expose a published Copilot Studio agent to outside clients: the client app registration needs delegated MCS.InvokeAsMCP or MCS.InvokeAsA2A, requests run as the signed-in user, and one registration can serve several clients and agents in the tenant.
- Business Applications in Work IQ (preview) brings Dynamics 365 Sales, Customer Service and model-driven app data to agents: turn on Work IQ in the environment with Dataverse search indexing, then enable Microsoft 365 Copilot in each model-driven app and publish it. In Copilot Studio the Business apps workload appears only after Enable preview workloads in the agent's Work IQ settings.
- The Dataverse Ask APIs (preview) answer natural-language questions at /api/iq/v1.0/ against a named semantic model, outside the OData service, with no $select or $filter and no SDK classes, and they respect the caller's data access. Use the Web API for precise create, retrieve, update, delete and query, and the Ask APIs when the input is a question.
- To call a Copilot Studio agent from your own app with the client library, register an app with the delegated Power Platform API permission CopilotStudio.Copilots.Invoke and supply the connection string from the Microsoft 365 Agents SDK channel, or the environment ID and schema name. The samples sign users in interactively; don't assume service-to-service authentication.
- Choose the integration method by authentication: the Agents SDK client signs the user in through Entra ID and lets you build your own UI, the default Web Chat iframe embed works only when the agent uses No authentication, and Direct Line covers scenarios the SDK doesn't fit.
- With @microsoft/agents-copilotstudio-client, create a CopilotStudioClient with connection settings and a token, start the conversation with startConversationStreaming to get its ID, then send messages with sendActivityStreaming. For programmatic use, have the agent return fixed JSON through an event activity, and post a custom event to pass context because the default iframe exposes no Web Chat store.
Domain 4: Develop integrations
- Publish an event from code by getting IServiceEndpointNotificationService from the service provider and calling Execute(new EntityReference("serviceendpoint", id), context). In an asynchronous plug-in a failed post reruns the whole plug-in, so keep the plug-in to preparing and posting the context.
- Payloads over 192 KB lose ParentContext, InputParameters and the entity images and gain MessageMaxSizeExceeded, and a message still too large isn't sent. A synchronous plug-in may post to Service Bus to retrieve information through a two-way or REST contract, but shouldn't use it to update an external system.
- Publish without code by selecting the service endpoint in the Plug-in Registration tool and registering a step for the message and table; Dataverse then posts the context asynchronously through a system job, whose status under System Jobs shows success or failure. Registration needs System Customizer or System Administrator and an Azure namespace set up for SAS authorisation.
- Register service endpoints for Azure Service Bus queues, topics, one-way, two-way and REST contracts, for Event Hubs, or for webhooks. Endpoints support only port 80 for HTTP and 443 for HTTPS, and any contract other than a queue needs an active listener for the post to succeed.
- Webhooks POST the execution context to an HTTPS endpoint you control, authenticated by HttpHeader, WebhookKey or HttpQueryString values, and can run synchronously or asynchronously, whereas Service Bus steps are asynchronous only. A webhook call times out after 60 seconds.
- Recommend the listener by delivery need: a Service Bus queue when the listener needn't be online and messages must persist, a topic for several subscribers, Event Hubs for high-volume event streams, a webhook for a simple endpoint, an Azure Function trigger to process messages in code, and a Power Automate Dataverse trigger when the consumer is a low-code process.
- Synchronise with change tracking instead of polling timestamps: once enabled on a table it can't be disabled, a Web API request with Prefer: odata.track-changes returns an @odata.deltaLink, and the next call with that delta token returns only new, changed and deleted rows. $filter, $orderby, $expand and $top aren't supported with track-changes, and the SDK uses RetrieveEntityChangesRequest.
- Alternate keys let integrations address rows by their own identifiers. Key columns can be Decimal Number, Whole Number, Single line of text, Date Time, Lookup or Option Set without field-level security, a table can have up to ten keys within SQL index limits of 900 bytes and 16 columns, and a key on a lookup uses the lookup property name such as _primarycontactid_value.
- Key values containing characters such as / < > * % & : ? + break GET and PATCH by alternate key, so query with $filter instead. In the SDK, put key values in KeyAttributes through Entity(logicalName, keyName, keyValue) or a KeyAttributeCollection, not in Attributes.
- UpsertRequest creates or updates in one call, and UpsertResponse.RecordCreated tells you which happened. A Web API PATCH without If-Match is an upsert, adding If-Match: * makes it update-only with 404 when nothing matches, and Upsert costs more than Create, so use Create when the row certainly doesn't exist.
- On an elastic table Upsert overwrites or creates the record without raising Create or Update events, so register logic on Upsert too. Alternate key values in an upsert body are ignored when the record exists, because keys can't be changed through upsert.
- Implement custom connector authentication in the connector rather than in flows: No authentication, Basic authentication, API key or OAuth 2.0 (generic or with an identity provider such as Microsoft Entra ID). For OAuth, copy the Redirect URL from the Security page into the Entra app registration, and a connector can use a managed identity instead of a client secret by adding the issuer and subject it shows as a federated credential.
- Policy templates reshape requests and responses at runtime without changing the API: Set Host URL builds the host from a connection parameter, Route Request sends an operation to another path on the same service, Set HTTP Header adds or overrides a header, and Set Query String Parameter can supply defaults. Policies can reference environment variables with @environmentVariables (preview).
- Import connector definitions from an OpenAPI 2.0 file or a Postman Collection V2, each under 1 MB, and build a GitHub webhook trigger by importing a sample OpenAPI definition; OpenAPI 3.0 isn't supported. After import, the General page shows the host and base URL taken from the definition.
- Create a connector for an Azure service from API Management (APIs > Power Platform > Create a connector), and configure a CORS policy plus an Origin header to test it from the Power Apps console. A connector to Azure Resource Manager uses the delegated Azure Service Management permission and the resource URL https://management.core.windows.net/ with its trailing slash.
- Develop an Azure Function behind a connector by linking its HTTP functions into API Management and downloading the OpenAPI definition. A function-level endpoint needs its key in the x-functions-key header, routes carry the api prefix unless routePrefix changes it, and anonymous access is for development only.
- Extend the OpenAPI definition with x-ms-visibility (important, advanced or internal, where an internal required parameter needs a default), x-ms-trigger to make an operation a trigger, x-ms-dynamic-values to fill options from another operation, and x-ms-url-encoding for path parameters.
- Custom code transforms data in a class named Script that implements ScriptBase and ExecuteAsync, targets .NET Standard 2.0 and takes precedence over policies for its operations. Forward requests with this.Context.SendAsync, branch on this.Context.OperationId, and remember the limit of one script per connector, under 1 MB, finishing within 2 minutes.
AB-400 exam tips
- Extend Microsoft Power Platform is 35-40% of the exam, more than any other area. Plug-ins and custom APIs, platform APIs, background operations, Azure Functions, flows and the Foundry and Copilot Studio agent integrations all sit there, so give it the largest share of your time.
- Code apps are the biggest new block in extend the user experience. Learn the pa command sequence - connection create, add data-source, add flow, add dataverse-api, push, share - and the power.config.json file it maintains, because questions show a terminal session or TypeScript excerpt and ask what is missing.
- When a question asks which approach to use, read the constraint first: least development effort points to configuration, logic that must apply to every caller points to a plug-in or custom API, work beyond two minutes points to Azure Functions or background operations, and irreversible agent actions point to a deterministic topic, flow or workflow.
- Watch for preview conditions. Microsoft says most questions cover generally available features, so when a stem relies on a preview feature such as background operations, generative page connectors, the Power Apps MCP server or Business Applications in Work IQ, it normally says so; don't assume a preview capability exists when the stem doesn't mention it.
- Read code excerpts line by line for the one wrong detail: the stage a step is registered on, a missing uses-feature declaration, key values put in Attributes instead of KeyAttributes, a cron expression without the seconds field, mm used for months, or a bound action whose record ID isn't the first argument.
- Keep the look-alike limits straight: 2 minutes for a Dataverse message and its plug-ins, for an agent flow without asynchronous response and for connector custom code; 230 seconds for an HTTP-triggered function; 60 seconds for a webhook; 192 KB for a service endpoint payload; and at most one poll a minute for a background operation status.
- ALM traps recur: an unmanaged layer hiding a managed update, a managed component that depends on an unmanaged one, environment variables without values in the target, a code app pushed without PA_CLI_SOLUTION_ID or with a solution name instead of its GUID, and a service principal that was never shared on the app.
- Identity questions usually want the option with no secret to rotate: Power Platform managed identity for plug-ins, managed identities for Azure Functions, a federated credential for a custom connector, and an application user with a least-privilege role for server-to-server access.
- If you studied for PL-400, keep the plug-in, client scripting, PCF, Web API and integration knowledge, but expect new names and objects: Microsoft Foundry rather than Azure AI Foundry, Copilot Studio workflows on the GitHub Copilot harness alongside agent flows, the Dataverse and Power Apps MCP servers, and code apps.
Study guide FAQ
How is the AB-400 scored and what do I need to pass?
A score of 700 or greater is required to pass, out of 1000 on Microsoft's scaled score, so treat 700 as a pass mark rather than as 70 per cent of questions answered correctly. Prepare against the published skills outline and its four weighted areas rather than to a question count. If the exam isn't available in your preferred language you can request an additional 30 minutes.
Did AB-400 replace PL-400, and what were the transition dates?
Yes. Registration for PL-400 closed on 16 October 2026 and registration opened exclusively for AB-400, which became available the same day. Candidates who had registered for PL-400 on or before 16 October could still take it through 30 October 2026. AB-400 now counts toward Microsoft Certified: Power Platform Developer Associate.
How do I renew the Power Platform Developer Associate certification?
Microsoft associate certifications expire annually. You renew by passing a free online assessment on Microsoft Learn, and connecting your certification profile to Microsoft Learn is what lets you schedule and renew exams.
Which domain should I focus on?
Extend Microsoft Power Platform at 35-40% is the largest area, followed by extend the user experience at 25-30%, which now includes Power Apps code apps. Build Microsoft Power Platform solutions (15-20%) covers architecture, agent and UX design decisions and ALM, and develop integrations (10-15%) covers Dataverse events, synchronisation and custom connectors.
Do I need to read and write code on the exam?
Yes. Microsoft expects development experience with JavaScript, TypeScript, JSON, Power Fx, C#, Python, HTML and YAML, and questions show excerpts you must read and complete or correct: C# plug-ins and connector scripts, TypeScript for code apps and PCF, JavaScript client scripts, Power Fx commands, flow expressions and Python SDK calls. You also need the pac and pa command-line syntax.
Does the exam test preview features?
Mostly no. Microsoft states that most questions cover generally available features but that preview features may appear if they are commonly used. Many AB-400 topics, such as background operations, MCP app widgets for Power Apps and Business Applications in Work IQ, are preview, so learn them but read each stem for the stated preview condition.
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 - Study guide for Exam AB-400Blueprint revised October 30, 2026 · link and content verified 10 November 2026Four domains totalling 100%: Build Microsoft Power Platform solutions 15-20%, Extend the user experience 25-30%, Extend Microsoft Power Platform 35-40%, Develop integrations 10-15%; leaf skills per the study guide (dated 2026-08-15, updated 2026-10-30; re-fetched 2026-11-09 and identical on all 88 leaf skills).
- Microsoft Learn - Exam AB-400 detailslink and content verified 10 November 2026Replaces PL-400 from 16 October 2026 for the Power Platform Developer Associate certification; English; passing score 700.