Forms in Model-Driven Apps — A Practical Guide

If you spend any time building model-driven apps, you quickly realise that forms are everywhere. Every time someone opens a record — an Account, a Case, a custom Project table — they are looking at a form. Understanding forms deeply is what separates someone who can use the maker portal from someone who can design real applications.

This post starts from the beginning and works through everything you need to know: what forms are, the four types and when each one appears, what a form is made of under the hood, how business logic hooks in, and how you control who sees what.


What Is a Form, Really?

A form in a model-driven app is the interface for a single record in a Dataverse table. When you open an Account record, you see the Account’s Main form. When you open a Contact, you see the Contact’s Main form. Forms and tables have a one-to-one ownership — a Contact form belongs to the Contact table and cannot be used anywhere else.

The form’s job is to surface columns from that record in a structured, organised layout. But it is more than just a field renderer. A form also defines:

  • Which columns are visible, required, or read-only
  • How columns are grouped into sections and tabs
  • What related data appears inline (sub-grids, quick views)
  • What logic runs when the form loads or a field changes

When you make a change in the form designer, you are not touching the data model. You are configuring the presentation layer on top of it.


The Four Form Types

Dataverse has four form types. They are not interchangeable — each one is triggered differently, supports different features, and serves a different purpose.


Main Form

The Main form is what users interact with most. It is the full record view — the page that loads when someone clicks on a row in a list. It supports tabs, sections, sub-grids, business logic, JavaScript, embedded components, and more.

How it is accessed: A user clicks on any record in a view. That is it. The app loads the Main form for that record automatically.

Practical example: Imagine a Sales app with an Opportunity table. Your Main form might have three tabs — Summary (deal name, estimated revenue, close date, owner), Customer (a quick view of the related Account and Contact), and Activity (a timeline of all emails and calls). Everything a sales rep needs in one place, organised logically.

A table can have more than one Main form. You might have a “Field Sales” form with just the essentials, and a “Manager” form that also shows pipeline analytics and approval history. Which one a user sees depends on their security role — more on that later.


Quick Create Form

The Quick Create form is a side-panel that slides in for fast record creation. It shows only the most essential fields and lets the user save without ever leaving the page they were on.

How it is accessed: The + New button in the top navigation bar of the app triggers a Quick Create form if one exists for that table. You can also trigger it from a lookup field on a Main form — when a user types in a lookup and the record does not exist, there is usually a “New Record” option that opens a Quick Create form inline.

Practical example: A support agent is on a Case record and realises they need to create a new Contact for the person calling. They click the lookup field next to “Customer”, type a name that returns no results, and select New Contact. A Quick Create form slides in with just Name, Email, and Phone. They fill it in, hit Save, and the new Contact is linked to the Case — without leaving the Case form at all.

Important constraints: Quick Create forms do not support tabs, Business Process Flows, or JavaScript event handlers. They also require the table to have Enable Quick Create turned on in its table settings — custom tables have this off by default.


Quick View Form

A Quick View form is a read-only window into a related record, embedded directly inside a Main form. It is never opened on its own — it lives inside another form as a component.

How it is accessed: It is not accessed directly. When a user opens a Main form that contains a Quick View control, that Quick View form renders automatically inside the form’s layout — showing data from the related record that the lookup field is pointing to.

Practical example: Your Account Main form has a lookup to a Primary Contact. Rather than making the rep open the Contact record separately every time they want to see a phone number or job title, you embed a Quick View form from the Contact table into the Account form. It renders inline, showing Name, Title, Email, and Mobile. The rep can see it without leaving the Account. If the lookup is empty (no Primary Contact set), the Quick View section simply shows nothing.

A key thing to understand: Quick View fields cannot be edited from the parent form. They are read-only by design. If the user needs to change the contact’s email address, they need to open the Contact record itself.


Card Form

The Card form controls how a record looks when displayed as a tile in a Card-layout view or an interactive dashboard stream. It is not opened, it is rendered — the user never navigates to a Card form, it just appears as a compact representation of a record in a list or dashboard.

How it is accessed: When a view is configured to use the Card layout (instead of the default grid/list layout), each record in that view is rendered using the table’s Card form. The same happens in interactive dashboards that show a stream of records.

Practical example: A team manager has a dashboard showing all open Tasks assigned to their team. Each Task appears as a small card with the Task name, assigned user, and due date. That card layout — which three fields appear, in which positions — is defined by the Card form. Without a custom Card form, Dataverse will use a default fallback. A well-designed Card form makes dashboards scannable; a poorly designed one shows the wrong fields.

Card forms have three fixed slots: Header, Details, and Footer. Each slot holds one or two columns. You do not get free-form layout control here — you work with what the slots give you.


How Each Form Type Is Triggered — A Summary

It is worth making this explicit because it is easy to conflate the form types.

Form TypeWhat Triggers It
MainUser clicks on a record in a view or navigates directly to a record URL
Quick CreateThe + New button in the app nav bar, or the “New Record” option in a lookup field
Quick ViewAutomatically rendered inside a Main form when a lookup has a related record
CardA view or dashboard using the Card layout renders each record using this form

Notice that Quick View and Card forms are never triggered by a user action on their own. They are always passive — embedded or rendered by some other context.


Anatomy of a Main Form

Since the Main form is the richest and most configurable, it is worth understanding exactly what it is made of.


The header is the strip at the very top of the form, always visible no matter which tab the user is on. It typically shows the record name and two to four of the most important columns — things like Owner, Status, or Priority. It also hosts the Business Process Flow bar when one is active.

Think of the header as your at-a-glance summary. Whatever a user needs to always be able to see, regardless of which tab they are on, goes here.


Tabs

Tabs sit below the header and divide the form into named sections. A form can have one tab or many. Common patterns you see in real apps:

  • A General tab with the core record details
  • A Details tab with secondary or less-frequently-edited fields
  • A Related tab (or a dedicated tab per relationship) hosting sub-grids
  • A Notes or Activity tab with the Timeline

Tabs are a navigation device. They let you group logically related fields together without showing everything at once. For tables with many columns — say, a custom Project table with 30+ fields — well-named tabs make the form feel organised rather than overwhelming.


Sections

Inside each tab are sections. A section is a labelled block of fields arranged in a grid. You configure the number of columns in a section (1, 2, 3, or 4). Most forms use 2-column sections, which aligns pairs of related fields side by side — for example, “First Name” on the left and “Last Name” on the right.

Sections can be collapsed by default, which is useful for fields that are relevant occasionally but should not clutter the form on every load.


The footer works like the header — always visible, good for a small number of fields that should always be in view. In practice, the footer is less commonly used than the header but you will encounter it in some standard table forms.


The navigation pane (usually accessed via a Related menu in the Unified Interface) contains links to associated views — for example, on an Account form you might have quick links to “Contacts”, “Opportunities”, “Cases”, and “Activities”. Clicking one opens that sub-list in context without leaving the record.


Field Properties — Separating the Form Layer from the Data Layer

When you add a column to a form, you get a set of properties for that field that are specific to the form. Changing them here does not touch the underlying column definition in the table. This distinction is important.

PropertyWhat It Does
LabelThe display name on the form. You might label a column “Mobile Number” on one form and “Phone” on another — same column underneath.
Requirement LevelOptional, Business Recommended, or Business Required on this form. Making a field required here blocks the save if it is empty, without changing the table column’s constraints.
Read-onlyLocks the field on this form regardless of the user’s write permissions on the column.
Visible by defaultWhether the field shows on load. Business rules can toggle this dynamically at runtime.

A common real-world use of this: you have a “Discount %” column on the Opportunity table. On the Sales Rep form, it is editable and optional. On the Manager form, you make the same column read-only — managers can see it, but only reps can change it. Same column, different form-level behaviour.


Components You Can Embed in a Main Form

Fields are just the start. Main forms support a range of components that pull in richer functionality.


Sub-grid

A sub-grid shows a list of related records inline on the form. It works like a miniature view embedded in a tab. You configure it with a target table and optionally a specific view to define which records appear and in what order.

Example: An Account form has a sub-grid on the “Contacts” tab showing all Contacts related to that Account. The user can see the list, click into any Contact, or create a new Contact directly from the sub-grid — all without leaving the Account.


Quick View Control

This is where Quick View forms are dropped into a Main form. You place a Quick View control in a section, point it at a lookup column on the current table, and choose which Quick View form from the target table to render. The control then displays those fields live, reflecting the current value of the lookup.


Timeline

The Timeline is the activity feed for a record — emails, phone calls, tasks, appointments, notes, all shown in reverse chronological order. It is one of the most used components on any customer-facing table. It is added from the Components panel just like any other element.


Embedded Canvas App

For situations where the native form controls are not enough — say, you want an interactive chart, a custom data-entry wizard, or integration with an external API — you can embed a canvas app directly into a section of a Main form. The embedded canvas app receives the current record’s ID as context and can read or write data accordingly.


Business Rules — Logic Without Code

Business rules give you a way to apply conditional logic to form fields without writing a single line of JavaScript. You define a condition and one or more actions, and Dataverse handles the rest.

Common actions include:

  • Show or hide a field
  • Lock or unlock a field
  • Set a field’s value
  • Mark a field as required or optional
  • Display an error message on a field

Example: You have an Opportunity form with a “Reason for Loss” field that should only appear when Status Reason is set to “Lost”. You create a business rule: If Status Reason equals Lost, show Reason for Loss. Otherwise, hide it. Now the field appears dynamically without any JavaScript.

The important concept here is scope. A business rule can be scoped to:

  • Form only — the rule runs in the browser. It only fires when a user is working through a form. If the record is updated via a Power Automate flow, an API call, or a bulk import, this rule has no effect.
  • Table — the rule runs on the server as well. It fires no matter how the record is created or updated — form, API, import, flow. Use this scope when the rule represents a real data constraint, not just a UI preference.

Most makers default to form-only scope without realising the limitation. If the rule is expressing something that should always be true about your data, set it to Table scope.


JavaScript on Forms

For logic too complex for business rules, Main forms support JavaScript via form event handlers. This is where you register a function — stored in a JavaScript web resource — to run when a specific event fires on the form.

The three events you need to know:

EventWhen It Fires
OnLoadOnce, when the form finishes loading a record
OnSaveWhen the user triggers a save (before or after the save completes, depending on configuration)
OnChangeWhen a specific field’s value changes

Example: When an Opportunity’s Estimated Revenue field changes, you want to automatically calculate and set a “Weighted Revenue” field (Estimated Revenue × Probability). You register a JavaScript function on the OnChange event of Estimated Revenue. The function reads both field values using the formContext API and writes the result to the Weighted Revenue field.

Your JavaScript lives in a web resource file (a .js file uploaded to Dataverse). The function signature receives an executionContext parameter, from which you call executionContext.getFormContext() to get access to the form and its fields. Avoid the older Xrm.Page global — it is deprecated.


Multiple Main Forms, Form Order, and Who Sees What

This is where things get interesting for real app design.

A single table can have multiple Main forms. Each can show completely different fields, tabs, and components. To control which form a specific user sees, you use two mechanisms together.


Security Roles on Forms

Each Main form can have one or more security roles assigned to it. If a form has security roles assigned, only users holding at least one of those roles can see it. If a form has no security roles assigned, it is available to everyone.

Example setup for an Opportunity table:

  • “Sales Rep Form” — assigned to the Sales Representative role. Shows core deal fields and the timeline.
  • “Sales Manager Form” — assigned to the Sales Manager role. Shows everything the rep sees, plus a sub-grid of historical deals and a revenue summary.
  • “Read-Only Form” — assigned to the Finance role. All fields locked as read-only. No ability to edit.

Each role sees a tailored version of the same record.


Form Order

When a user opens a record, Dataverse does not just pick a form at random. It works through the Form Order list — a ranked sequence of all Main forms for the table — and shows the user the first form they have access to.

If the top-ranked form is restricted to a role the user does not hold, Dataverse skips it and tries the next one. This continues down the list until a compatible form is found.

If no form in the list is accessible to the user, they see a bare system fallback form — which is almost never what you want. This happens when every form has security roles assigned and a user’s role is not covered by any of them.

Practical tip: Always have one “catch-all” form with no security roles assigned, placed last in the Form Order. It ensures every user sees something sensible, even if their role was not accounted for in the original design.

Form Order is managed in the maker portal under Tables > [Table] > Forms > Form Order in the command bar.


Form Modes

When a form loads, it is always in one of three modes. The mode determines what a user can do with the record.

ModeDescription
Create (1)A brand new, unsaved record. All editable fields are blank and available to fill in.
Update (2)An existing record, open for editing.
Read-only (3)An existing record that cannot be edited — either because the user lacks write permission, or the form is explicitly configured as read-only.

This matters when you write JavaScript. The function formContext.ui.getFormType() returns the numeric value of the current mode. A common use is auto-populating a field only on record creation — you check if the mode is 1 before setting any default values, so you do not overwrite data when someone reopens an existing record.


Saving and Publishing — A Common Trap

New makers often hit this and get confused. There are two distinct steps when making form changes.

Save records your changes in the solution but makes them invisible to users. The form they see in the live app is still the previous version.

Publish pushes your changes live. Only after publishing will users see the updated form.

If you build a form, save it, and then ask a colleague to test it — they will see the old version. You need to publish first. You can publish from the form designer toolbar, or from the Solutions area using Publish all customizations (which pushes all pending changes at once).


Forms and Solutions

Every form configuration is a solution component. This means forms travel with solutions — the packaging and deployment mechanism in the Power Platform.

The standard flow:

  1. You build and configure forms in a development environment, working inside an unmanaged solution.
  2. When ready to deploy, you export that solution as managed — a sealed, read-only package.
  3. You import the managed solution into test, validate, then import into production.
  4. When you need to make changes, you update the form in development, bump the solution version, and re-import. The new version layers over the existing one cleanly.

This is why you cannot directly edit a form in a production environment that was deployed via a managed solution. The form is locked to the solution layer. All changes must go through the development environment and be redeployed. Understanding this cycle is fundamental to working professionally with the platform.


Summary Reference

MainQuick CreateQuick ViewCard
Triggered byOpening a record from a view+ New button or lookup “New Record”Automatically inside a Main formCard-layout view or dashboard stream
EditableYesYesNoNo
TabsYesNoNoNo
Business rulesYesYes (limited)NoNo
JavaScript eventsYesNoNoNo
Business Process FlowYesNoNoNo
Sub-gridsYesNoNoNo
TimelineYesNoNoNo
Embedded canvas appYesNoNoNo
Multiple per tableYesYesYesYes
Security role controlYesNoNoNo
Form Order appliesYesNoNoNo

Further Reading

All of the topics covered here are documented in depth on Microsoft Learn under the Power Apps model-driven apps section. The key areas to look up are: form types, the form designer, business rules, client API reference (for JavaScript), form order, security roles on forms, and solutions overview.

Comments